-
-
Notifications
You must be signed in to change notification settings - Fork 389
Resolve for explicit-imports #3682
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
Resolve for explicit-imports #3682
Conversation
This is nice, I think I even have an issue for this. In particular, it seems easier for people to write client-side stuff to trigger specific code actions, so this will make it easier for people to define a hotkey for "make current import (only) explicit". |
Probably still needs some clean up and general refactoring
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The plugin "works" now and there are specific tests for it. While I am pretty happy with the general structure, I will want to go over and see if there is anything I want to refactor or clean up still though
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needs a bit more work but generally looking plausible. I like how we get to unify all the logic into the rule and the (sort of) command handler.
It almost feels like there's a pattern here you could use to unify all of the things. Something like:
- You have a rule that generates a bunch of possible actions in a file, with ranges.
- You have a command handler/resolve function handler that applies the action
- You have a code lens handler that generates lenses for some subset of the actions
- You have a code lens resolve handler that calls the command handler and uses some logic to set the title appropriately
- You have a code action handler that generates code actions for some subset of the actions.
- You have a code action resolve handler that calls the command handler/fills in the edit, depending.
I think it probably would only work for a restricted set of things: you really need your "logic handler" to just produce an edit, so you can use that same edit in all scenarios (applyEdit
, filling it in on a code action, etc.).
plugins/hls-explicit-imports-plugin/src/Ide/Plugin/ExplicitImports.hs
Outdated
Show resolved
Hide resolved
pluginHandlers = | ||
-- This plugin provides code lenses | ||
mkPluginHandler SMethod_TextDocumentCodeLens (lensProvider pred) | ||
<> mkPluginHandler SMethod_CodeLensResolve lensResolveProvider |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want to design a similar paired handler function for lenses like we have for code actions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the client never advertises a code lens resolve capability, we have to assume that all clients support it, and can't change our behaviour depending on whether they support it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
seems busted, I opened microsoft/language-server-protocol#1761
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's very likely that almost no clients support it so this may just break for many clients.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that actually not having a capability for it must mean that all clients (need to?) support it. It's also a relatively important resolve type, because you send all your codelens every edit. Vscode works with resolve, and I know the rust server uses it too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could still be useful to have the paired function since maybe we can simplify some stuff that's always the same like the data handling.
runImportCommand :: CommandFunction IdeState EIResolveData | ||
runImportCommand ideState eird = pluginResponse $ do | ||
case eird of | ||
ResolveOne uri int -> do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing case for ResolveAll
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, since you've named the fields, this can be a nice place to use NamedFieldPuns
, i.e.
ResolveOne{uri, int} -> ...
just makes sure you can't mix up the positional args
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The command actually is responding only to the lens, so shouldn't handle the ResolveAll case. In this case I'm actually reusing the data function, but it might be better to have seperate types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it seems reasonable to share the code for running the action if possible? Having one function that handles either case and then we just call it seems good.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay good point, there is no reason to use resolve from codeActions then, but no reason to implement resolve for no reason.
plugins/hls-explicit-imports-plugin/src/Ide/Plugin/ExplicitImports.hs
Outdated
Show resolved
Hide resolved
plugins/hls-explicit-imports-plugin/src/Ide/Plugin/ExplicitImports.hs
Outdated
Show resolved
Hide resolved
{getMinimalImportsResult :: [(LImportDecl GhcRn, Maybe T.Text)]} | ||
data MinimalImportsResult = MinimalImportsResult | ||
{ forLens :: [(Range, Int)] | ||
, forCodeActions :: RM.RangeMap (Range, Int) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need the previous field, can't we just enumerate the (Range, Int)
pairs from the RangeMap
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... also why does this have a range in the output? is it the same range as in the input?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From what I've researched it's not possible to easily extract the Range key from the RangeMap. Also the range in the output is the range of the textedit, but the range of the input is (in this case) what the client sends over, which is not identical
plugins/hls-explicit-imports-plugin/src/Ide/Plugin/ExplicitImports.hs
Outdated
Show resolved
Hide resolved
plugins/hls-explicit-imports-plugin/src/Ide/Plugin/ExplicitImports.hs
Outdated
Show resolved
Hide resolved
plugins/hls-explicit-imports-plugin/src/Ide/Plugin/ExplicitImports.hs
Outdated
Show resolved
Hide resolved
plugins/hls-explicit-imports-plugin/src/Ide/Plugin/ExplicitImports.hs
Outdated
Show resolved
Hide resolved
plugins/hls-explicit-imports-plugin/hls-explicit-imports-plugin.cabal
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally looking good!
plugins/hls-explicit-imports-plugin/src/Ide/Plugin/ExplicitImports.hs
Outdated
Show resolved
Hide resolved
plugins/hls-explicit-imports-plugin/src/Ide/Plugin/ExplicitImports.hs
Outdated
Show resolved
Hide resolved
-------------------------------------------------------------------------------- | ||
|
||
resolveWTextEdit :: IdeState -> EIResolveData -> ExceptT String (LspT Config IO) WorkspaceEdit | ||
resolveWTextEdit ideState (ResolveOne uri int) = do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: you could deduplicate a bit by doing:
resolveWTextEdit ideState action = do
nfp <- ..
(MinimalImports ...
case action of
ResolveOne ...
let edits = IM.elems forResolve | ||
pure $ mkWorkspaceEdit uri edits | ||
|
||
mkWorkspaceEdit :: Uri -> [TextEdit] -> WorkspaceEdit |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... and if you did the deduplication you could probably inline this
codeActionBreakFile :: FilePath -> Int -> Int -> TestTree | ||
codeActionBreakFile fp l c = goldenWithExplicitImports " code action" fp codeActionNoResolveCaps $ \doc -> do | ||
_ <- waitForDiagnostics | ||
changeDoc doc [edit] | ||
actions <- getCodeActions doc (pointRange l c) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test doesn't look quite right to me: shouldn't we be getting the code actions, picking one, then editing the file, then resolving the action we picked before and seeing it fail?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this test causes a parsing error in the file and then gets a code action and executes it. Because we are using "useWithStale" we can still operate on a file that has a parse fail.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I see, that's slightly different then. I think it might also be interesting to put an edit in between getting the actions and resolving one. Potentially one that doesn't cause a parse error, so it just mixes up the uniques, which should cause a failure still.
@@ -79,18 +123,16 @@ caTitle _ = Nothing | |||
-- code lens tests | |||
|
|||
codeLensGoldenTest :: FilePath -> Int -> TestTree |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe these things could also go in hls-test-utils
? Seems like you've been repeating something similar a few times. Good to deduplicate the test stuff too!
I am pretty much rewriting the interface with hls for this plugin, as it was pretty poorly written (functions inside both an io and maybe monad, that used neither of them). In the process besides adding support for resolve for the code lens and code actions, I am also adding specific code actions for each import (before there was just one code action for explicit imports for everything)