-
-
Notifications
You must be signed in to change notification settings - Fork 369
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
support add-argument action #3149
Conversation
Thanks for your contribution! For reference, below are the behavior of some other language servers/IDEs on this:
I'm not quite sure about the design, maybe someone else will provide better insight. |
Thanks for the screenshots! Very helpful context :) You note that Rust/TS have no quick fix. I am quite familiar with the IntelliJ interface from my previous job. I would love to support this workflow, but after I looked around the HLS and LSP issues I got the impression that such a pretty workflow would require too much work to justify. Is that you guys' experience? I really think our UX could be best in class given Haskell's type system, but I'm concerned about maintenance hell. On a side note, it would be really great to have some sort of function: alterAllUsages :: {- function name -} RdrName -> (LHsExpr -> TransformT m LHsExpr) -> IDEState -> TransformT m IDEState The idea being that when I add a function parameter to a definition, I can insert appropriate holes in all usages project wide. Does anyone have any thoughts on the feasibility of this? @OliverMadine seems like a candidate for such knowledge! module One where
import Two
bar = foo True
----
module Two where
foo True = unknown False
----- BECOMES -----
module One where
import Two
bar = foo True _unknown
----
module Two where
foo True unknown = unknown False |
Yes, it seems very feasible to me! |
@@ -623,6 +653,14 @@ eqSrcSpanA l r = leftmost_smallest l r == EQ | |||
#endif | |||
|
|||
#if MIN_VERSION_ghc(9,2,0) | |||
|
|||
spanContainsRange :: SrcSpan -> Range -> Bool |
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 you can use realSrcSpanToRange
and isSubrangeOf
?
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 forgot to respond. This worked perfectly thanks :)
plugins/hls-refactor-plugin/src/Development/IDE/GHC/ExactPrint.hs
Outdated
Show resolved
Hide resolved
plugins/hls-refactor-plugin/src/Development/IDE/GHC/ExactPrint.hs
Outdated
Show resolved
Hide resolved
plugins/hls-refactor-plugin/src/Development/IDE/Plugin/CodeAction.hs
Outdated
Show resolved
Hide resolved
pure $ Just [decl'] | ||
_ -> pure Nothing | ||
case runTransformT $ modifySmallestDeclWithM (`spanContainsRange` range) insertArg (makeDeltaAst parsedSource) of | ||
Left err -> error $ "Error when inserting argument: " <> err |
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.
Avoid throwing an error
here, as it will at best kill all other code actions and at worst kill all other plugins. Ideally you should return a ResponseError
but that probably requires too much 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.
I'm unclear on what to do here. For the time being I used a trace call and returned no diffs. Is ResponseError part of the MonadLSP interface?
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.
Yes it is :)
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 summoned up the courage to reveal ResponseErrors in the CodeAction API. The only hold up is I don't know how to throw/report ResponseErrors with MonadLSP after some hoogling.
Anyone have any pointers? 😅 Note that I am returning a list of errors and successes (since there are many potential code actions), so it should just be logResponseError :: MonadLSP m => ResponseError -> m ()
, not throw
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 fine to just use logError
or logWarn
and return nothing
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.
After some investigation, it appears that the only Recorder available to CodeActions is Recorder (WithPriority E.Log)
, which is the logging from Development.IDE.Core.Shake
.
Which logger should I be using for code actions? I can't find a Logger or nonShake recorder in the parent functions...
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 is because this plugin was only recently extracted from ghcide core. The plugin needs to define its own Logger I guess.
plugins/hls-refactor-plugin/src/Development/IDE/GHC/ExactPrint.hs
Outdated
Show resolved
Hide resolved
plugins/hls-refactor-plugin/src/Development/IDE/Plugin/CodeAction.hs
Outdated
Show resolved
Hide resolved
There is a plug-in that attempts to update signatures when it can. |
@drsooch I've thought about the change-type-sig plugin in this context. I think they're fairly different situations. In change-type-sig, I can only see occurrences where the new type signature is given. However, in this case, we do not have the full type signature: we only have the type of the argument we're adding. That means we do have to manually alter the HsSigType with the exact print api, which is more complicated. I implemented a working first attempt (it's surely very buggy for non-trivial layouts): https://github.com/santiweight/haskell-language-server/tree/add-arg-type-sig |
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.
Looks good to me modulo the build failures
Can you add yourself to |
It seems that the tests didn't run as part of CI? Besides that I've added myself to codeowners but I don't have write access, which is causing an error in the code owners file! I'm happy to merge whenever now :) Thanks for the help getting this one through! |
They should have, we're testing
I've invited you to the repo :) |
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 took a fairly superficial look, but I think we can get this over the line :)
Thank you for digging into the code actions in hls-refactor-plugin
. As Pepe mentioned, they only recently got extracted from ghcide
, so they quite probably be tidied up more to be more in keeping with the style of more recent code action handlers if you felt moved to!
@@ -119,6 +121,9 @@ p `isInsideSrcSpan` r = case srcSpanToRange r of | |||
Just (Range sp ep) -> sp <= p && p <= ep | |||
_ -> False | |||
|
|||
spanContainsRange :: SrcSpan -> Range -> Bool | |||
spanContainsRange srcSpan range = maybe False (range `isSubrangeOf`) $ srcSpanToRange srcSpan |
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 this is clearer as a simple case
spanContainsRange srcSpan range = maybe False (range `isSubrangeOf`) $ srcSpanToRange srcSpan | |
spanContainsRange srcSpan range = case srcSpanToRange srcSpan of | |
Just r -> range `isSubrangeOf` r | |
-- Nice place to explain why this is the right policy | |
Nothing -> False |
And having written it that way, I'm not sure whether we have got the right policy there. Do we want to return False
in the "can't turn a src span into a range" case? Should we be returning a Maybe Bool
and preserving that information?
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.
At the very least the Haddock should tell you that the returned bool includes this information!
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 returned a Nothing and added a comment; I think it's clearer now...
] | ||
|
||
#if MIN_VERSION_ghc(9,2,1) | ||
addFunctionArgumentTests :: 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.
This is a great time to break the cycle of violence and stop adding things to the single gigantic test file :) put it in another module!
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 have already done this in a followup change. Unfortunately fixing this now would cost me some time with conflicts, but I promise to follow up :)
@@ -881,34 +891,95 @@ suggestReplaceIdentifier contents Diagnostic{_range=_range,..} | |||
= [ ("Replace with ‘" <> name <> "’", [mkRenameEdit contents _range name]) | name <- renameSuggestions ] | |||
| otherwise = [] | |||
|
|||
matchVariableNotInScope :: T.Text -> Maybe (T.Text, Maybe T.Text) |
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.
Perhaps an annoying suggestion, but these matching functions are all nice pure functions that could benefit from some direct tests checking that they do definitely match all the cases you care about.
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, this module is also quite large, perhaps the add-action stuff could go in a separate module also?
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 actually already did this in a followup MR. Would you be okay with following up with this change (to avoid unnecessary conflicts)?
suggestNewDefinition ideOptions parsedModule contents Diagnostic {_message, _range} | ||
| Just (name, typ) <- matchVariableNotInScope message = | ||
newDefinitionAction ideOptions parsedModule _range name typ | ||
| Just (name, typ) <- matchFoundHole message, |
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 you have a Plan. Something like:
- Look for errors of a particular kind
- Based on the kind of error, make certain kinds of suggestion
But the Plan is not written down anywhere, and as a reader it's hard to figure out what it is. Maybe worth writing it down somewhere and referring to 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.
Revised
Thanks for the review @michaelpj A heads up on the status of this PR... I'm pretty happy with where it is right now, but I'm a little worried about my approach to using the Exactprint API, and so I'm going to attempt reimplementing the code in a better style (by following the examples in ghc-exactprint). This MR isn't forgotten, just hopefully being replaced by something better... |
I was hoping to upstream some of the changes here in ghc-exactprint before merging, but I think that process will take some time. I've answered the comments from reviewers, and I now think this is ready to merge! |
I'm happy for you to continue improving things, especially since it sounds like you're definitely continuing to work in this area :) |
This action supports an action "Add argument
xxx
to function" whenever there is an undefined variable error:The only minor wrinkle is that
But this is good enough for now as a matter of pragmatism.
As part of this MR, I fixed some behavior. Previously an unknown-type undefined variable did not offer a "define x" option, but now does: