Skip to content

Commit cc23521

Browse files
authored
Merge pull request #691 from alanz/switch-for-import-lens
Introduce generic config for plugins
2 parents 0c7e9a0 + 501b8f9 commit cc23521

File tree

8 files changed

+275
-44
lines changed

8 files changed

+275
-44
lines changed

CONTRIBUTING.md

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Contributors Guide
2+
3+
## Testing
4+
5+
The tests make use of the [Tasty](https://github.com/feuerbach/tasty) test framework.
6+
7+
There are two test suites, functional tests, and wrapper tests.
8+
9+
### Testing with Cabal
10+
11+
Running all the tests
12+
13+
```bash
14+
$ cabal test
15+
```
16+
17+
Running just the functional tests
18+
19+
```bash
20+
$ cabal test func-test
21+
```
22+
23+
Running just the wrapper tests
24+
25+
```bash
26+
$ cabal test wrapper-test
27+
```
28+
29+
Running a subset of tests
30+
31+
Tasty supports providing
32+
[Patterns](https://github.com/feuerbach/tasty#patterns) as command
33+
line arguments, to select the specific tests to run.
34+
35+
```bash
36+
$ cabal test func-test --test-option "-p hlint"
37+
```
38+
39+
The above recompiles everything every time you use a different test option though.
40+
41+
An alternative is
42+
43+
```bash
44+
$ cabal run haskell-language-server:func-test -- -p "hlint enables"
45+
```

haskell-language-server.cabal

+1
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,7 @@ test-suite func-test
381381
other-modules:
382382
Command
383383
Completion
384+
Config
384385
Deferred
385386
Definition
386387
Diagnostic

hls-plugin-api/src/Ide/Plugin.hs

+68-11
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ module Ide.Plugin
1919
, responseError
2020
, getClientConfig
2121
, getClientConfigAction
22+
, getPluginConfig
23+
, configForPlugin
24+
, pluginEnabled
2225
) where
2326

2427
import Control.Exception(SomeException, catch)
@@ -121,7 +124,12 @@ makeCodeAction :: [(PluginId, CodeActionProvider)]
121124
makeCodeAction cas lf ideState (CodeActionParams docId range context _) = do
122125
let caps = LSP.clientCapabilities lf
123126
unL (List ls) = ls
124-
r <- mapM (\(pid,provider) -> provider lf ideState pid docId range context) cas
127+
makeAction (pid,provider) = do
128+
pluginConfig <- getPluginConfig lf pid
129+
if pluginEnabled pluginConfig plcCodeActionsOn
130+
then provider lf ideState pid docId range context
131+
else return $ Right (List [])
132+
r <- mapM makeAction cas
125133
let actions = filter wasRequested . concat $ map unL $ rights r
126134
res <- send caps actions
127135
return $ Right res
@@ -181,7 +189,10 @@ makeCodeLens cas lf ideState params = do
181189
logInfo (ideLogger ideState) "Plugin.makeCodeLens (ideLogger)" -- AZ
182190
let
183191
makeLens (pid, provider) = do
184-
r <- provider lf ideState pid params
192+
pluginConfig <- getPluginConfig lf pid
193+
r <- if pluginEnabled pluginConfig plcCodeLensOn
194+
then provider lf ideState pid params
195+
else return $ Right (List [])
185196
return (pid, r)
186197
breakdown :: [(PluginId, Either ResponseError a)] -> ([(PluginId, ResponseError)], [(PluginId, a)])
187198
breakdown ls = (concatMap doOneLeft ls, concatMap doOneRight ls)
@@ -409,9 +420,15 @@ makeHover :: [(PluginId, HoverProvider)]
409420
-> LSP.LspFuncs Config -> IdeState
410421
-> TextDocumentPositionParams
411422
-> IO (Either ResponseError (Maybe Hover))
412-
makeHover hps _lf ideState params
423+
makeHover hps lf ideState params
413424
= do
414-
mhs <- mapM (\(_,p) -> p ideState params) hps
425+
let
426+
makeHover(pid,p) = do
427+
pluginConfig <- getPluginConfig lf pid
428+
if pluginEnabled pluginConfig plcHoverOn
429+
then p ideState params
430+
else return $ Right Nothing
431+
mhs <- mapM makeHover hps
415432
-- TODO: We should support ServerCapabilities and declare that
416433
-- we don't support hover requests during initialization if we
417434
-- don't have any hover providers
@@ -462,7 +479,12 @@ makeSymbols sps lf ideState params
462479
si = SymbolInformation name' (ds ^. kind) (ds ^. deprecated) loc parent
463480
in [si] <> children'
464481

465-
mhs <- mapM (\(_,p) -> p lf ideState params) sps
482+
makeSymbols (pid,p) = do
483+
pluginConfig <- getPluginConfig lf pid
484+
if pluginEnabled pluginConfig plcSymbolsOn
485+
then p lf ideState params
486+
else return $ Right []
487+
mhs <- mapM makeSymbols sps
466488
case rights mhs of
467489
[] -> return $ Left $ responseError $ T.pack $ show $ lefts mhs
468490
hs -> return $ Right $ convertSymbols $ concat hs
@@ -485,7 +507,14 @@ renameWith ::
485507
RenameParams ->
486508
IO (Either ResponseError WorkspaceEdit)
487509
renameWith providers lspFuncs state params = do
488-
results <- mapM (\(_,p) -> p lspFuncs state params) providers
510+
let
511+
makeAction (pid,p) = do
512+
pluginConfig <- getPluginConfig lspFuncs pid
513+
if pluginEnabled pluginConfig plcRenameOn
514+
then p lspFuncs state params
515+
else return $ Right $ WorkspaceEdit Nothing Nothing
516+
-- TODO:AZ: we need to consider the right way to combine possible renamers
517+
results <- mapM makeAction providers
489518
case partitionEithers results of
490519
(errors, []) -> return $ Left $ responseError $ T.pack $ show $ errors
491520
(_, edits) -> return $ Right $ mconcat edits
@@ -530,7 +559,7 @@ makeCompletions :: [(PluginId, CompletionProvider)]
530559
makeCompletions sps lf ideState params@(CompletionParams (TextDocumentIdentifier doc) pos _context _mt)
531560
= do
532561
mprefix <- getPrefixAtPos lf doc pos
533-
_snippets <- WithSnippets <$> completionSnippetsOn <$> (getClientConfig lf)
562+
_snippets <- WithSnippets <$> completionSnippetsOn <$> getClientConfig lf
534563

535564
let
536565
combine :: [CompletionResponseResult] -> CompletionResponseResult
@@ -545,11 +574,16 @@ makeCompletions sps lf ideState params@(CompletionParams (TextDocumentIdentifier
545574
= go (CompletionList $ CompletionListType (complete || complete2) (List (ls <> ls2))) rest
546575
go (CompletionList (CompletionListType complete (List ls))) (Completions (List ls2):rest)
547576
= go (CompletionList $ CompletionListType complete (List (ls <> ls2))) rest
577+
makeAction (pid,p) = do
578+
pluginConfig <- getPluginConfig lf pid
579+
if pluginEnabled pluginConfig plcCompletionOn
580+
then p lf ideState params
581+
else return $ Right $ Completions $ List []
548582

549583
case mprefix of
550584
Nothing -> return $ Right $ Completions $ List []
551585
Just _prefix -> do
552-
mhs <- mapM (\(_,p) -> p lf ideState params) sps
586+
mhs <- mapM makeAction sps
553587
case rights mhs of
554588
[] -> return $ Left $ responseError $ T.pack $ show $ lefts mhs
555589
hs -> return $ Right $ combine hs
@@ -583,15 +617,15 @@ getPrefixAtPos lf uri pos = do
583617

584618
-- ---------------------------------------------------------------------
585619
-- | Returns the current client configuration. It is not wise to permanently
586-
-- cache the returned value of this function, as clients can at runitime change
587-
-- their configuration.
620+
-- cache the returned value of this function, as clients can change their
621+
-- configuration at runtime.
588622
--
589623
-- If no custom configuration has been set by the client, this function returns
590624
-- our own defaults.
591625
getClientConfig :: LSP.LspFuncs Config -> IO Config
592626
getClientConfig lf = fromMaybe Data.Default.def <$> LSP.config lf
593627

594-
-- | Returns the client configurarion stored in the IdeState.
628+
-- | Returns the client configuration stored in the IdeState.
595629
-- You can use this function to access it from shake Rules
596630
getClientConfigAction :: Action Config
597631
getClientConfigAction = do
@@ -600,4 +634,27 @@ getClientConfigAction = do
600634
case J.fromJSON <$> mbVal of
601635
Just (J.Success c) -> return c
602636
_ -> return Data.Default.def
637+
603638
-- ---------------------------------------------------------------------
639+
640+
-- | Returns the current plugin configuration. It is not wise to permanently
641+
-- cache the returned value of this function, as clients can change their
642+
-- configuration at runtime.
643+
--
644+
-- If no custom configuration has been set by the client, this function returns
645+
-- our own defaults.
646+
getPluginConfig :: LSP.LspFuncs Config -> PluginId -> IO PluginConfig
647+
getPluginConfig lf plugin = do
648+
config <- getClientConfig lf
649+
return $ configForPlugin config plugin
650+
651+
configForPlugin :: Config -> PluginId -> PluginConfig
652+
configForPlugin config (PluginId plugin)
653+
= Map.findWithDefault Data.Default.def plugin (plugins config)
654+
655+
-- ---------------------------------------------------------------------
656+
657+
-- | Checks that a given plugin is both enabled and the specific feature is
658+
-- enabled
659+
pluginEnabled :: PluginConfig -> (PluginConfig -> Bool) -> Bool
660+
pluginEnabled pluginConfig f = plcGlobalOn pluginConfig && f pluginConfig

hls-plugin-api/src/Ide/Plugin/Config.hs

+75-11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
{-# LANGUAGE RecordWildCards #-}
2-
{-# LANGUAGE DeriveGeneric #-}
31
{-# LANGUAGE FlexibleInstances #-}
42
{-# LANGUAGE OverloadedStrings #-}
53
{-# LANGUAGE TypeFamilies #-}
@@ -8,6 +6,7 @@ module Ide.Plugin.Config
86
getInitialConfig
97
, getConfigFromNotification
108
, Config(..)
9+
, PluginConfig(..)
1110
) where
1211

1312
import Control.Applicative
@@ -16,6 +15,7 @@ import Data.Aeson hiding ( Error )
1615
import Data.Default
1716
import qualified Data.Text as T
1817
import Language.Haskell.LSP.Types
18+
import qualified Data.Map as Map
1919

2020
-- ---------------------------------------------------------------------
2121

@@ -43,14 +43,15 @@ getInitialConfig (RequestMessage _ _ _ InitializeParams{_initializationOptions =
4343
-- will be surprises relating to config options being ignored, initially though.
4444
data Config =
4545
Config
46-
{ hlintOn :: Bool
47-
, diagnosticsOnChange :: Bool
48-
, maxNumberOfProblems :: Int
49-
, diagnosticsDebounceDuration :: Int
50-
, liquidOn :: Bool
51-
, completionSnippetsOn :: Bool
52-
, formatOnImportOn :: Bool
53-
, formattingProvider :: T.Text
46+
{ hlintOn :: !Bool
47+
, diagnosticsOnChange :: !Bool
48+
, maxNumberOfProblems :: !Int
49+
, diagnosticsDebounceDuration :: !Int
50+
, liquidOn :: !Bool
51+
, completionSnippetsOn :: !Bool
52+
, formatOnImportOn :: !Bool
53+
, formattingProvider :: !T.Text
54+
, plugins :: !(Map.Map T.Text PluginConfig)
5455
} deriving (Show,Eq)
5556

5657
instance Default Config where
@@ -66,6 +67,7 @@ instance Default Config where
6667
, formattingProvider = "ormolu"
6768
-- , formattingProvider = "floskell"
6869
-- , formattingProvider = "stylish-haskell"
70+
, plugins = Map.empty
6971
}
7072

7173
-- TODO: Add API for plugins to expose their own LSP config options
@@ -83,6 +85,7 @@ instance A.FromJSON Config where
8385
<*> o .:? "completionSnippetsOn" .!= completionSnippetsOn def
8486
<*> o .:? "formatOnImportOn" .!= formatOnImportOn def
8587
<*> o .:? "formattingProvider" .!= formattingProvider def
88+
<*> o .:? "plugin" .!= plugins def
8689

8790
-- 2017-10-09 23:22:00.710515298 [ThreadId 11] - ---> {"jsonrpc":"2.0","method":"workspace/didChangeConfiguration","params":{"settings":{"haskell":{"maxNumberOfProblems":100,"hlintOn":true}}}}
8891
-- 2017-10-09 23:22:00.710667381 [ThreadId 15] - reactor:got didChangeConfiguration notification:
@@ -94,7 +97,7 @@ instance A.FromJSON Config where
9497
-- ,("maxNumberOfProblems",Number 100.0)]))])}}
9598

9699
instance A.ToJSON Config where
97-
toJSON (Config h diag m d l c f fp) = object [ "haskell" .= r ]
100+
toJSON (Config h diag m d l c f fp p) = object [ "haskell" .= r ]
98101
where
99102
r = object [ "hlintOn" .= h
100103
, "diagnosticsOnChange" .= diag
@@ -104,4 +107,65 @@ instance A.ToJSON Config where
104107
, "completionSnippetsOn" .= c
105108
, "formatOnImportOn" .= f
106109
, "formattingProvider" .= fp
110+
, "plugin" .= p
107111
]
112+
113+
-- ---------------------------------------------------------------------
114+
115+
-- | A PluginConfig is a generic configuration for a given HLS plugin. It
116+
-- provides a "big switch" to turn it on or off as a whole, as well as small
117+
-- switches per feature, and a slot for custom config.
118+
-- This provides a regular naming scheme for all plugin config.
119+
data PluginConfig =
120+
PluginConfig
121+
{ plcGlobalOn :: !Bool
122+
, plcCodeActionsOn :: !Bool
123+
, plcCodeLensOn :: !Bool
124+
, plcDiagnosticsOn :: !Bool
125+
, plcHoverOn :: !Bool
126+
, plcSymbolsOn :: !Bool
127+
, plcCompletionOn :: !Bool
128+
, plcRenameOn :: !Bool
129+
, plcConfig :: !A.Object
130+
} deriving (Show,Eq)
131+
132+
instance Default PluginConfig where
133+
def = PluginConfig
134+
{ plcGlobalOn = True
135+
, plcCodeActionsOn = True
136+
, plcCodeLensOn = True
137+
, plcDiagnosticsOn = True
138+
, plcHoverOn = True
139+
, plcSymbolsOn = True
140+
, plcCompletionOn = True
141+
, plcRenameOn = True
142+
, plcConfig = mempty
143+
}
144+
145+
instance A.ToJSON PluginConfig where
146+
toJSON (PluginConfig g ca cl d h s c rn cfg) = r
147+
where
148+
r = object [ "globalOn" .= g
149+
, "codeActionsOn" .= ca
150+
, "codeLensOn" .= cl
151+
, "diagnosticsOn" .= d
152+
, "hoverOn" .= h
153+
, "symbolsOn" .= s
154+
, "completionOn" .= c
155+
, "renameOn" .= rn
156+
, "config" .= cfg
157+
]
158+
159+
instance A.FromJSON PluginConfig where
160+
parseJSON = A.withObject "PluginConfig" $ \o -> PluginConfig
161+
<$> o .:? "globalOn" .!= plcGlobalOn def
162+
<*> o .:? "codeActionsOn" .!= plcCodeActionsOn def
163+
<*> o .:? "codeLensOn" .!= plcCodeLensOn def
164+
<*> o .:? "diagnosticsOn" .!= plcDiagnosticsOn def -- AZ
165+
<*> o .:? "hoverOn" .!= plcHoverOn def
166+
<*> o .:? "symbolsOn" .!= plcSymbolsOn def
167+
<*> o .:? "completionOn" .!= plcCompletionOn def
168+
<*> o .:? "renameOn" .!= plcRenameOn def
169+
<*> o .:? "config" .!= plcConfig def
170+
171+
-- ---------------------------------------------------------------------

plugins/hls-hlint-plugin/src/Ide/Plugin/Hlint.hs

+6-4
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ import GHC.Generics (Generic)
6767

6868
descriptor :: PluginId -> PluginDescriptor
6969
descriptor plId = (defaultPluginDescriptor plId)
70-
{ pluginRules = rules
70+
{ pluginRules = rules plId
7171
, pluginCommands =
7272
[ PluginCommand "applyOne" "Apply a single hint" applyOneCmd
7373
, PluginCommand "applyAll" "Apply all hints to the file" applyAllCmd
@@ -93,10 +93,12 @@ type instance RuleResult GetHlintDiagnostics = ()
9393
-- | - `getIdeas` -> `getFileContents` if the hls ghc does not match the hlint default ghc
9494
-- | - The client settings have changed, to honour the `hlintOn` setting, via `getClientConfigAction`
9595
-- | - The hlint specific settings have changed, via `getHlintSettingsRule`
96-
rules :: Rules ()
97-
rules = do
96+
rules :: PluginId -> Rules ()
97+
rules plugin = do
9898
define $ \GetHlintDiagnostics file -> do
99-
hlintOn' <- hlintOn <$> getClientConfigAction
99+
config <- getClientConfigAction
100+
let pluginConfig = configForPlugin config plugin
101+
let hlintOn' = hlintOn config && pluginEnabled pluginConfig plcDiagnosticsOn
100102
ideas <- if hlintOn' then getIdeas file else return (Right [])
101103
return (diagnostics file ideas, Just ())
102104

0 commit comments

Comments
 (0)