From 6f58e0bc9e7667e54d164a0c1c96e43c3a3ebbb5 Mon Sep 17 00:00:00 2001 From: Colbyn Wadman Date: Tue, 5 Feb 2019 13:10:29 -0700 Subject: [PATCH 1/5] =?UTF-8?q?Added=20support=20for=20feature=20overrides?= =?UTF-8?q?=20via=20a=20configuration=20file=20called=20=E2=80=98hie.yaml?= =?UTF-8?q?=E2=80=99.=20See=20=E2=80=98./docs/hie-yaml-file.md=E2=80=99=20?= =?UTF-8?q?for=20details.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +- docs/hie-yaml-file.md | 19 +++++ src/Haskell/Ide/Engine/Plugin/HieExtras.hs | 18 +++-- src/Haskell/Ide/Engine/Transport/LspStdio.hs | 42 +++++----- src/Haskell/Ide/Engine/User/Config.hs | 83 ++++++++++++++++++++ 5 files changed, 138 insertions(+), 27 deletions(-) create mode 100644 docs/hie-yaml-file.md create mode 100644 src/Haskell/Ide/Engine/User/Config.hs diff --git a/.gitignore b/.gitignore index 2ffbdb80f..1777f21b0 100644 --- a/.gitignore +++ b/.gitignore @@ -68,4 +68,5 @@ test-logs/ .vscode # shake build information -_build/ \ No newline at end of file +_build/ +/.hie.yaml \ No newline at end of file diff --git a/docs/hie-yaml-file.md b/docs/hie-yaml-file.md new file mode 100644 index 000000000..7d1f9ae63 --- /dev/null +++ b/docs/hie-yaml-file.md @@ -0,0 +1,19 @@ +# Haskell IDE Engine Specific Configuration + +## File format and options +> `hie` will look for the `hie.yaml` file in `/$PWD/hie.yaml` or `/$HOME/hie.yaml`. + +```yaml +overrides: + # Disables interactive “as you type“ linter/diagnostic feedback. + - on_save_only + # Excludes argument types from autocomplete insertions. + - no_autocomplete_arguments +``` + + +### With regards to atom users: +* If using the ‘linter’ package, setting “Lint on Change” to `false` will have no effect unless you create an `hie.yaml` file with the `on_save_only` option. +* Completion insertions from the ‘linter’ or the ‘atom-ide-ui’ packages in conjunction with 'hie' and 'ide-haskell-hie' will include the argument types. E.g. selecting `mapM` will insert `mapM a -> m b t a` unless your `hie.yaml` file includes the `no_autocomplete_arguments` option. + + diff --git a/src/Haskell/Ide/Engine/Plugin/HieExtras.hs b/src/Haskell/Ide/Engine/Plugin/HieExtras.hs index 7924e8b7b..6025e8c44 100644 --- a/src/Haskell/Ide/Engine/Plugin/HieExtras.hs +++ b/src/Haskell/Ide/Engine/Plugin/HieExtras.hs @@ -58,6 +58,7 @@ import Haskell.Ide.Engine.MonadFunctions import Haskell.Ide.Engine.MonadTypes import Haskell.Ide.Engine.PluginUtils import qualified Haskell.Ide.Engine.Plugin.Fuzzy as Fuzzy +import qualified Haskell.Ide.Engine.User.Config as UserConfig import HscTypes import qualified Language.Haskell.LSP.VFS as VFS import qualified Language.Haskell.LSP.Types as J @@ -123,8 +124,8 @@ mkQuery :: T.Text -> T.Text -> HoogleQuery mkQuery name importedFrom = name <> " module:" <> importedFrom <> " is:exact" -mkCompl :: CompItem -> J.CompletionItem -mkCompl CI{origName,importedFrom,thingType,label} = +mkCompl :: UserConfig.HieConfigFile -> CompItem -> J.CompletionItem +mkCompl userConfig CI{origName,importedFrom,thingType,label} = J.CompletionItem label kind (Just $ maybe "" (<>"\n") typeText <> importedFrom) Nothing Nothing Nothing Nothing Nothing (Just insertText) (Just J.Snippet) Nothing Nothing Nothing Nothing hoogleQuery @@ -133,9 +134,11 @@ mkCompl CI{origName,importedFrom,thingType,label} = argTypes = maybe [] getArgs thingType insertText | [] <- argTypes = label - | otherwise = label <> " " <> argText + | otherwise = if UserConfig.hasUserOverrideRequest UserConfig.NoAutoCompleteArguments userConfig + then label + else label <> " " <> argText argText :: T.Text - argText = mconcat $ List.intersperse " " $ zipWith snippet [1..] argTypes + argText = mconcat $ List.intersperse " " $ zipWith snippet [1..] argTypes stripForall t | T.isPrefixOf "forall" t = -- We drop 2 to remove the '.' and the space after it @@ -337,8 +340,8 @@ instance ModuleCache CachedCompletions where newtype WithSnippets = WithSnippets Bool -- | Returns the cached completions for the given module and position. -getCompletions :: Uri -> PosPrefixInfo -> WithSnippets -> IdeM (IdeResult [J.CompletionItem]) -getCompletions uri prefixInfo (WithSnippets withSnippets) = +getCompletions :: UserConfig.HieConfigFile -> Uri -> PosPrefixInfo -> WithSnippets -> IdeM (IdeResult [J.CompletionItem]) +getCompletions userConfig uri prefixInfo (WithSnippets withSnippets) = pluginGetFile "getCompletions: " uri $ \file -> do let snippetLens = (^? J.textDocument . _Just @@ -390,7 +393,6 @@ getCompletions uri prefixInfo (WithSnippets withSnippets) = map mkModCompl $ mapMaybe (T.stripPrefix enteredQual) $ Fuzzy.simpleFilter fullPrefix allModNamesAsNS - filtCompls = Fuzzy.filterBy label prefixText ctxCompls where isTypeCompl = isTcOcc . occName . origName @@ -438,7 +440,7 @@ getCompletions uri prefixInfo (WithSnippets withSnippets) = | "{-# " `T.isPrefixOf` fullLine = filtPragmaCompls (pragmaSuffix fullLine) | otherwise - = filtModNameCompls ++ map (toggleSnippets . mkCompl . stripAutoGenerated) filtCompls + = filtModNameCompls ++ map (toggleSnippets . mkCompl userConfig . stripAutoGenerated) filtCompls in return $ IdeResultOk result where diff --git a/src/Haskell/Ide/Engine/Transport/LspStdio.hs b/src/Haskell/Ide/Engine/Transport/LspStdio.hs index f14f50df4..f89863c3d 100644 --- a/src/Haskell/Ide/Engine/Transport/LspStdio.hs +++ b/src/Haskell/Ide/Engine/Transport/LspStdio.hs @@ -55,6 +55,7 @@ import qualified Haskell.Ide.Engine.Plugin.ApplyRefact as ApplyRefact import qualified Haskell.Ide.Engine.Plugin.Brittany as Brittany import qualified Haskell.Ide.Engine.Plugin.Hoogle as Hoogle import qualified Haskell.Ide.Engine.Plugin.HieExtras as Hie +import qualified Haskell.Ide.Engine.User.Config as UserConfig import Haskell.Ide.Engine.Plugin.Base import qualified Language.Haskell.LSP.Control as CTRL import qualified Language.Haskell.LSP.Core as Core @@ -120,11 +121,13 @@ run scheduler _origDir plugins captureFp = flip E.catches handlers $ do rin <- atomically newTChan :: IO (TChan ReactorInput) commandIds <- allLspCmdIds plugins + + userConfig <- UserConfig.getUserConfigFile Nothing let dp lf = do diagIn <- atomically newTChan let react = runReactor lf scheduler diagnosticProviders hps sps - reactorFunc = react $ reactor rin diagIn + reactorFunc = react $ reactor userConfig rin diagIn let errorHandler :: Scheduler.ErrorHandler errorHandler lid code e = @@ -368,8 +371,8 @@ sendErrorLog msg = reactorSend' (`Core.sendErrorLogS` msg) -- | The single point that all events flow through, allowing management of state -- to stitch replies and requests together from the two asynchronous sides: lsp -- server and hie dispatcher -reactor :: forall void. TChan ReactorInput -> TChan DiagnosticsRequest -> R void -reactor inp diagIn = do +reactor :: forall void. UserConfig.HieConfigFile -> TChan ReactorInput -> TChan DiagnosticsRequest -> R void +reactor userConfig inp diagIn = do -- forever $ do let loop :: TrackingNumber -> R void @@ -493,20 +496,23 @@ reactor inp diagIn = do -- ------------------------------- - NotDidChangeTextDocument notification -> do - liftIO $ U.logm "****** reactor: processing NotDidChangeTextDocument" - let - params = notification ^. J.params - vtdi = params ^. J.textDocument - uri = vtdi ^. J.uri - ver = vtdi ^. J.version - J.List changes = params ^. J.contentChanges - mapFileFromVfs tn vtdi - makeRequest $ GReq tn (Just uri) Nothing Nothing (const $ return ()) $ - -- Important - Call this before requestDiagnostics - updatePositionMap uri changes - - queueDiagnosticsRequest diagIn DiagnosticOnChange tn uri ver + NotDidChangeTextDocument notification -> + if UserConfig.hasUserOverrideRequest UserConfig.OnSaveOnly userConfig + then liftIO $ U.logm "****** reactor: not processing NotDidChangeTextDocument" + else do + liftIO $ U.logm "****** reactor: processing NotDidChangeTextDocument" + let + params = notification ^. J.params + vtdi = params ^. J.textDocument + uri = vtdi ^. J.uri + ver = vtdi ^. J.version + J.List changes = params ^. J.contentChanges + mapFileFromVfs tn vtdi + makeRequest $ GReq tn (Just uri) Nothing Nothing (const $ return ()) $ + -- Important - Call this before requestDiagnostics + updatePositionMap uri changes + + queueDiagnosticsRequest diagIn DiagnosticOnChange tn uri ver -- ------------------------------- @@ -658,7 +664,7 @@ reactor inp diagIn = do Just prefix -> do snippets <- Hie.WithSnippets <$> configVal True completionSnippetsOn let hreq = IReq tn (req ^. J.id) callback - $ lift $ Hie.getCompletions doc prefix snippets + $ lift $ Hie.getCompletions userConfig doc prefix snippets makeRequest hreq ReqCompletionItemResolve req -> do diff --git a/src/Haskell/Ide/Engine/User/Config.hs b/src/Haskell/Ide/Engine/User/Config.hs new file mode 100644 index 000000000..911935292 --- /dev/null +++ b/src/Haskell/Ide/Engine/User/Config.hs @@ -0,0 +1,83 @@ +{-# LANGUAGE LambdaCase #-} +module Haskell.Ide.Engine.User.Config + ( HieConfigFile + , Override(..) + , hasUserOverrideRequest + , getUserConfigFile + ) +where + +import Control.Monad (join) +import Data.Functor ((<&>)) +import qualified Data.Text as T (pack, unpack, toLower) +import qualified Data.Yaml as Yaml (decodeFileEither) +import qualified Data.Aeson as A (FromJSON(..), withObject) +import qualified Data.Aeson.Types as A (parseFieldMaybe) +import qualified System.Directory as SD (getCurrentDirectory, getHomeDirectory, doesFileExist) +import qualified Data.Maybe as Maybe (fromMaybe, mapMaybe) +import qualified Control.Exception as E (handle, IOException) + + + +newtype HieConfigFile = HieConfigFile + { requestOverrides :: [Override] + } deriving (Show) + +data Override + = OnSaveOnly + | NoAutoCompleteArguments + deriving (Show, Eq) + +emptyHieConfigFile :: HieConfigFile +emptyHieConfigFile = HieConfigFile + { requestOverrides = [] + } + +hasUserOverrideRequest :: Override -> HieConfigFile -> Bool +hasUserOverrideRequest x = elem x . requestOverrides + +getUserConfigFile :: Maybe FilePath -> IO HieConfigFile +getUserConfigFile root = E.handle onIOException go + where + onIOException :: E.IOException -> IO HieConfigFile + onIOException _ = return emptyHieConfigFile + + parse :: FilePath -> IO HieConfigFile + parse filePath = Yaml.decodeFileEither filePath <&> \case + Left _ -> emptyHieConfigFile + Right x -> x + + go :: IO HieConfigFile + go = do + suggested <- join <$> mapM checkForConfigFile root + local <- checkForConfigFile =<< SD.getCurrentDirectory + home <- checkForConfigFile =<< SD.getHomeDirectory + case (suggested, local, home) of + (Just filePath, _, _) -> parse filePath + (_, Just filePath, _) -> parse filePath + (_, _, Just filePath) -> parse filePath + _ -> return emptyHieConfigFile + +checkForConfigFile :: FilePath -> IO (Maybe FilePath) +checkForConfigFile root = SD.doesFileExist hieFilePath <&> \case + True -> Just hieFilePath + _ -> Nothing + where + hieFilePath = appendHieFileName root + +appendHieFileName :: FilePath -> FilePath +appendHieFileName root = root <> "/hie.yaml" + +instance A.FromJSON HieConfigFile where + parseJSON = A.withObject "config file" $ \o -> do + overrides <- A.parseFieldMaybe o (T.pack "overrides") + <&> Maybe.fromMaybe [] + <&> Maybe.mapMaybe (f . T.unpack . T.toLower) + return $ HieConfigFile {requestOverrides = overrides} + where + f :: String -> Maybe Override + f = \case + "on_save_only" -> Just OnSaveOnly + "no_autocomplete_arguments" -> Just NoAutoCompleteArguments + _ -> Nothing + From ad95c12fd1c2067f89e67bda6afcb3d6fe7dd0ca Mon Sep 17 00:00:00 2001 From: Colbyn Wadman Date: Tue, 5 Feb 2019 19:50:53 -0700 Subject: [PATCH 2/5] Whoops, forgot to add module to cabal file. --- haskell-ide-engine.cabal | 1 + 1 file changed, 1 insertion(+) diff --git a/haskell-ide-engine.cabal b/haskell-ide-engine.cabal index d22b6f732..20dd67b67 100644 --- a/haskell-ide-engine.cabal +++ b/haskell-ide-engine.cabal @@ -44,6 +44,7 @@ library Haskell.Ide.Engine.Transport.JsonStdio Haskell.Ide.Engine.Transport.LspStdio Haskell.Ide.Engine.Types + Haskell.Ide.Engine.User.Config other-modules: Paths_haskell_ide_engine build-depends: Cabal >= 1.22 , Diff From 5c0b9474363be71d22a0f29a3a6f8aa1610e8a5c Mon Sep 17 00:00:00 2001 From: Colbyn Wadman Date: Sat, 23 Feb 2019 15:15:04 -0700 Subject: [PATCH 3/5] Use preexisting config format. --- .gitignore | 3 +- README.md | 18 ++++- docs/hie-yaml-file.md | 19 ----- haskell-ide-engine.cabal | 1 - hie-plugin-api/Haskell/Ide/Engine/Config.hs | 52 +++++++++++- src/Haskell/Ide/Engine/Plugin/HieExtras.hs | 14 ++-- src/Haskell/Ide/Engine/Transport/LspStdio.hs | 21 +++-- src/Haskell/Ide/Engine/User/Config.hs | 83 -------------------- 8 files changed, 84 insertions(+), 127 deletions(-) delete mode 100644 docs/hie-yaml-file.md delete mode 100644 src/Haskell/Ide/Engine/User/Config.hs diff --git a/.gitignore b/.gitignore index 1777f21b0..2ffbdb80f 100644 --- a/.gitignore +++ b/.gitignore @@ -68,5 +68,4 @@ test-logs/ .vscode # shake build information -_build/ -/.hie.yaml \ No newline at end of file +_build/ \ No newline at end of file diff --git a/README.md b/README.md index 65225bc75..372cb9edc 100644 --- a/README.md +++ b/README.md @@ -355,15 +355,29 @@ to VS Code user settings. ## Configuration There are some settings that can be configured via a `settings.json` file: -```json +```javascript { "languageServerHaskell": { + // Default: true. "hlintOn": Boolean, - "maxNumberOfProblems": Number + // Default: 100. + "maxNumberOfProblems": Number, + // Disables interactive “as you type“ linter/diagnostic feedback. + // Default: false. + "onSaveOnly": Boolean, + // Excludes argument types from autocomplete insertions (see “With regards to atom users” for elaboration). + // Default: false. + "noAutocompleteArguments": Boolean, } } ``` +> Note that the above comments are for field specific commentary and must be excluded in your real `settings.json` file. + +#### With regards to atom users: +* If you are using the ‘linter’ package, setting “Lint on Change” to `false` will have no effect unless you create a `settings.json` file with the aforementioned `noAutocompleteArguments` option. +* Completion insertions from the ‘linter’ or the ‘atom-ide-ui’ packages in conjunction with 'hie' and 'ide-haskell-hie' will include the argument types. E.g. selecting `mapM` will insert `mapM a -> m b t a` unless your `settings.json` file includes the aforementioned `noAutocompleteArguments` option. +#### Misc. - VS Code: These settings will show up in the settings window - LanguageClient-neovim: Create this file in `$projectdir/.vim/settings.json` or set `g:LanguageClient_settingsPath` diff --git a/docs/hie-yaml-file.md b/docs/hie-yaml-file.md deleted file mode 100644 index 7d1f9ae63..000000000 --- a/docs/hie-yaml-file.md +++ /dev/null @@ -1,19 +0,0 @@ -# Haskell IDE Engine Specific Configuration - -## File format and options -> `hie` will look for the `hie.yaml` file in `/$PWD/hie.yaml` or `/$HOME/hie.yaml`. - -```yaml -overrides: - # Disables interactive “as you type“ linter/diagnostic feedback. - - on_save_only - # Excludes argument types from autocomplete insertions. - - no_autocomplete_arguments -``` - - -### With regards to atom users: -* If using the ‘linter’ package, setting “Lint on Change” to `false` will have no effect unless you create an `hie.yaml` file with the `on_save_only` option. -* Completion insertions from the ‘linter’ or the ‘atom-ide-ui’ packages in conjunction with 'hie' and 'ide-haskell-hie' will include the argument types. E.g. selecting `mapM` will insert `mapM a -> m b t a` unless your `hie.yaml` file includes the `no_autocomplete_arguments` option. - - diff --git a/haskell-ide-engine.cabal b/haskell-ide-engine.cabal index 20dd67b67..d22b6f732 100644 --- a/haskell-ide-engine.cabal +++ b/haskell-ide-engine.cabal @@ -44,7 +44,6 @@ library Haskell.Ide.Engine.Transport.JsonStdio Haskell.Ide.Engine.Transport.LspStdio Haskell.Ide.Engine.Types - Haskell.Ide.Engine.User.Config other-modules: Paths_haskell_ide_engine build-depends: Cabal >= 1.22 , Diff diff --git a/hie-plugin-api/Haskell/Ide/Engine/Config.hs b/hie-plugin-api/Haskell/Ide/Engine/Config.hs index cfaa10b2e..5d3328e9a 100644 --- a/hie-plugin-api/Haskell/Ide/Engine/Config.hs +++ b/hie-plugin-api/Haskell/Ide/Engine/Config.hs @@ -1,9 +1,14 @@ {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE LambdaCase #-} module Haskell.Ide.Engine.Config where import Data.Aeson import Data.Default +import Data.Functor ((<&>)) +import Control.Monad (join) import qualified Data.Text as T +import qualified Control.Exception as E (handle, IOException) +import qualified System.Directory as SD (getCurrentDirectory, getHomeDirectory, doesFileExist) import Language.Haskell.LSP.Types -- --------------------------------------------------------------------- @@ -16,6 +21,38 @@ getConfigFromNotification (NotificationMessage _ _ (DidChangeConfigurationParams Success c -> Right c Error err -> Left $ T.pack err +-- | +-- Workaround to ‘getConfigFromNotification’ not working (Atom Editor). +getConfigFromFileSystem :: Maybe FilePath -> IO Config +getConfigFromFileSystem root = E.handle onIOException go + where + onIOException :: E.IOException -> IO Config + onIOException _ = return def + + parse :: FilePath -> IO Config + parse filePath = decodeFileStrict filePath <&> \case + Just x -> x + Nothing -> def + + go :: IO Config + go = do + suggested <- join <$> mapM checkForConfigFile root + local <- checkForConfigFile =<< SD.getCurrentDirectory + home <- checkForConfigFile =<< SD.getHomeDirectory + case (suggested, local, home) of + (Just filePath, _, _) -> parse filePath + (_, Just filePath, _) -> parse filePath + (_, _, Just filePath) -> parse filePath + _ -> return def + + checkForConfigFile :: FilePath -> IO (Maybe FilePath) + checkForConfigFile dir = SD.doesFileExist settingsFilePath <&> \case + True -> Just settingsFilePath + _ -> Nothing + where + settingsFilePath :: FilePath + settingsFilePath = dir <> "/settings.json" + -- --------------------------------------------------------------------- data Config = @@ -26,6 +63,10 @@ data Config = , liquidOn :: Bool , completionSnippetsOn :: Bool , formatOnImportOn :: Bool + , onSaveOnly :: Bool + -- ^ Disables interactive “as you type“ linter/diagnostic feedback. + , noAutocompleteArguments :: Bool + -- ^ Excludes argument types from autocomplete insertions (see "Configuration" from README.md for details). } deriving (Show,Eq) instance Default Config where @@ -36,6 +77,8 @@ instance Default Config where , liquidOn = False , completionSnippetsOn = True , formatOnImportOn = True + , onSaveOnly = False + , noAutocompleteArguments = False } -- TODO: Add API for plugins to expose their own LSP config options @@ -49,6 +92,9 @@ instance FromJSON Config where <*> o .:? "liquidOn" .!= liquidOn def <*> o .:? "completionSnippetsOn" .!= completionSnippetsOn def <*> o .:? "formatOnImportOn" .!= formatOnImportOn def + <*> o .:? "onSaveOnly" .!= onSaveOnly def + <*> o .:? "noAutocompleteArguments" .!= noAutocompleteArguments def + -- 2017-10-09 23:22:00.710515298 [ThreadId 11] - ---> {"jsonrpc":"2.0","method":"workspace/didChangeConfiguration","params":{"settings":{"languageServerHaskell":{"maxNumberOfProblems":100,"hlintOn":true}}}} -- 2017-10-09 23:22:00.710667381 [ThreadId 15] - reactor:got didChangeConfiguration notification: @@ -60,7 +106,7 @@ instance FromJSON Config where -- ,("maxNumberOfProblems",Number 100.0)]))])}} instance ToJSON Config where - toJSON (Config h m d l c f) = object [ "languageServerHaskell" .= r ] + toJSON (Config h m d l c f saveOnly noAutoArg) = object [ "languageServerHaskell" .= r ] where r = object [ "hlintOn" .= h , "maxNumberOfProblems" .= m @@ -68,4 +114,8 @@ instance ToJSON Config where , "liquidOn" .= l , "completionSnippetsOn" .= c , "formatOnImportOn" .= f + , "onSaveOnly" .= saveOnly + , "noAutocompleteArguments" .= noAutoArg ] + + diff --git a/src/Haskell/Ide/Engine/Plugin/HieExtras.hs b/src/Haskell/Ide/Engine/Plugin/HieExtras.hs index 6025e8c44..5d8693fb7 100644 --- a/src/Haskell/Ide/Engine/Plugin/HieExtras.hs +++ b/src/Haskell/Ide/Engine/Plugin/HieExtras.hs @@ -52,13 +52,13 @@ import qualified GhcMod.Exe.CaseSplit as GM import qualified GhcMod.Gap as GM import qualified GhcMod.LightGhc as GM import qualified GhcMod.Utils as GM +import Haskell.Ide.Engine.Config import Haskell.Ide.Engine.ArtifactMap import Haskell.Ide.Engine.Context import Haskell.Ide.Engine.MonadFunctions import Haskell.Ide.Engine.MonadTypes import Haskell.Ide.Engine.PluginUtils import qualified Haskell.Ide.Engine.Plugin.Fuzzy as Fuzzy -import qualified Haskell.Ide.Engine.User.Config as UserConfig import HscTypes import qualified Language.Haskell.LSP.VFS as VFS import qualified Language.Haskell.LSP.Types as J @@ -124,8 +124,8 @@ mkQuery :: T.Text -> T.Text -> HoogleQuery mkQuery name importedFrom = name <> " module:" <> importedFrom <> " is:exact" -mkCompl :: UserConfig.HieConfigFile -> CompItem -> J.CompletionItem -mkCompl userConfig CI{origName,importedFrom,thingType,label} = +mkCompl :: Config -> CompItem -> J.CompletionItem +mkCompl config CI{origName,importedFrom,thingType,label} = J.CompletionItem label kind (Just $ maybe "" (<>"\n") typeText <> importedFrom) Nothing Nothing Nothing Nothing Nothing (Just insertText) (Just J.Snippet) Nothing Nothing Nothing Nothing hoogleQuery @@ -134,7 +134,7 @@ mkCompl userConfig CI{origName,importedFrom,thingType,label} = argTypes = maybe [] getArgs thingType insertText | [] <- argTypes = label - | otherwise = if UserConfig.hasUserOverrideRequest UserConfig.NoAutoCompleteArguments userConfig + | otherwise = if noAutocompleteArguments config then label else label <> " " <> argText argText :: T.Text @@ -340,8 +340,8 @@ instance ModuleCache CachedCompletions where newtype WithSnippets = WithSnippets Bool -- | Returns the cached completions for the given module and position. -getCompletions :: UserConfig.HieConfigFile -> Uri -> PosPrefixInfo -> WithSnippets -> IdeM (IdeResult [J.CompletionItem]) -getCompletions userConfig uri prefixInfo (WithSnippets withSnippets) = +getCompletions :: Config -> Uri -> PosPrefixInfo -> WithSnippets -> IdeM (IdeResult [J.CompletionItem]) +getCompletions config uri prefixInfo (WithSnippets withSnippets) = pluginGetFile "getCompletions: " uri $ \file -> do let snippetLens = (^? J.textDocument . _Just @@ -440,7 +440,7 @@ getCompletions userConfig uri prefixInfo (WithSnippets withSnippets) = | "{-# " `T.isPrefixOf` fullLine = filtPragmaCompls (pragmaSuffix fullLine) | otherwise - = filtModNameCompls ++ map (toggleSnippets . mkCompl userConfig . stripAutoGenerated) filtCompls + = filtModNameCompls ++ map (toggleSnippets . mkCompl config . stripAutoGenerated) filtCompls in return $ IdeResultOk result where diff --git a/src/Haskell/Ide/Engine/Transport/LspStdio.hs b/src/Haskell/Ide/Engine/Transport/LspStdio.hs index f89863c3d..bde7e620d 100644 --- a/src/Haskell/Ide/Engine/Transport/LspStdio.hs +++ b/src/Haskell/Ide/Engine/Transport/LspStdio.hs @@ -55,7 +55,6 @@ import qualified Haskell.Ide.Engine.Plugin.ApplyRefact as ApplyRefact import qualified Haskell.Ide.Engine.Plugin.Brittany as Brittany import qualified Haskell.Ide.Engine.Plugin.Hoogle as Hoogle import qualified Haskell.Ide.Engine.Plugin.HieExtras as Hie -import qualified Haskell.Ide.Engine.User.Config as UserConfig import Haskell.Ide.Engine.Plugin.Base import qualified Language.Haskell.LSP.Control as CTRL import qualified Language.Haskell.LSP.Core as Core @@ -121,13 +120,12 @@ run scheduler _origDir plugins captureFp = flip E.catches handlers $ do rin <- atomically newTChan :: IO (TChan ReactorInput) commandIds <- allLspCmdIds plugins - - userConfig <- UserConfig.getUserConfigFile Nothing + configFile <- getConfigFromFileSystem Nothing let dp lf = do diagIn <- atomically newTChan let react = runReactor lf scheduler diagnosticProviders hps sps - reactorFunc = react $ reactor userConfig rin diagIn + reactorFunc = react $ reactor configFile rin diagIn let errorHandler :: Scheduler.ErrorHandler errorHandler lid code e = @@ -195,10 +193,8 @@ type ReactorInput -- --------------------------------------------------------------------- configVal :: c -> (Config -> c) -> R c -configVal defVal field = do - gmc <- asksLspFuncs Core.config - mc <- liftIO gmc - return $ maybe defVal field mc +configVal defVal field = + maybe defVal field <$> (liftIO =<< asksLspFuncs Core.config) -- --------------------------------------------------------------------- @@ -371,8 +367,8 @@ sendErrorLog msg = reactorSend' (`Core.sendErrorLogS` msg) -- | The single point that all events flow through, allowing management of state -- to stitch replies and requests together from the two asynchronous sides: lsp -- server and hie dispatcher -reactor :: forall void. UserConfig.HieConfigFile -> TChan ReactorInput -> TChan DiagnosticsRequest -> R void -reactor userConfig inp diagIn = do +reactor :: forall void. Config -> TChan ReactorInput -> TChan DiagnosticsRequest -> R void +reactor configFile inp diagIn = do -- forever $ do let loop :: TrackingNumber -> R void @@ -497,7 +493,7 @@ reactor userConfig inp diagIn = do -- ------------------------------- NotDidChangeTextDocument notification -> - if UserConfig.hasUserOverrideRequest UserConfig.OnSaveOnly userConfig + configVal (onSaveOnly configFile) onSaveOnly >>= \flag -> if flag then liftIO $ U.logm "****** reactor: not processing NotDidChangeTextDocument" else do liftIO $ U.logm "****** reactor: processing NotDidChangeTextDocument" @@ -662,9 +658,10 @@ reactor userConfig inp diagIn = do case mprefix of Nothing -> callback [] Just prefix -> do + configState <- fromMaybe configFile <$> (liftIO =<< asksLspFuncs Core.config) snippets <- Hie.WithSnippets <$> configVal True completionSnippetsOn let hreq = IReq tn (req ^. J.id) callback - $ lift $ Hie.getCompletions userConfig doc prefix snippets + $ lift $ Hie.getCompletions configState doc prefix snippets makeRequest hreq ReqCompletionItemResolve req -> do diff --git a/src/Haskell/Ide/Engine/User/Config.hs b/src/Haskell/Ide/Engine/User/Config.hs deleted file mode 100644 index 911935292..000000000 --- a/src/Haskell/Ide/Engine/User/Config.hs +++ /dev/null @@ -1,83 +0,0 @@ -{-# LANGUAGE LambdaCase #-} -module Haskell.Ide.Engine.User.Config - ( HieConfigFile - , Override(..) - , hasUserOverrideRequest - , getUserConfigFile - ) -where - -import Control.Monad (join) -import Data.Functor ((<&>)) -import qualified Data.Text as T (pack, unpack, toLower) -import qualified Data.Yaml as Yaml (decodeFileEither) -import qualified Data.Aeson as A (FromJSON(..), withObject) -import qualified Data.Aeson.Types as A (parseFieldMaybe) -import qualified System.Directory as SD (getCurrentDirectory, getHomeDirectory, doesFileExist) -import qualified Data.Maybe as Maybe (fromMaybe, mapMaybe) -import qualified Control.Exception as E (handle, IOException) - - - -newtype HieConfigFile = HieConfigFile - { requestOverrides :: [Override] - } deriving (Show) - -data Override - = OnSaveOnly - | NoAutoCompleteArguments - deriving (Show, Eq) - -emptyHieConfigFile :: HieConfigFile -emptyHieConfigFile = HieConfigFile - { requestOverrides = [] - } - -hasUserOverrideRequest :: Override -> HieConfigFile -> Bool -hasUserOverrideRequest x = elem x . requestOverrides - -getUserConfigFile :: Maybe FilePath -> IO HieConfigFile -getUserConfigFile root = E.handle onIOException go - where - onIOException :: E.IOException -> IO HieConfigFile - onIOException _ = return emptyHieConfigFile - - parse :: FilePath -> IO HieConfigFile - parse filePath = Yaml.decodeFileEither filePath <&> \case - Left _ -> emptyHieConfigFile - Right x -> x - - go :: IO HieConfigFile - go = do - suggested <- join <$> mapM checkForConfigFile root - local <- checkForConfigFile =<< SD.getCurrentDirectory - home <- checkForConfigFile =<< SD.getHomeDirectory - case (suggested, local, home) of - (Just filePath, _, _) -> parse filePath - (_, Just filePath, _) -> parse filePath - (_, _, Just filePath) -> parse filePath - _ -> return emptyHieConfigFile - -checkForConfigFile :: FilePath -> IO (Maybe FilePath) -checkForConfigFile root = SD.doesFileExist hieFilePath <&> \case - True -> Just hieFilePath - _ -> Nothing - where - hieFilePath = appendHieFileName root - -appendHieFileName :: FilePath -> FilePath -appendHieFileName root = root <> "/hie.yaml" - -instance A.FromJSON HieConfigFile where - parseJSON = A.withObject "config file" $ \o -> do - overrides <- A.parseFieldMaybe o (T.pack "overrides") - <&> Maybe.fromMaybe [] - <&> Maybe.mapMaybe (f . T.unpack . T.toLower) - return $ HieConfigFile {requestOverrides = overrides} - where - f :: String -> Maybe Override - f = \case - "on_save_only" -> Just OnSaveOnly - "no_autocomplete_arguments" -> Just NoAutoCompleteArguments - _ -> Nothing - From 7c89ef552bc3e58a9442b60e93d16dad9c83e7f1 Mon Sep 17 00:00:00 2001 From: Colbyn Wadman Date: Sat, 23 Feb 2019 15:52:45 -0700 Subject: [PATCH 4/5] Updated test file. --- test/unit/JsonSpec.hs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/unit/JsonSpec.hs b/test/unit/JsonSpec.hs index a24a9e56a..bc5b6f3df 100644 --- a/test/unit/JsonSpec.hs +++ b/test/unit/JsonSpec.hs @@ -102,4 +102,5 @@ instance Arbitrary Position where return $ Position l c instance Arbitrary Config where - arbitrary = Config <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary + arbitrary = Config + <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary From c1f1468bda239da2631f9b9cd913afc237acbc51 Mon Sep 17 00:00:00 2001 From: Colbyn Wadman Date: Sat, 23 Feb 2019 16:27:24 -0700 Subject: [PATCH 5/5] Small changes to work with an older prelude. --- hie-plugin-api/Haskell/Ide/Engine/Config.hs | 28 +++++++++++--------- hie-plugin-api/hie-plugin-api.cabal | 1 + src/Haskell/Ide/Engine/Transport/LspStdio.hs | 6 ++--- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/hie-plugin-api/Haskell/Ide/Engine/Config.hs b/hie-plugin-api/Haskell/Ide/Engine/Config.hs index 5d3328e9a..3d55aa82f 100644 --- a/hie-plugin-api/Haskell/Ide/Engine/Config.hs +++ b/hie-plugin-api/Haskell/Ide/Engine/Config.hs @@ -1,16 +1,18 @@ {-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE LambdaCase #-} module Haskell.Ide.Engine.Config where import Data.Aeson import Data.Default -import Data.Functor ((<&>)) import Control.Monad (join) +import Control.Applicative ((<**>)) +import Data.Maybe (fromMaybe) +import qualified Data.ByteString.Lazy as LBS import qualified Data.Text as T import qualified Control.Exception as E (handle, IOException) import qualified System.Directory as SD (getCurrentDirectory, getHomeDirectory, doesFileExist) import Language.Haskell.LSP.Types + -- --------------------------------------------------------------------- -- | Callback from haskell-lsp core to convert the generic message to the @@ -29,10 +31,8 @@ getConfigFromFileSystem root = E.handle onIOException go onIOException :: E.IOException -> IO Config onIOException _ = return def - parse :: FilePath -> IO Config - parse filePath = decodeFileStrict filePath <&> \case - Just x -> x - Nothing -> def + parse :: FilePath -> IO (Maybe Config) + parse filePath = LBS.readFile filePath <**> return decode go :: IO Config go = do @@ -40,18 +40,20 @@ getConfigFromFileSystem root = E.handle onIOException go local <- checkForConfigFile =<< SD.getCurrentDirectory home <- checkForConfigFile =<< SD.getHomeDirectory case (suggested, local, home) of - (Just filePath, _, _) -> parse filePath - (_, Just filePath, _) -> parse filePath - (_, _, Just filePath) -> parse filePath + (Just filePath, _, _) -> fromMaybe def <$> parse filePath + (_, Just filePath, _) -> fromMaybe def <$> parse filePath + (_, _, Just filePath) -> fromMaybe def <$> parse filePath _ -> return def checkForConfigFile :: FilePath -> IO (Maybe FilePath) - checkForConfigFile dir = SD.doesFileExist settingsFilePath <&> \case - True -> Just settingsFilePath - _ -> Nothing + checkForConfigFile dir = SD.doesFileExist settingsFilePath <**> return f where + f :: Bool -> Maybe FilePath + f True = Just settingsFilePath + f False = Nothing + settingsFilePath :: FilePath - settingsFilePath = dir <> "/settings.json" + settingsFilePath = dir ++ "/settings.json" -- --------------------------------------------------------------------- diff --git a/hie-plugin-api/hie-plugin-api.cabal b/hie-plugin-api/hie-plugin-api.cabal index b80f6c9ab..e29c751a3 100644 --- a/hie-plugin-api/hie-plugin-api.cabal +++ b/hie-plugin-api/hie-plugin-api.cabal @@ -50,6 +50,7 @@ library , text , transformers , unordered-containers + , bytestring if os(windows) build-depends: Win32 else diff --git a/src/Haskell/Ide/Engine/Transport/LspStdio.hs b/src/Haskell/Ide/Engine/Transport/LspStdio.hs index bde7e620d..d8133a39d 100644 --- a/src/Haskell/Ide/Engine/Transport/LspStdio.hs +++ b/src/Haskell/Ide/Engine/Transport/LspStdio.hs @@ -493,9 +493,9 @@ reactor configFile inp diagIn = do -- ------------------------------- NotDidChangeTextDocument notification -> - configVal (onSaveOnly configFile) onSaveOnly >>= \flag -> if flag - then liftIO $ U.logm "****** reactor: not processing NotDidChangeTextDocument" - else do + configVal (onSaveOnly configFile) onSaveOnly >>= \case + True -> liftIO $ U.logm "****** reactor: not processing NotDidChangeTextDocument" + False -> do liftIO $ U.logm "****** reactor: processing NotDidChangeTextDocument" let params = notification ^. J.params