-
-
Notifications
You must be signed in to change notification settings - Fork 391
Resolve 2: Support for resolve in hls-hlint-plugin #3679
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
355e95c
fb49c31
985340b
9712ca5
26af22d
d1d299b
6b7754e
5335f1d
735feca
6b3b915
a57e5d9
d2ba87e
57119c4
9f9e807
68080fb
1ac3823
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -67,6 +67,7 @@ import Control.Lens (_Just, (.~), (?~), (^.), (^?)) | |
import Control.Monad.Trans.Class (lift) | ||
import Control.Monad.Trans.Except (ExceptT (..), runExceptT) | ||
import Data.Aeson hiding (Null, defaultOptions) | ||
import qualified Data.Aeson | ||
import Data.Default | ||
import Data.Dependent.Map (DMap) | ||
import qualified Data.Dependent.Map as DMap | ||
|
@@ -93,8 +94,10 @@ import qualified Language.LSP.Protocol.Lens as L | |
import Language.LSP.Protocol.Message | ||
import Language.LSP.Protocol.Types | ||
import Language.LSP.Server (LspM, LspT, | ||
ProgressCancellable (Cancellable), | ||
getClientCapabilities, | ||
getVirtualFile) | ||
getVirtualFile, sendRequest, | ||
withIndefiniteProgress) | ||
import Language.LSP.VFS | ||
import Numeric.Natural | ||
import OpenTelemetry.Eventlog | ||
|
@@ -1051,30 +1054,48 @@ mkCodeActionHandlerWithResolve codeActionMethod codeResolveMethod = | |
-- support. This means you don't have to check whether the client supports resolve | ||
-- and act accordingly in your own providers. | ||
mkCodeActionWithResolveAndCommand | ||
:: forall ideState. (ideState -> PluginId -> CodeActionParams -> LspM Config (Either ResponseError ([Command |? CodeAction] |? Null))) | ||
:: forall ideState. | ||
PluginId | ||
-> (ideState -> PluginId -> CodeActionParams -> LspM Config (Either ResponseError ([Command |? CodeAction] |? Null))) | ||
-> (ideState -> PluginId -> CodeAction -> LspM Config (Either ResponseError CodeAction)) | ||
-> PluginHandlers ideState | ||
mkCodeActionWithResolveAndCommand codeActionMethod codeResolveMethod = | ||
-> ([PluginCommand ideState], PluginHandlers ideState) | ||
mkCodeActionWithResolveAndCommand plId codeActionMethod codeResolveMethod = | ||
let newCodeActionMethod ideState pid params = runExceptT $ | ||
do codeActionReturn <- ExceptT $ codeActionMethod ideState pid params | ||
caps <- lift getClientCapabilities | ||
case codeActionReturn of | ||
r@(InR Null) -> pure r | ||
(InL ls) | -- If the client supports resolve, we will wrap the resolve data in a owned | ||
-- resolve data type to allow the server to know who to send the resolve request to | ||
-- and dump the command fields. | ||
supportsCodeActionResolve caps -> | ||
pure $ InL (dropCommands . wrapCodeActionResolveData pid <$> ls) | ||
-- If they do not we will drop the data field. | ||
| otherwise -> pure $ InL $ dropData <$> ls | ||
pure $ InL (wrapCodeActionResolveData pid <$> ls) | ||
-- If they do not we will drop the data field, in addition we will populate the command | ||
-- field with our command to execute the resolve, with the whole code action as it's argument. | ||
| otherwise -> pure $ InL $ moveDataToCommand <$> ls | ||
newCodeResolveMethod ideState pid params = | ||
codeResolveMethod ideState pid (unwrapCodeActionResolveData params) | ||
in mkPluginHandler SMethod_TextDocumentCodeAction newCodeActionMethod | ||
<> mkPluginHandler SMethod_CodeActionResolve newCodeResolveMethod | ||
where dropData :: Command |? CodeAction -> Command |? CodeAction | ||
dropData ca = ca & _R . L.data_ .~ Nothing | ||
dropCommands :: Command |? CodeAction -> Command |? CodeAction | ||
dropCommands ca = ca & _R . L.command .~ Nothing | ||
in ([PluginCommand "codeActionResolve" "Executes resolve for code action" (executeResolveCmd plId codeResolveMethod)], | ||
joyfulmantis marked this conversation as resolved.
Show resolved
Hide resolved
|
||
mkPluginHandler SMethod_TextDocumentCodeAction newCodeActionMethod | ||
<> mkPluginHandler SMethod_CodeActionResolve newCodeResolveMethod) | ||
where moveDataToCommand :: Command |? CodeAction -> Command |? CodeAction | ||
moveDataToCommand ca = | ||
let dat = toJSON <$> ca ^? _R -- We need to take the whole codeAction | ||
-- And put it in the argument for the Command, that way we can later | ||
-- pas it to the resolve handler (which expects a whole code action) | ||
cmd = mkLspCommand plId (CommandId "codeActionResolve") "Execute Code Action" (pure <$> dat) | ||
in ca | ||
& _R . L.data_ .~ Nothing -- Set the data field to nothing | ||
& _R . L.command ?~ cmd -- And set the command to our previously created command | ||
executeResolveCmd :: PluginId -> PluginMethodHandler ideState Method_CodeActionResolve -> CommandFunction ideState CodeAction | ||
executeResolveCmd pluginId resolveProvider ideState ca = do | ||
withIndefiniteProgress "Executing code action..." Cancellable $ do | ||
joyfulmantis marked this conversation as resolved.
Show resolved
Hide resolved
|
||
resolveResult <- resolveProvider ideState pluginId ca | ||
case resolveResult of | ||
Right CodeAction {_edit = Just wedits } -> do | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What happens if it has the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, if it's cheap it would be nice to check that all the other fields of the code action are the same as the input one, i.e. the resolve handler only gave us an edit. In the more generalized case, we should check that the only fields that differ are the ones that the client says it can resolve. Then we can log if the handler does something wrong there. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, I guess also the command and data fields should be unset? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think, according to the spec, we can only change the set of resolvable fields, so even though it would make sense to dump the data field, it still needs to stay the same as when we received it. (We also don't have the client capabilities so can't do anything with that). Finally in this case the only thing we can execute on is the returned WorkspaceEdit. We can return an error if any other field is changed, but not sure about the performance penalty of doing that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't we pass in the client caps here? I don't know if it's worth doing the checking of field setting, it just seems like a way to sanity check plugins, which seems somewhat useful. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, I guess we can't pass in the client capabilities here, but we should be able to pull them from the monad itself, since the plugin handler is effectively in the Lsp monad already There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah I see, this is exactly the sort of thing we could do if we had the client caps when making the handlers! |
||
_ <- sendRequest SMethod_WorkspaceApplyEdit (ApplyWorkspaceEditParams Nothing wedits) (\_ -> pure ()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems like we probably should care a little about the response? e.g. we probably want to do something if we fail, like at least log There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's quite hard here, as we can't use a recorder (That's defined in the ghcide package, so cyclic dependency), and we only have a callback to work with. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ugh. Okay, I guess we can't do much here, but that's rather unsatisfying. Maybe we need to move some of the recorder stuff into There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would really like to be able to do some logging in the generic functions you're defining, that way we can get somewhat-decent logging for lots of plugins by defining it in one place. |
||
pure $ Right Data.Aeson.Null | ||
Right _ -> pure $ Left $ responseError "No edit in CodeAction" | ||
Left err -> pure $ Left err | ||
|
||
supportsCodeActionResolve :: ClientCapabilities -> Bool | ||
supportsCodeActionResolve caps = | ||
|
Uh oh!
There was an error while loading. Please reload this page.