12
12
{-# LANGUAGE MonadComprehensions #-}
13
13
{-# LANGUAGE MultiParamTypeClasses #-}
14
14
{-# LANGUAGE NamedFieldPuns #-}
15
+ {-# LANGUAGE OverloadedLabels #-}
15
16
{-# LANGUAGE OverloadedStrings #-}
16
17
{-# LANGUAGE PatternSynonyms #-}
17
18
{-# LANGUAGE PolyKinds #-}
@@ -47,6 +48,9 @@ module Ide.Types
47
48
, installSigUsr1Handler
48
49
, responseError
49
50
, lookupCommandProvider
51
+ , OwnedResolveData (.. )
52
+ , mkCodeActionHandlerWithResolve
53
+ , mkCodeActionWithResolveAndCommand
50
54
)
51
55
where
52
56
@@ -59,7 +63,9 @@ import System.Posix.Signals
59
63
#endif
60
64
import Control.Applicative ((<|>) )
61
65
import Control.Arrow ((&&&) )
62
- import Control.Lens ((.~) , (^.) )
66
+ import Control.Lens (_Just , (.~) , (?~) , (^.) , (^?) )
67
+ import Control.Monad.Trans.Class (lift )
68
+ import Control.Monad.Trans.Except (ExceptT (.. ), runExceptT )
63
69
import Data.Aeson hiding (Null , defaultOptions )
64
70
import Data.Default
65
71
import Data.Dependent.Map (DMap )
@@ -74,6 +80,7 @@ import Data.List.NonEmpty (NonEmpty (..), toList)
74
80
import qualified Data.Map as Map
75
81
import Data.Maybe
76
82
import Data.Ord
83
+ import Data.Row ((.!) )
77
84
import Data.Semigroup
78
85
import Data.String
79
86
import qualified Data.Text as T
@@ -85,7 +92,9 @@ import Ide.Plugin.Properties
85
92
import qualified Language.LSP.Protocol.Lens as L
86
93
import Language.LSP.Protocol.Message
87
94
import Language.LSP.Protocol.Types
88
- import Language.LSP.Server (LspM , getVirtualFile )
95
+ import Language.LSP.Server (LspM , LspT ,
96
+ getClientCapabilities ,
97
+ getVirtualFile )
89
98
import Language.LSP.VFS
90
99
import Numeric.Natural
91
100
import OpenTelemetry.Eventlog
@@ -403,32 +412,10 @@ instance PluginMethod Request Method_TextDocumentCodeAction where
403
412
where
404
413
uri = msgParams ^. L. textDocument . L. uri
405
414
406
- instance PluginRequestMethod Method_TextDocumentCodeAction where
407
- combineResponses _method _config (ClientCapabilities _ textDocCaps _ _ _ _) (CodeActionParams _ _ _ _ context) resps =
408
- InL $ fmap compat $ filter wasRequested $ concat $ mapMaybe nullToMaybe $ toList resps
409
- where
410
- compat :: (Command |? CodeAction ) -> (Command |? CodeAction )
411
- compat x@ (InL _) = x
412
- compat x@ (InR action)
413
- | Just _ <- textDocCaps >>= _codeAction >>= _codeActionLiteralSupport
414
- = x
415
- | otherwise = InL cmd
416
- where
417
- cmd = mkLspCommand " hls" " fallbackCodeAction" (action ^. L. title) (Just cmdParams)
418
- cmdParams = [toJSON (FallbackCodeActionParams (action ^. L. edit) (action ^. L. command))]
419
-
420
- wasRequested :: (Command |? CodeAction ) -> Bool
421
- wasRequested (InL _) = True
422
- wasRequested (InR ca)
423
- | Nothing <- _only context = True
424
- | Just allowed <- _only context
425
- -- See https://github.com/microsoft/language-server-protocol/issues/970
426
- -- This is somewhat vague, but due to the hierarchical nature of action kinds, we
427
- -- should check whether the requested kind is a *prefix* of the action kind.
428
- -- That means, for example, we will return actions with kinds `quickfix.import` and
429
- -- `quickfix.somethingElse` if the requested kind is `quickfix`.
430
- , Just caKind <- ca ^. L. kind = any (\ k -> k `codeActionKindSubsumes` caKind) allowed
431
- | otherwise = False
415
+ instance PluginMethod Request Method_CodeActionResolve where
416
+ pluginEnabled _ msgParams pluginDesc config =
417
+ pluginResolverResponsible (msgParams ^. L. data_) pluginDesc
418
+ && pluginEnabledConfig plcCodeActionsOn (configForPlugin config pluginDesc)
432
419
433
420
instance PluginMethod Request Method_TextDocumentDefinition where
434
421
pluginEnabled _ msgParams pluginDesc _ =
@@ -464,6 +451,11 @@ instance PluginMethod Request Method_TextDocumentCodeLens where
464
451
where
465
452
uri = msgParams ^. L. textDocument . L. uri
466
453
454
+ instance PluginMethod Request Method_CodeLensResolve where
455
+ pluginEnabled _ msgParams pluginDesc config =
456
+ pluginResolverResponsible (msgParams ^. L. data_) pluginDesc
457
+ && pluginEnabledConfig plcCodeActionsOn (configForPlugin config pluginDesc)
458
+
467
459
instance PluginMethod Request Method_TextDocumentRename where
468
460
pluginEnabled _ msgParams pluginDesc config = pluginResponsible uri pluginDesc
469
461
&& pluginEnabledConfig plcRenameOn (configForPlugin config pluginDesc)
@@ -535,6 +527,38 @@ instance PluginMethod Request (Method_CustomMethod m) where
535
527
pluginEnabled _ _ _ _ = True
536
528
537
529
---
530
+ instance PluginRequestMethod Method_TextDocumentCodeAction where
531
+ combineResponses _method _config (ClientCapabilities _ textDocCaps _ _ _ _) (CodeActionParams _ _ _ _ context) resps =
532
+ InL $ fmap compat $ filter wasRequested $ concat $ mapMaybe nullToMaybe $ toList resps
533
+ where
534
+ compat :: (Command |? CodeAction ) -> (Command |? CodeAction )
535
+ compat x@ (InL _) = x
536
+ compat x@ (InR action)
537
+ | Just _ <- textDocCaps >>= _codeAction >>= _codeActionLiteralSupport
538
+ = x
539
+ | otherwise = InL cmd
540
+ where
541
+ cmd = mkLspCommand " hls" " fallbackCodeAction" (action ^. L. title) (Just cmdParams)
542
+ cmdParams = [toJSON (FallbackCodeActionParams (action ^. L. edit) (action ^. L. command))]
543
+
544
+ wasRequested :: (Command |? CodeAction ) -> Bool
545
+ wasRequested (InL _) = True
546
+ wasRequested (InR ca)
547
+ | Nothing <- _only context = True
548
+ | Just allowed <- _only context
549
+ -- See https://github.com/microsoft/language-server-protocol/issues/970
550
+ -- This is somewhat vague, but due to the hierarchical nature of action kinds, we
551
+ -- should check whether the requested kind is a *prefix* of the action kind.
552
+ -- That means, for example, we will return actions with kinds `quickfix.import` and
553
+ -- `quickfix.somethingElse` if the requested kind is `quickfix`.
554
+ , Just caKind <- ca ^. L. kind = any (\ k -> k `codeActionKindSubsumes` caKind) allowed
555
+ | otherwise = False
556
+
557
+ instance PluginRequestMethod Method_CodeActionResolve where
558
+ -- CodeAction resolve is currently only used to changed the edit field, thus
559
+ -- that's the only field we are combining.
560
+ combineResponses _ _ _ codeAction (toList -> codeActions) = codeAction & L. edit .~ mconcat ((^. L. edit) <$> codeActions)
561
+
538
562
instance PluginRequestMethod Method_TextDocumentDefinition where
539
563
combineResponses _ _ _ _ (x :| _) = x
540
564
@@ -552,6 +576,10 @@ instance PluginRequestMethod Method_WorkspaceSymbol where
552
576
553
577
instance PluginRequestMethod Method_TextDocumentCodeLens where
554
578
579
+ instance PluginRequestMethod Method_CodeLensResolve where
580
+ -- A resolve request should only ever get one response
581
+ combineResponses _ _ _ _ (x :| _) = x
582
+
555
583
instance PluginRequestMethod Method_TextDocumentRename where
556
584
557
585
instance PluginRequestMethod Method_TextDocumentHover where
@@ -848,7 +876,7 @@ type CommandFunction ideState a
848
876
849
877
newtype PluginId = PluginId T. Text
850
878
deriving (Show , Read , Eq , Ord )
851
- deriving newtype (FromJSON , Hashable )
879
+ deriving newtype (ToJSON , FromJSON , Hashable )
852
880
853
881
instance IsString PluginId where
854
882
fromString = PluginId . T. pack
@@ -949,7 +977,8 @@ instance HasTracing WorkspaceSymbolParams where
949
977
instance HasTracing CallHierarchyIncomingCallsParams
950
978
instance HasTracing CallHierarchyOutgoingCallsParams
951
979
instance HasTracing CompletionItem
952
-
980
+ instance HasTracing CodeAction
981
+ instance HasTracing CodeLens
953
982
-- ---------------------------------------------------------------------
954
983
955
984
{-# NOINLINE pROCESS_ID #-}
@@ -983,3 +1012,107 @@ getProcessID = fromIntegral <$> P.getProcessID
983
1012
984
1013
installSigUsr1Handler h = void $ installHandler sigUSR1 (Catch h) Nothing
985
1014
#endif
1015
+
1016
+ -- | When provided with both a codeAction provider and an affiliated codeAction
1017
+ -- resolve provider, this function creates a handler that automatically uses
1018
+ -- your resolve provider to fill out you original codeAction if the client doesn't
1019
+ -- have codeAction resolve support. This means you don't have to check whether
1020
+ -- the client supports resolve and act accordingly in your own providers.
1021
+ mkCodeActionHandlerWithResolve
1022
+ :: forall ideState . (ideState -> PluginId -> CodeActionParams -> LspM Config (Either ResponseError ([Command |? CodeAction ] |? Null )))
1023
+ -> (ideState -> PluginId -> CodeAction -> LspM Config (Either ResponseError CodeAction ))
1024
+ -> PluginHandlers ideState
1025
+ mkCodeActionHandlerWithResolve codeActionMethod codeResolveMethod =
1026
+ let newCodeActionMethod ideState pid params = runExceptT $
1027
+ do codeActionReturn <- ExceptT $ codeActionMethod ideState pid params
1028
+ caps <- lift getClientCapabilities
1029
+ case codeActionReturn of
1030
+ r@ (InR Null ) -> pure r
1031
+ (InL ls) | -- If the client supports resolve, we will wrap the resolve data in a owned
1032
+ -- resolve data type to allow the server to know who to send the resolve request to
1033
+ supportsCodeActionResolve caps -> pure $ InL (wrapCodeActionResolveData pid <$> ls)
1034
+ -- This is the actual part where we call resolveCodeAction which fills in the edit data for the client
1035
+ | otherwise -> InL <$> traverse (resolveCodeAction ideState pid) ls
1036
+ newCodeResolveMethod ideState pid params =
1037
+ codeResolveMethod ideState pid (unwrapCodeActionResolveData params)
1038
+ in mkPluginHandler SMethod_TextDocumentCodeAction newCodeActionMethod
1039
+ <> mkPluginHandler SMethod_CodeActionResolve newCodeResolveMethod
1040
+ where
1041
+ dropData :: CodeAction -> CodeAction
1042
+ dropData ca = ca & L. data_ .~ Nothing
1043
+ resolveCodeAction :: ideState -> PluginId -> (Command |? CodeAction ) -> ExceptT ResponseError (LspT Config IO ) (Command |? CodeAction )
1044
+ resolveCodeAction _ideState _pid c@ (InL _) = pure c
1045
+ resolveCodeAction ideState pid (InR codeAction) =
1046
+ fmap (InR . dropData) $ ExceptT $ codeResolveMethod ideState pid codeAction
1047
+
1048
+ -- | When provided with both a codeAction provider that includes both a command
1049
+ -- and a data field and a resolve provider, this function creates a handler that
1050
+ -- defaults to using your command if the client doesn't have code action resolve
1051
+ -- support. This means you don't have to check whether the client supports resolve
1052
+ -- and act accordingly in your own providers.
1053
+ mkCodeActionWithResolveAndCommand
1054
+ :: forall ideState . (ideState -> PluginId -> CodeActionParams -> LspM Config (Either ResponseError ([Command |? CodeAction ] |? Null )))
1055
+ -> (ideState -> PluginId -> CodeAction -> LspM Config (Either ResponseError CodeAction ))
1056
+ -> PluginHandlers ideState
1057
+ mkCodeActionWithResolveAndCommand codeActionMethod codeResolveMethod =
1058
+ let newCodeActionMethod ideState pid params = runExceptT $
1059
+ do codeActionReturn <- ExceptT $ codeActionMethod ideState pid params
1060
+ caps <- lift getClientCapabilities
1061
+ case codeActionReturn of
1062
+ r@ (InR Null ) -> pure r
1063
+ (InL ls) | -- If the client supports resolve, we will wrap the resolve data in a owned
1064
+ -- resolve data type to allow the server to know who to send the resolve request to
1065
+ -- and dump the command fields.
1066
+ supportsCodeActionResolve caps ->
1067
+ pure $ InL (dropCommands . wrapCodeActionResolveData pid <$> ls)
1068
+ -- If they do not we will drop the data field.
1069
+ | otherwise -> pure $ InL $ dropData <$> ls
1070
+ newCodeResolveMethod ideState pid params =
1071
+ codeResolveMethod ideState pid (unwrapCodeActionResolveData params)
1072
+ in mkPluginHandler SMethod_TextDocumentCodeAction newCodeActionMethod
1073
+ <> mkPluginHandler SMethod_CodeActionResolve newCodeResolveMethod
1074
+ where dropData :: Command |? CodeAction -> Command |? CodeAction
1075
+ dropData ca = ca & _R . L. data_ .~ Nothing
1076
+ dropCommands :: Command |? CodeAction -> Command |? CodeAction
1077
+ dropCommands ca = ca & _R . L. command .~ Nothing
1078
+
1079
+ supportsCodeActionResolve :: ClientCapabilities -> Bool
1080
+ supportsCodeActionResolve caps =
1081
+ caps ^? L. textDocument . _Just . L. codeAction . _Just . L. dataSupport . _Just == Just True
1082
+ && case caps ^? L. textDocument . _Just . L. codeAction . _Just . L. resolveSupport . _Just of
1083
+ Just row -> " edit" `elem` row .! # properties
1084
+ _ -> False
1085
+
1086
+ -- We don't wrap commands
1087
+ wrapCodeActionResolveData :: PluginId -> (a |? CodeAction ) -> a |? CodeAction
1088
+ wrapCodeActionResolveData _pid c@ (InL _) = c
1089
+ wrapCodeActionResolveData pid (InR c@ (CodeAction {_data_= Just x})) =
1090
+ InR $ c & L. data_ ?~ toJSON (ORD pid x)
1091
+ -- Neither do we wrap code actions's without data fields,
1092
+ wrapCodeActionResolveData _pid c@ (InR (CodeAction {_data_= Nothing })) = c
1093
+
1094
+ unwrapCodeActionResolveData :: CodeAction -> CodeAction
1095
+ unwrapCodeActionResolveData c@ CodeAction {_data_ = Just x}
1096
+ | Success ORD {value = v} <- fromJSON x = c & L. data_ ?~ v
1097
+ -- If we can't successfully decode the value as a ORD type than
1098
+ -- we just return the codeAction untouched.
1099
+ unwrapCodeActionResolveData c = c
1100
+
1101
+ -- | Allow plugins to "own" resolve data, allowing only them to be queried for
1102
+ -- the resolve action. This design has added flexibility at the cost of nested
1103
+ -- Value types
1104
+ data OwnedResolveData = ORD {
1105
+ owner :: PluginId
1106
+ , value :: Value
1107
+ } deriving (Generic , Show )
1108
+ instance ToJSON OwnedResolveData
1109
+ instance FromJSON OwnedResolveData
1110
+
1111
+ pluginResolverResponsible :: Maybe Value -> PluginDescriptor c -> Bool
1112
+ pluginResolverResponsible (Just val) pluginDesc =
1113
+ case fromJSON val of
1114
+ (Success (ORD o _)) -> pluginId pluginDesc == o
1115
+ _ -> True -- We want to fail open in case our resolver is not using the ORD type
1116
+ -- This is a wierd case, because anything that gets resolved should have a data
1117
+ -- field, but in any case, failing open is safe enough.
1118
+ pluginResolverResponsible Nothing _ = True
0 commit comments