-
-
Notifications
You must be signed in to change notification settings - Fork 391
Provide explicit import in inlay hints #4235
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 4 commits
b7da1f5
3305973
245049a
0598138
a27c5c8
6b3e5aa
f9f1207
8e28076
d0f27c2
3e9d5a1
8d94c51
2e869db
dbd7508
d0fe221
75b0ecb
e0543b9
a6b7556
2085635
ccf2d8f
fb52cee
6e5f746
f4c2ea2
62a51ce
f70e402
57ef0db
f8b1993
a50b148
af635f7
0fab728
4c7313d
ffdb94c
0a876c3
c11e356
599ebcf
2a2b516
3e5b88f
6cfafd5
ce761e7
2f3b57b
64b9533
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 |
---|---|---|
|
@@ -504,6 +504,12 @@ instance PluginMethod Request Method_WorkspaceSymbol where | |
-- Unconditionally enabled, but should it really be? | ||
handlesRequest _ _ _ _ = HandlesRequest | ||
|
||
instance PluginMethod Request Method_TextDocumentInlayHint where | ||
handlesRequest _ _ _ _ = HandlesRequest | ||
|
||
instance PluginMethod Request Method_InlayHintResolve where | ||
handlesRequest _ _ _ _ = HandlesRequest | ||
|
||
instance PluginMethod Request Method_TextDocumentCodeLens where | ||
handlesRequest = pluginEnabledWithFeature plcCodeLensOn | ||
|
||
|
@@ -803,6 +809,12 @@ instance PluginRequestMethod Method_TextDocumentSemanticTokensFull where | |
instance PluginRequestMethod Method_TextDocumentSemanticTokensFullDelta where | ||
combineResponses _ _ _ _ (x :| _) = x | ||
|
||
instance PluginRequestMethod Method_TextDocumentInlayHint where | ||
combineResponses _ _ _ _ (x :| _) = x | ||
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. This doesn't seem right: the inlay hints request returns a list! So it's a really easy example where we can combine the responses nicely, just concatenate them. |
||
|
||
instance PluginRequestMethod Method_InlayHintResolve where | ||
combineResponses _ _ _ _ (x :| _) = x | ||
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. Include the reference to the note explaining why there should only be one resolve handler. We can also consider implementing resolve later. |
||
|
||
takeLefts :: [a |? b] -> [a] | ||
takeLefts = mapMaybe (\x -> [res | (InL res) <- Just x]) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,10 +6,12 @@ | |
{-# LANGUAGE RecordWildCards #-} | ||
{-# LANGUAGE TypeFamilies #-} | ||
{-# LANGUAGE ViewPatterns #-} | ||
|
||
module Ide.Plugin.ExplicitImports | ||
( descriptor | ||
, descriptorForModules | ||
, abbreviateImportTitle | ||
, squashedAbbreviateImportTitle | ||
, Log(..) | ||
) where | ||
|
||
|
@@ -22,6 +24,7 @@ import Control.Monad.Trans.Except (ExceptT) | |
import Control.Monad.Trans.Maybe | ||
import qualified Data.Aeson as A (ToJSON (toJSON)) | ||
import Data.Aeson.Types (FromJSON) | ||
import Data.Char (isSpace) | ||
import qualified Data.IntMap as IM (IntMap, elems, | ||
fromList, (!?)) | ||
import Data.IORef (readIORef) | ||
|
@@ -44,8 +47,9 @@ import GHC.Generics (Generic) | |
import Ide.Plugin.Error (PluginError (..), | ||
getNormalizedFilePathE, | ||
handleMaybe) | ||
import Ide.Plugin.RangeMap (filterByRange) | ||
import qualified Ide.Plugin.RangeMap as RM (RangeMap, fromList) | ||
import qualified Ide.Plugin.RangeMap as RM (RangeMap, | ||
filterByRange, | ||
fromList) | ||
import Ide.Plugin.Resolve | ||
import Ide.PluginUtils | ||
import Ide.Types | ||
|
@@ -98,9 +102,11 @@ descriptorForModules recorder modFilter plId = | |
-- This plugin provides code lenses | ||
mkPluginHandler SMethod_TextDocumentCodeLens (lensProvider recorder) | ||
<> mkResolveHandler SMethod_CodeLensResolve (lensResolveProvider recorder) | ||
-- This plugin provides code actions | ||
-- This plugin provides inlay hints | ||
<> mkPluginHandler SMethod_TextDocumentInlayHint (inlayHintProvider recorder) | ||
-- <> mkResolveHandler SMethod_InlayHintResolve (inlayHintResolveProvider recorder) | ||
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 just delete this for now, we can worry about resolve later. |
||
-- This plugin provides code actions | ||
<> codeActionHandlers | ||
|
||
} | ||
|
||
-- | The actual command handler | ||
|
@@ -146,12 +152,13 @@ lensProvider _ state _ CodeLensParams {_textDocument = TextDocumentIdentifier { | |
, _range = range | ||
, _command = Nothing } | ||
|
||
|
||
lensResolveProvider :: Recorder (WithPriority Log) -> ResolveFunction IdeState IAResolveData 'Method_CodeLensResolve | ||
lensResolveProvider _ ideState plId cl uri rd@(ResolveOne _ uid) = do | ||
nfp <- getNormalizedFilePathE uri | ||
(ImportActionsResult{forResolve}, _) <- runActionE "ImportActions" ideState $ useWithStaleE ImportActions nfp | ||
target <- handleMaybe PluginStaleResolve $ forResolve IM.!? uid | ||
let updatedCodeLens = cl & L.command ?~ mkCommand plId target | ||
let updatedCodeLens = cl & L.command ?~ mkCommand plId target | ||
pure updatedCodeLens | ||
where mkCommand :: PluginId -> ImportEdit -> Command | ||
mkCommand pId (ImportEdit{ieResType, ieText}) = | ||
|
@@ -166,6 +173,36 @@ lensResolveProvider _ ideState plId cl uri rd@(ResolveOne _ uid) = do | |
lensResolveProvider _ _ _ _ _ rd = do | ||
throwError $ PluginInvalidParams (T.pack $ "Unexpected argument for lens resolve handler: " <> show rd) | ||
|
||
|
||
inlayHintProvider :: Recorder (WithPriority Log) -> PluginMethodHandler IdeState 'Method_TextDocumentInlayHint | ||
inlayHintProvider _ state _ InlayHintParams {_textDocument = TextDocumentIdentifier {_uri}, _range = visibleRange} = do | ||
nfp <- getNormalizedFilePathE _uri | ||
(ImportActionsResult {forLens, forResolve}, pm) <- runActionE "ImportActions" state $ useWithStaleE ImportActions nfp | ||
let inlayHints = [ generateInlayHints newRange ie | ||
| (range, int) <- forLens | ||
, range < visibleRange | ||
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 you want |
||
, Just newRange <- [toCurrentRange pm range] | ||
, Just ie <- [forResolve IM.!? int]] | ||
pure $ InL inlayHints | ||
where | ||
generateInlayHints :: Range -> ImportEdit -> InlayHint | ||
generateInlayHints Range {_end} ie = | ||
InlayHint { _position = _end | ||
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. This needs a comment. We are assuming that the appropriate position for the hint to begin is the end of the range for the lens, we should document that carefully. |
||
, _label = mkLabel ie | ||
, _kind = Nothing | ||
, _textEdits = Nothing | ||
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. We should definitely set this, it's kind of the point! 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. God, the spec is weird. Inlay hints themselves only have text edits, but label parts can have commands??? I'm not even sure what you're meant to do there. |
||
, _tooltip = Nothing | ||
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 we should definitely consider using this! For example, the tooltip for the minimal imports inlay hint could say "This is an import list for exactly the names used by the current module", or something. That way it's a bit more discoverable what it means. It's a nice UX feature! 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. Could this be as simple as "Make this import explicit"? This doesn't even require resolve, even though the LSP spec recommends that the tooltip be obtained via resolve, but it's simple enough. edit: it may be different when refining, but this still simple enough. 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. Yes, I think either of those would be fine. I think we can just make it a constant string; the hint itself shows the things that are going to be inserted. So no need for resolve. Resolve is only for things that are expensive to compute. |
||
, _paddingLeft = Just True | ||
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. Did you figure out what these padding fields do? It has been very unclear to me form the spec! 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. 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, so it basically means "show an extra space before the inlay hint. I guess that makes sense. |
||
, _paddingRight = Nothing | ||
, _data_ = Nothing | ||
} | ||
mkLabel :: ImportEdit -> T.Text |? [InlayHintLabelPart] | ||
mkLabel (ImportEdit{ieResType, ieText}) = | ||
let title ExplicitImport = squashedAbbreviateImportTitle ieText | ||
title RefineImport = T.intercalate ", " (T.lines ieText) | ||
in InL $ title ieResType | ||
|
||
|
||
-- |For explicit imports: If there are any implicit imports, provide both one | ||
-- code action per import to make that specific import explicit, and one code | ||
-- action to turn them all into explicit imports. For refine imports: If there | ||
|
@@ -176,7 +213,7 @@ codeActionProvider _ ideState _pId (CodeActionParams _ _ TextDocumentIdentifier | |
nfp <- getNormalizedFilePathE _uri | ||
(ImportActionsResult{forCodeActions}, pm) <- runActionE "ImportActions" ideState $ useWithStaleE ImportActions nfp | ||
newRange <- toCurrentRangeE pm range | ||
let relevantCodeActions = filterByRange newRange forCodeActions | ||
let relevantCodeActions = RM.filterByRange newRange forCodeActions | ||
allExplicit = | ||
[InR $ mkCodeAction "Make all imports explicit" (Just $ A.toJSON $ ExplicitAll _uri) | ||
-- We should only provide this code action if there are any code | ||
|
@@ -410,8 +447,6 @@ isExplicitImport _ = False | |
maxColumns :: Int | ||
maxColumns = 120 | ||
|
||
|
||
-- | The title of the command is ideally the minimal explicit import decl, but | ||
-- we don't want to create a really massive code lens (and the decl can be extremely large!). | ||
-- So we abbreviate it to fit a max column size, and indicate how many more items are in the list | ||
-- after the abbreviation | ||
|
@@ -444,6 +479,10 @@ abbreviateImportTitle input = | |
else actualPrefix <> suffixText | ||
in title | ||
|
||
squashedAbbreviateImportTitle :: T.Text -> T.Text | ||
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. How is this different to what we do on line 170? Why are we doing different things? 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 I worked it out: the previous thing has the whole import spec in it. I think it would be better to just create the two bits of the import spec separately when we generate the title, then we can use only the pieces we need. We'd then need to tweak 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. Not only 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. The thing I was suggesting is that in the |
||
squashedAbbreviateImportTitle ieText = abbreviateImportTitle $ (T.intercalate " " . filter (not . T.null) . T.split isSpace . T.dropWhile (/= '(')) ieText | ||
|
||
-- | The title of the command is ideally the minimal explicit import decl, but | ||
-------------------------------------------------------------------------------- | ||
|
||
|
||
|
@@ -462,10 +501,7 @@ filterByImport (ImportDecl{ideclHiding = Just (_, L _ names)}) | |
else Nothing | ||
where importedNames = S.fromList $ map (ieName . unLoc) names | ||
res = flip Map.filter avails $ \a -> | ||
any (`S.member` importedNames) | ||
$ concatMap | ||
getAvailNames | ||
a | ||
any (any (`S.member` importedNames) . getAvailNames) a | ||
allFilteredAvailsNames = S.fromList | ||
$ concatMap getAvailNames | ||
$ mconcat | ||
|
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.
you can use the released version now, just bump the index-state