Skip to content
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

Add sig inlay hints for where clause #4368

Open
wants to merge 30 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
ba2d544
Add sig lens for where clauses
July541 Jun 17, 2022
7bf07ca
Compat
July541 Jun 19, 2022
51ef231
Golden tests
July541 Jun 20, 2022
cd34e8c
Merge branch 'master' into local-binding-type-lens
pepeiborra Jun 24, 2022
c9daf86
Merge branch 'master' into local-binding-type-lens
July541 Dec 10, 2022
232e6ba
Merge remote-tracking branch 'upstream/master' into inlay-hints-local…
jetjinser Jul 27, 2024
f401ff2
Provide where sigs in inlay hints
jetjinser Jul 30, 2024
287f7fa
add offset for FunBind since infix function
jetjinser Jul 31, 2024
228d669
add ghcide InlayHintTests
jetjinser Jul 31, 2024
52ea29f
use liftZonkM in GHC > 9.7.0
jetjinser Jul 31, 2024
e61663d
Fix accidentally broken codeLens
jetjinser Aug 1, 2024
8b6f15e
Add Inlay Hints payload test for type lens
jetjinser Aug 1, 2024
e544187
update testdata schema
jetjinser Aug 1, 2024
5fa456d
Apply suggestions from code review
jetjinser Aug 16, 2024
561d082
refactor: bindToSig just return signature string
jetjinser Aug 17, 2024
b226661
fix: renamed existingSigNames
jetjinser Aug 17, 2024
837ee45
chore: correct schema property `whereInlayHintOn`
jetjinser Aug 17, 2024
96417e0
fix: typo
jetjinser Aug 17, 2024
20e1274
refactor: rewrite where bindings sig as Rules
jetjinser Aug 18, 2024
6d5f4af
Merge remote-tracking branch 'upstream/master' into inlay-hints-local…
jetjinser Aug 21, 2024
efc434b
feat: show local binding instead of just where clause
jetjinser Sep 1, 2024
d065a0a
refactor: correct comment location
jetjinser Sep 1, 2024
2d221d8
ignore things that don't have signatures
jetjinser Sep 1, 2024
953db22
refactor
jetjinser Sep 1, 2024
0df54d2
update local-binding inlayHints config desc
jetjinser Sep 1, 2024
59d56bc
update TypeLenses plugin desc
jetjinser Sep 1, 2024
0e69f46
update TypeLenses test/data dirname
jetjinser Sep 1, 2024
2f27e9a
HsLet compatible
jetjinser Sep 2, 2024
80184f8
Merge remote-tracking branch 'upstream/master' into inlay-hints-local…
jetjinser Sep 2, 2024
94c6841
Merge remote-tracking branch 'upstream/master' into inlay-hints-local…
jetjinser Sep 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
205 changes: 187 additions & 18 deletions ghcide/src/Development/IDE/Plugin/TypeLenses.hs

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions ghcide/test/data/local-sig-lens/Infix.expected.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module Infix where

f :: a
f = undefined
where
g :: p1 -> p -> p1
a `g` b = a
6 changes: 6 additions & 0 deletions ghcide/test/data/local-sig-lens/Infix.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module Infix where

f :: a
f = undefined
where
a `g` b = a
6 changes: 6 additions & 0 deletions ghcide/test/data/local-sig-lens/Inline.expected.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module Inline where

f :: a
f = undefined
where g :: Bool
g = True
5 changes: 5 additions & 0 deletions ghcide/test/data/local-sig-lens/Inline.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module Inline where

f :: a
f = undefined
where g = True
10 changes: 10 additions & 0 deletions ghcide/test/data/local-sig-lens/Nest.expected.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module Nest where

f :: Int
f = g
where
g :: Int
g = h
h :: Int
h = k where k :: Int
k = 3
7 changes: 7 additions & 0 deletions ghcide/test/data/local-sig-lens/Nest.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module Nest where

f :: Int
f = g
where
g = h
h = k where k = 3
13 changes: 13 additions & 0 deletions ghcide/test/data/local-sig-lens/NoLens.expected.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module NoLens where

f :: a
f = undefined
where
g = 3






g :: Int
13 changes: 13 additions & 0 deletions ghcide/test/data/local-sig-lens/NoLens.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module NoLens where

f :: a
f = undefined
where
g = 3






g :: Int
7 changes: 7 additions & 0 deletions ghcide/test/data/local-sig-lens/Operator.expected.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module Operator where

f :: a
f = undefined
where
g :: (a -> b) -> a -> b
g = ($)
6 changes: 6 additions & 0 deletions ghcide/test/data/local-sig-lens/Operator.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module Operator where

f :: a
f = undefined
where
g = ($)
9 changes: 9 additions & 0 deletions ghcide/test/data/local-sig-lens/Qualified.expected.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module Qualified where

import qualified Data.Map as Map

f :: a
f = undefined
where
g :: Map.Map Bool Char
g = Map.singleton True 'c'
8 changes: 8 additions & 0 deletions ghcide/test/data/local-sig-lens/Qualified.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module Qualified where

import qualified Data.Map as Map

f :: a
f = undefined
where
g = Map.singleton True 'c'
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{-# LANGUAGE ExplicitForAll #-}
module ScopedTypeVariables where

f :: forall a b. a -> b -> (a, b)
f aa bb = (aa, ida bb)
where
ida :: b -> b
ida = id
7 changes: 7 additions & 0 deletions ghcide/test/data/local-sig-lens/ScopedTypeVariables.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{-# LANGUAGE ExplicitForAll #-}
module ScopedTypeVariables where

f :: forall a b. a -> b -> (a, b)
f aa bb = (aa, ida bb)
where
ida = id
7 changes: 7 additions & 0 deletions ghcide/test/data/local-sig-lens/Simple.expected.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module Simple where

f :: a
f = undefined
where
g :: Bool
g = True
6 changes: 6 additions & 0 deletions ghcide/test/data/local-sig-lens/Simple.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module Simple where

f :: a
f = undefined
where
g = True
8 changes: 8 additions & 0 deletions ghcide/test/data/local-sig-lens/Tuple.expected.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module Typle where

f :: a
f = undefined
where
g :: Integer
h :: Bool
(g, h) = (1, True)
6 changes: 6 additions & 0 deletions ghcide/test/data/local-sig-lens/Tuple.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module Typle where

f :: a
f = undefined
where
(g, h) = (1, True)
7 changes: 7 additions & 0 deletions ghcide/test/data/local-sig-lens/Typeclass.expected.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module Typeclass where

f :: a
f = undefined
where
g :: Num a => a -> a -> a
g a b = a + b
6 changes: 6 additions & 0 deletions ghcide/test/data/local-sig-lens/Typeclass.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module Typeclass where

f :: a
f = undefined
where
g a b = a + b
179 changes: 179 additions & 0 deletions ghcide/test/exe/InlayHintTests.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
{-# LANGUAGE ExplicitNamespaces #-}

module InlayHintTests (tests) where

import Config (mkIdeTestFs, testWithDummyPlugin,
testWithDummyPluginEmpty)
import Control.Monad (void)
import Control.Monad.IO.Class (MonadIO (liftIO))
import qualified Data.Aeson as A
import Data.Maybe (mapMaybe)
import qualified Data.Text as T
import Language.LSP.Protocol.Types (InlayHint (..),
Position (Position),
Range (Range, _end, _start),
TextDocumentIdentifier (TextDocumentIdentifier),
TextEdit (TextEdit, _newText, _range),
UInt,
VersionedTextDocumentIdentifier (_uri),
type (|?) (..))
import Language.LSP.Test (applyEdit, createDoc,
documentContents, getInlayHints,
openDoc, setConfigSection)
import Test.Hls (Assertion, Session, expectFail,
waitForTypecheck)
import Test.Hls.FileSystem (copyDir)
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit ((@=?), (@?=))

tests :: TestTree
tests = testGroup "inlay hints"
[ whereInlayHintsTests
]

whereInlayHintsTests :: TestTree
whereInlayHintsTests = testGroup "add signature for where clauses"
[ testWithDummyPluginEmpty "No where inlay hints if disabled" $ do
let content = T.unlines
[ "module Sigs where"
, "f :: b"
, "f = undefined"
, " where"
, " g = True"
]
range = Range { _start = Position 4 0
, _end = Position 4 1000
}
doc <- createDoc "Sigs.hs" "haskell" content
setConfigSection "haskell" (createConfig False)
inlayHints <- getInlayHints doc range
liftIO $ length inlayHints @?= 0
, testGroup "apply EditText"
[ editTest "Simple"
, editTest "Tuple"
, editTest "Inline"
, editTest "Infix"
, editTest "Operator"
, expectFail $ editTest "ScopedTypeVariables"
, editTest "Nest"
, editTest "NoLens"
, expectFail $ editTest "Typeclass"
, editTest "Qualified"
]
, testGroup "apply EditText"
[ hintTest "Simple" $ (@=?)
[defInlayHint { _position = Position 5 9
, _label = InL ":: Bool"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should display the hints on the previous line rather than right after the binding. i.e. make the hint look like the edit currently does.

Advantages:

  1. It makes the effect of applying the hint align with how the hint looks, which is I think what we're supposed to do.
  2. It makes the situation less bad if the type is very long. That might already be annoying, but it could be even more annoying if it's right in the middle of something you're trying to read. Maybe we should look at what other language servers do?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think inlay hints can do this.
I found this issue: microsoft/language-server-protocol#1821.

AFAIK, standard inlay hints cannot be in virtual line.
It's more like a fixed position code lens?

, _textEdits = Just [mkTextEdit 5 8 "g :: Bool\n "]
}]
, hintTest "Tuple" $ (@=?)
[ defInlayHint { _position = Position 5 10
, _label = InL ":: Integer"
, _textEdits = Just [mkTextEdit 5 8 "g :: Integer\n "]
}
, defInlayHint { _position = Position 5 13
, _label = InL ":: Bool"
, _textEdits = Just [mkTextEdit 5 8 "h :: Bool\n "]
}
]
, hintTest "Inline" $ (@=?)
[defInlayHint { _position = Position 4 11
, _label = InL ":: Bool"
, _textEdits = Just [mkTextEdit 4 10 "g :: Bool\n "]
}]
, hintTest "Infix" $ (@=?)
[defInlayHint { _position = Position 5 13
, _label = InL ":: p1 -> p -> p1"
, _textEdits = Just [mkTextEdit 5 8 "g :: p1 -> p -> p1\n "]
}]
, hintTest "Operator" $ (@=?)
[defInlayHint { _position = Position 5 9
, _label = InL ":: (a -> b) -> a -> b"
, _textEdits = Just [mkTextEdit 5 8 "g :: (a -> b) -> a -> b\n "]
}]
, hintTest "Nest" $ (@=?)
[ defInlayHint { _position = Position 6 9
, _label = InL ":: Int"
, _textEdits = Just [mkTextEdit 6 8 "h :: Int\n "]
}
, defInlayHint { _position = Position 5 9
, _label = InL ":: Int"
, _textEdits = Just [mkTextEdit 5 8 "g :: Int\n "]
}
, defInlayHint { _position = Position 6 21
, _label = InL ":: Int"
, _textEdits = Just [mkTextEdit 6 20 "k :: Int\n "]
}
]
, hintTest "NoLens" $ (@=?) []
, hintTest "Qualified" $ (@=?)
[ defInlayHint { _position = Position 7 10
, _label = InL ":: Map.Map Bool Char"
, _textEdits = Just [mkTextEdit 7 9 "g :: Map.Map Bool Char\n "]
}
]
]
]

editTest :: String -> TestTree
editTest file =
testWithDummyPlugin (file <> " (InlayHint EditText)") (mkIdeTestFs [copyDir "local-sig-lens"]) $ do
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the directory should be "local-sig-hint", perhaps?

doc <- openDoc (file ++ ".hs") "haskell"
executeAllHints doc globalRange
real <- documentContents doc
expectedDoc <- openDoc (file ++ ".expected.hs") "haskell"
expected <- documentContents expectedDoc
liftIO $ real @?= expected

hintTest :: String -> ([InlayHint] -> Assertion) -> TestTree
hintTest file assert =
testWithDummyPlugin (file <> " (InlayHint)") (mkIdeTestFs [copyDir "local-sig-lens"]) $ do
doc <- openDoc (file ++ ".hs") "haskell"
hints <- getInlayHints doc globalRange
liftIO $ assert hints


createConfig :: Bool -> A.Value
createConfig on =
A.object [ "plugin"
A..= A.object [ "ghcide-type-lenses"
A..= A.object [ "config"
A..= A.object [ "whereInlayHintOn" A..= A.Bool on ]]]]


executeAllHints :: TextDocumentIdentifier -> Range -> Session ()
executeAllHints doc range = do
void $ waitForTypecheck doc
hints <- getInlayHints doc range
let edits = concat $ mapMaybe _textEdits hints
case edits of
[] -> pure ()
edit : _ -> do
newDoc <- applyEdit doc edit
executeAllHints (TextDocumentIdentifier $ _uri newDoc) range

defInlayHint :: InlayHint
defInlayHint =
InlayHint { _position = Position 0 0
, _label = InL ""
, _kind = Nothing
, _textEdits = Nothing
, _tooltip = Nothing
, _paddingLeft = Just True
, _paddingRight = Nothing
, _data_ = Nothing
}

mkTextEdit :: UInt -> UInt -> T.Text -> TextEdit
mkTextEdit x y text =
TextEdit { _range = pointRange x y
, _newText = text
}

pointRange :: UInt -> UInt -> Range
pointRange x y = Range (Position x y) (Position x y)

globalRange :: Range
globalRange = Range { _start = Position 0 0
, _end = Position 1000 0
}
2 changes: 2 additions & 0 deletions ghcide/test/exe/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import HaddockTests
import HighlightTests
import IfaceTests
import InitializeResponseTests
import InlayHintTests
import LogType ()
import NonLspCommandLine
import OpenCloseTest
Expand Down Expand Up @@ -99,4 +100,5 @@ main = do
, GarbageCollectionTests.tests
, HieDbRetry.tests
, ExceptionTests.tests
, InlayHintTests.tests
]
1 change: 1 addition & 0 deletions haskell-language-server.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -2180,6 +2180,7 @@ test-suite ghcide-tests
THTests
UnitTests
WatchedFileTests
InlayHintTests

-- Tests that have been pulled out of the main file
default-extensions:
Expand Down
6 changes: 4 additions & 2 deletions test/testdata/schema/ghc94/default-config.golden.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,12 @@
"symbolsOn": true
},
"ghcide-type-lenses": {
"codeLensOn": true,
"config": {
"mode": "always"
"mode": "always",
"whereInlayHintOn": true
},
"globalOn": true
"inlayHintsOn": true
},
"hlint": {
"codeActionsOn": true,
Expand Down
Loading
Loading