Skip to content

Commit 022df1e

Browse files
Jana Chadtfendor
authored andcommitted
Generalise file extension handling for plugins
NotificationHandler now distinguishes between different file extensions RequestHandler distinguishes between different file extensions
1 parent 2121495 commit 022df1e

File tree

5 files changed

+180
-51
lines changed

5 files changed

+180
-51
lines changed

ghcide/src/Development/IDE/Plugin/HLS.hs

Lines changed: 56 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ asGhcIdePlugin :: Recorder (WithPriority Log) -> IdePlugins IdeState -> Plugin C
5858
asGhcIdePlugin recorder (IdePlugins ls) =
5959
mkPlugin rulesPlugins HLS.pluginRules <>
6060
mkPlugin executeCommandPlugins HLS.pluginCommands <>
61-
mkPlugin extensiblePlugins HLS.pluginHandlers <>
62-
mkPlugin (extensibleNotificationPlugins recorder) HLS.pluginNotificationHandlers <>
61+
mkPlugin extensiblePlugins id <>
62+
mkPlugin (extensibleNotificationPlugins recorder) id <>
6363
mkPlugin dynFlagsPlugins HLS.pluginModifyDynflags
6464
where
6565

@@ -153,55 +153,80 @@ executeCommandHandlers ecs = requestHandler SWorkspaceExecuteCommand execCmd
153153

154154
-- ---------------------------------------------------------------------
155155

156-
extensiblePlugins :: [(PluginId, PluginHandlers IdeState)] -> Plugin Config
156+
extensiblePlugins :: [(PluginId, PluginDescriptor IdeState)] -> Plugin Config
157157
extensiblePlugins xs = mempty { P.pluginHandlers = handlers }
158158
where
159+
getPluginDescriptor pid = lookup pid xs
159160
IdeHandlers handlers' = foldMap bakePluginId xs
160-
bakePluginId :: (PluginId, PluginHandlers IdeState) -> IdeHandlers
161-
bakePluginId (pid,PluginHandlers hs) = IdeHandlers $ DMap.map
161+
bakePluginId :: (PluginId, PluginDescriptor IdeState) -> IdeHandlers
162+
bakePluginId (pid,pluginDesc) = IdeHandlers $ DMap.map
162163
(\(PluginHandler f) -> IdeHandler [(pid,f pid)])
163164
hs
165+
where
166+
PluginHandlers hs = HLS.pluginHandlers pluginDesc
164167
handlers = mconcat $ do
165168
(IdeMethod m :=> IdeHandler fs') <- DMap.assocs handlers'
166169
pure $ requestHandler m $ \ide params -> do
167170
config <- Ide.PluginUtils.getClientConfig
168-
let fs = filter (\(pid,_) -> pluginEnabled m pid config) fs'
169-
case nonEmpty fs of
170-
Nothing -> pure $ Left $ ResponseError InvalidRequest
171-
("No plugin enabled for " <> T.pack (show m) <> ", available: " <> T.pack (show $ map fst fs))
172-
Nothing
173-
Just fs -> do
174-
let msg e pid = "Exception in plugin " <> T.pack (show pid) <> "while processing " <> T.pack (show m) <> ": " <> T.pack (show e)
175-
es <- runConcurrently msg (show m) fs ide params
176-
let (errs,succs) = partitionEithers $ toList es
177-
case nonEmpty succs of
178-
Nothing -> pure $ Left $ combineErrors errs
179-
Just xs -> do
180-
caps <- LSP.getClientCapabilities
181-
pure $ Right $ combineResponses m config caps params xs
171+
let pluginInfo = map (\(pid,_) -> (pid, getPluginDescriptor pid)) fs'
172+
cleanPluginInfo <- collectPluginDescriptors pluginInfo []
173+
case cleanPluginInfo of
174+
Left err -> pure $ Left err
175+
Right pluginInfos -> do
176+
let fs = map snd $ filter (\((_, desc), _) -> pluginEnabled m params desc config) (zip pluginInfos fs')
177+
case nonEmpty fs of
178+
Nothing -> pure $ Left $ ResponseError InvalidRequest
179+
("No plugin enabled for " <> T.pack (show m) <> ", available: " <> T.pack (show $ map fst fs))
180+
Nothing
181+
Just fs -> do
182+
let msg e pid = "Exception in plugin " <> T.pack (show pid) <> "while processing " <> T.pack (show m) <> ": " <> T.pack (show e)
183+
es <- runConcurrently msg (show m) fs ide params
184+
let (errs,succs) = partitionEithers $ toList es
185+
case nonEmpty succs of
186+
Nothing -> pure $ Left $ combineErrors errs
187+
Just xs -> do
188+
caps <- LSP.getClientCapabilities
189+
pure $ Right $ combineResponses m config caps params xs
190+
191+
collectPluginDescriptors :: [(PluginId, Maybe (PluginDescriptor c))] -> [(PluginId, PluginDescriptor c)] -> LSP.LspM Config (Either ResponseError [(PluginId, PluginDescriptor c)])
192+
collectPluginDescriptors ((pid, Nothing):_) _ = pure $ Left $ ResponseError InvalidRequest
193+
("No plugindescriptor found for " <> pidT <> ", available: ")
194+
Nothing
195+
where
196+
PluginId pidT = pid
197+
collectPluginDescriptors ((pid, Just desc):xs) ys = collectPluginDescriptors xs (ys ++ [(pid, desc)])
198+
collectPluginDescriptors [] ys = pure $ Right ys
199+
182200
-- ---------------------------------------------------------------------
183201

184-
extensibleNotificationPlugins :: Recorder (WithPriority Log) -> [(PluginId, PluginNotificationHandlers IdeState)] -> Plugin Config
202+
extensibleNotificationPlugins :: Recorder (WithPriority Log) -> [(PluginId, PluginDescriptor IdeState)] -> Plugin Config
185203
extensibleNotificationPlugins recorder xs = mempty { P.pluginHandlers = handlers }
186204
where
205+
getPluginDescriptor pid = lookup pid xs
187206
IdeNotificationHandlers handlers' = foldMap bakePluginId xs
188-
bakePluginId :: (PluginId, PluginNotificationHandlers IdeState) -> IdeNotificationHandlers
189-
bakePluginId (pid,PluginNotificationHandlers hs) = IdeNotificationHandlers $ DMap.map
207+
bakePluginId :: (PluginId, PluginDescriptor IdeState) -> IdeNotificationHandlers
208+
bakePluginId (pid,pluginDesc) = IdeNotificationHandlers $ DMap.map
190209
(\(PluginNotificationHandler f) -> IdeNotificationHandler [(pid,f pid)])
191210
hs
211+
where PluginNotificationHandlers hs = HLS.pluginNotificationHandlers pluginDesc
192212
handlers = mconcat $ do
193213
(IdeNotification m :=> IdeNotificationHandler fs') <- DMap.assocs handlers'
194214
pure $ notificationHandler m $ \ide vfs params -> do
195215
config <- Ide.PluginUtils.getClientConfig
196-
let fs = filter (\(pid,_) -> plcGlobalOn $ configForPlugin config pid) fs'
197-
case nonEmpty fs of
198-
Nothing -> do
199-
logWith recorder Info LogNoEnabledPlugins
200-
pure ()
201-
Just fs -> do
202-
-- We run the notifications in order, so the core ghcide provider
203-
-- (which restarts the shake process) hopefully comes last
204-
mapM_ (\(pid,f) -> otTracedProvider pid (fromString $ show m) $ f ide vfs params) fs
216+
let pluginInfo = map (\(pid,_) -> (pid, getPluginDescriptor pid)) fs'
217+
cleanPluginInfo <- collectPluginDescriptors pluginInfo []
218+
case cleanPluginInfo of
219+
Left _ -> pure ()
220+
Right pluginInfos -> do
221+
let fs = map snd $ filter (\((_, desc), _) -> pluginEnabled2 m params desc config) (zip pluginInfos fs')
222+
case nonEmpty fs of
223+
Nothing -> do
224+
logWith recorder Info LogNoEnabledPlugins
225+
pure ()
226+
Just fs -> do
227+
-- We run the notifications in order, so the core ghcide provider
228+
-- (which restarts the shake process) hopefully comes last
229+
mapM_ (\(pid,f) -> otTracedProvider pid (fromString $ show m) $ f ide vfs params) fs
205230

206231
-- ---------------------------------------------------------------------
207232

haskell-language-server.cabal

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,8 @@ flag dynamic
226226
common example-plugins
227227
hs-source-dirs: plugins/default/src
228228
other-modules: Ide.Plugin.Example,
229-
Ide.Plugin.Example2
229+
Ide.Plugin.Example2,
230+
Ide.Plugin.ExampleCabal
230231

231232
common class
232233
if flag(class)

hls-plugin-api/hls-plugin-api.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ library
4343
, Diff ^>=0.4.0
4444
, dlist
4545
, extra
46+
, filepath
4647
, ghc
4748
, hashable
4849
, hls-graph ^>= 1.7

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

Lines changed: 119 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ import Language.LSP.Types.Lens as J (HasChildren (children),
6969
import Language.LSP.VFS
7070
import OpenTelemetry.Eventlog
7171
import Options.Applicative (ParserInfo)
72+
import System.FilePath
7273
import System.IO.Unsafe
7374
import Text.Regex.TDFA.Text ()
7475

@@ -108,7 +109,7 @@ instance Show (IdeCommand st) where show _ = "<ide command>"
108109

109110
-- ---------------------------------------------------------------------
110111

111-
data PluginDescriptor ideState =
112+
data PluginDescriptor (ideState :: *) =
112113
PluginDescriptor { pluginId :: !PluginId
113114
, pluginRules :: !(Rules ())
114115
, pluginCommands :: ![PluginCommand ideState]
@@ -117,6 +118,11 @@ data PluginDescriptor ideState =
117118
, pluginNotificationHandlers :: PluginNotificationHandlers ideState
118119
, pluginModifyDynflags :: DynFlagsModifications
119120
, pluginCli :: Maybe (ParserInfo (IdeCommand ideState))
121+
, pluginFileType :: [T.Text]
122+
-- ^ File extension of the files the plugin is responsible for.
123+
-- The plugin is only allowed to handle files with these extensions
124+
-- When writing handlers, etc. for this plugin it can be assumed that all handled files are of this type.
125+
-- The file extension must have a leading '.'.
120126
}
121127

122128
-- | An existential wrapper of 'Properties'
@@ -162,7 +168,7 @@ defaultConfigDescriptor = ConfigDescriptor True False (mkCustomConfig emptyPrope
162168
class HasTracing (MessageParams m) => PluginMethod m where
163169

164170
-- | Parse the configuration to check if this plugin is enabled
165-
pluginEnabled :: SMethod m -> PluginId -> Config -> Bool
171+
pluginEnabled :: SMethod m -> MessageParams m -> PluginDescriptor c -> Config -> Bool
166172

167173
-- | How to combine responses from different plugins
168174
combineResponses
@@ -177,11 +183,13 @@ class HasTracing (MessageParams m) => PluginMethod m where
177183
combineResponses _method _config _caps _params = sconcat
178184

179185
instance PluginMethod TextDocumentCodeAction where
180-
pluginEnabled _ = pluginEnabledConfig plcCodeActionsOn
186+
pluginEnabled _ msgParams pluginDesc config =
187+
pluginResponsible uri pluginDesc && pluginEnabledConfig plcCodeActionsOn (pluginId pluginDesc) config
188+
where
189+
uri = msgParams ^. J.textDocument . J.uri
181190
combineResponses _method _config (ClientCapabilities _ textDocCaps _ _ _) (CodeActionParams _ _ _ _ context) resps =
182191
fmap compat $ List $ filter wasRequested $ (\(List x) -> x) $ sconcat resps
183192
where
184-
185193
compat :: (Command |? CodeAction) -> (Command |? CodeAction)
186194
compat x@(InL _) = x
187195
compat x@(InR action)
@@ -205,12 +213,34 @@ instance PluginMethod TextDocumentCodeAction where
205213
, Just caKind <- ca ^. kind = any (\k -> k `codeActionKindSubsumes` caKind) allowed
206214
| otherwise = False
207215

216+
-- | Check whether the given plugin descriptor is responsible for the file with the given path.
217+
-- Compares the file extension of the file at the given path with the file extension
218+
-- the plugin is responsible for.
219+
pluginResponsible :: Uri -> PluginDescriptor c -> Bool
220+
pluginResponsible uri pluginDesc
221+
| Just fp <- mfp
222+
, T.pack (takeExtension fp) `elem` pluginFileType pluginDesc = True
223+
| otherwise = False
224+
where
225+
mfp = uriToFilePath uri
226+
208227
instance PluginMethod TextDocumentCodeLens where
209-
pluginEnabled _ = pluginEnabledConfig plcCodeLensOn
228+
pluginEnabled _ msgParams pluginDesc config =
229+
pluginResponsible uri pluginDesc
230+
&& pluginEnabledConfig plcCodeLensOn (pluginId pluginDesc) config
231+
where
232+
uri = msgParams ^. J.textDocument . J.uri
233+
210234
instance PluginMethod TextDocumentRename where
211-
pluginEnabled _ = pluginEnabledConfig plcRenameOn
235+
pluginEnabled _ msgParams pluginDesc config = pluginResponsible uri pluginDesc
236+
&& pluginEnabledConfig plcRenameOn (pluginId pluginDesc) config
237+
where
238+
uri = msgParams ^. J.textDocument . J.uri
212239
instance PluginMethod TextDocumentHover where
213-
pluginEnabled _ = pluginEnabledConfig plcHoverOn
240+
pluginEnabled _ msgParams pluginDesc config = pluginResponsible uri pluginDesc
241+
&& pluginEnabledConfig plcHoverOn (pluginId pluginDesc) config
242+
where
243+
uri = msgParams ^. J.textDocument . J.uri
214244
combineResponses _ _ _ _ (catMaybes . toList -> hs) = h
215245
where
216246
r = listToMaybe $ mapMaybe (^. range) hs
@@ -219,7 +249,10 @@ instance PluginMethod TextDocumentHover where
219249
hh -> Just $ Hover hh r
220250

221251
instance PluginMethod TextDocumentDocumentSymbol where
222-
pluginEnabled _ = pluginEnabledConfig plcSymbolsOn
252+
pluginEnabled _ msgParams pluginDesc config = pluginResponsible uri pluginDesc
253+
&& pluginEnabledConfig plcSymbolsOn (pluginId pluginDesc) config
254+
where
255+
uri = msgParams ^. J.textDocument . J.uri
223256
combineResponses _ _ (ClientCapabilities _ tdc _ _ _) params xs = res
224257
where
225258
uri' = params ^. textDocument . uri
@@ -241,7 +274,10 @@ instance PluginMethod TextDocumentDocumentSymbol where
241274
in [si] <> children'
242275

243276
instance PluginMethod TextDocumentCompletion where
244-
pluginEnabled _ = pluginEnabledConfig plcCompletionOn
277+
pluginEnabled _ msgParams pluginDesc config = pluginResponsible uri pluginDesc
278+
&& pluginEnabledConfig plcCompletionOn (pluginId pluginDesc) config
279+
where
280+
uri = msgParams ^. J.textDocument . J.uri
245281
combineResponses _ conf _ _ (toList -> xs) = snd $ consumeCompletionResponse limit $ combine xs
246282
where
247283
limit = maxCompletions conf
@@ -270,32 +306,82 @@ instance PluginMethod TextDocumentCompletion where
270306
consumeCompletionResponse n (InR (CompletionList isCompleteResponse (List xx)))
271307

272308
instance PluginMethod TextDocumentFormatting where
273-
pluginEnabled _ pid conf = (PluginId $ formattingProvider conf) == pid
274-
combineResponses _ _ _ _ (x :| _) = x
309+
pluginEnabled STextDocumentFormatting msgParams pluginDesc conf =
310+
pluginResponsible uri pluginDesc && PluginId (formattingProvider conf) == pid
311+
where
312+
uri = msgParams ^. J.textDocument . J.uri
313+
pid = pluginId pluginDesc
314+
combineResponses _ _ _ _ x = sconcat x
315+
275316

276317
instance PluginMethod TextDocumentRangeFormatting where
277-
pluginEnabled _ pid conf = (PluginId $ formattingProvider conf) == pid
318+
pluginEnabled _ msgParams pluginDesc conf = pluginResponsible uri pluginDesc
319+
&& PluginId (formattingProvider conf) == pid
320+
where
321+
uri = msgParams ^. J.textDocument . J.uri
322+
pid = pluginId pluginDesc
278323
combineResponses _ _ _ _ (x :| _) = x
279324

280325
instance PluginMethod TextDocumentPrepareCallHierarchy where
281-
pluginEnabled _ = pluginEnabledConfig plcCallHierarchyOn
326+
pluginEnabled _ msgParams pluginDesc conf = pluginResponsible uri pluginDesc
327+
&& pluginEnabledConfig plcCallHierarchyOn pid conf
328+
where
329+
uri = msgParams ^. J.textDocument . J.uri
330+
pid = pluginId pluginDesc
282331

283332
instance PluginMethod TextDocumentSelectionRange where
284-
pluginEnabled _ = pluginEnabledConfig plcSelectionRangeOn
333+
pluginEnabled _ _ pluginDesc conf = pluginEnabledConfig plcSelectionRangeOn pid conf
334+
where
335+
pid = pluginId pluginDesc
285336
combineResponses _ _ _ _ (x :| _) = x
286337

287338
instance PluginMethod CallHierarchyIncomingCalls where
288-
pluginEnabled _ = pluginEnabledConfig plcCallHierarchyOn
339+
pluginEnabled _ _ pluginDesc conf = pluginEnabledConfig plcCallHierarchyOn pid conf
340+
where
341+
pid = pluginId pluginDesc
289342

290343
instance PluginMethod CallHierarchyOutgoingCalls where
291-
pluginEnabled _ = pluginEnabledConfig plcCallHierarchyOn
344+
pluginEnabled _ _ pluginDesc conf = pluginEnabledConfig plcCallHierarchyOn pid conf
345+
where
346+
pid = pluginId pluginDesc
292347

293348
instance PluginMethod CustomMethod where
294-
pluginEnabled _ _ _ = True
349+
pluginEnabled _ _ _ _ = True
295350
combineResponses _ _ _ _ (x :| _) = x
296351

297352
-- ---------------------------------------------------------------------
298353

354+
class HasTracing (MessageParams m) => PluginNotificationMethod (m :: Method FromClient Notification) where
355+
pluginEnabled2 :: SMethod m -> MessageParams m -> PluginDescriptor c -> Config -> Bool
356+
357+
default pluginEnabled2 :: (HasTextDocument (MessageParams m) doc, HasUri doc Uri)
358+
=> SMethod m -> MessageParams m -> PluginDescriptor c -> Config -> Bool
359+
pluginEnabled2 _ params desc conf = pluginResponsible uri desc && plcGlobalOn (configForPlugin conf (pluginId desc))
360+
where
361+
uri = params ^. J.textDocument . J.uri
362+
363+
instance PluginNotificationMethod TextDocumentDidOpen where
364+
365+
instance PluginNotificationMethod TextDocumentDidChange where
366+
367+
instance PluginNotificationMethod TextDocumentDidSave where
368+
369+
instance PluginNotificationMethod TextDocumentDidClose where
370+
371+
instance PluginNotificationMethod WorkspaceDidChangeWatchedFiles where
372+
pluginEnabled2 _ _ desc conf = plcGlobalOn $ configForPlugin conf (pluginId desc)
373+
374+
instance PluginNotificationMethod WorkspaceDidChangeWorkspaceFolders where
375+
pluginEnabled2 _ _ desc conf = plcGlobalOn $ configForPlugin conf (pluginId desc)
376+
377+
instance PluginNotificationMethod WorkspaceDidChangeConfiguration where
378+
pluginEnabled2 _ _ desc conf = plcGlobalOn $ configForPlugin conf (pluginId desc)
379+
380+
instance PluginNotificationMethod Initialized where
381+
pluginEnabled2 _ _ desc conf = plcGlobalOn $ configForPlugin conf (pluginId desc)
382+
383+
-- ---------------------------------------------------------------------
384+
299385
-- | Methods which have a PluginMethod instance
300386
data IdeMethod (m :: Method FromClient Request) = PluginMethod m => IdeMethod (SMethod m)
301387
instance GEq IdeMethod where
@@ -304,7 +390,7 @@ instance GCompare IdeMethod where
304390
gcompare (IdeMethod a) (IdeMethod b) = gcompare a b
305391

306392
-- | Methods which have a PluginMethod instance
307-
data IdeNotification (m :: Method FromClient Notification) = HasTracing (MessageParams m) => IdeNotification (SMethod m)
393+
data IdeNotification (m :: Method FromClient Notification) = PluginNotificationMethod m => IdeNotification (SMethod m)
308394
instance GEq IdeNotification where
309395
geq (IdeNotification a) (IdeNotification b) = geq a b
310396
instance GCompare IdeNotification where
@@ -353,7 +439,7 @@ mkPluginHandler m f = PluginHandlers $ DMap.singleton (IdeMethod m) (PluginHandl
353439

354440
-- | Make a handler for plugins with no extra data
355441
mkPluginNotificationHandler
356-
:: HasTracing (MessageParams m)
442+
:: PluginNotificationMethod m
357443
=> SClientMethod (m :: Method FromClient Notification)
358444
-> PluginNotificationMethodHandler ideState m
359445
-> PluginNotificationHandlers ideState
@@ -373,6 +459,20 @@ defaultPluginDescriptor plId =
373459
mempty
374460
mempty
375461
Nothing
462+
[".hs", ".lhs", ".hs-boot"]
463+
464+
defaultCabalPluginDescriptor :: PluginId -> PluginDescriptor ideState
465+
defaultCabalPluginDescriptor plId =
466+
PluginDescriptor
467+
plId
468+
mempty
469+
mempty
470+
mempty
471+
defaultConfigDescriptor
472+
mempty
473+
mempty
474+
Nothing
475+
[".cabal"]
376476

377477
newtype CommandId = CommandId T.Text
378478
deriving (Show, Read, Eq, Ord)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
module Ide.Plugin.ExampleCabal where
2+

0 commit comments

Comments
 (0)