Skip to content

Commit 8c22b84

Browse files
authored
Fix other file goto definition (#3725)
* Add gotoDefinition other file tests * Use correct position mapping in getDefinition
1 parent f20562f commit 8c22b84

File tree

5 files changed

+80
-10
lines changed

5 files changed

+80
-10
lines changed

ghcide/src/Development/IDE/Core/Actions.hs

+37-6
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ module Development.IDE.Core.Actions
1313
, lookupMod
1414
) where
1515

16+
import Control.Monad.Extra (mapMaybeM)
1617
import Control.Monad.Reader
1718
import Control.Monad.Trans.Maybe
1819
import qualified Data.HashMap.Strict as HM
@@ -31,7 +32,9 @@ import Development.IDE.Types.HscEnvEq (hscEnv)
3132
import Development.IDE.Types.Location
3233
import qualified HieDb
3334
import Language.LSP.Protocol.Types (DocumentHighlight (..),
34-
SymbolInformation (..))
35+
SymbolInformation (..),
36+
normalizedFilePathToUri,
37+
uriToNormalizedFilePath)
3538

3639

3740
-- | Eventually this will lookup/generate URIs for files in dependencies, but not in the
@@ -66,10 +69,36 @@ getAtPoint file pos = runMaybeT $ do
6669
!pos' <- MaybeT (return $ fromCurrentPosition mapping pos)
6770
MaybeT $ pure $ first (toCurrentRange mapping =<<) <$> AtPoint.atPoint opts hf dkMap env pos'
6871

69-
toCurrentLocations :: PositionMapping -> [Location] -> [Location]
70-
toCurrentLocations mapping = mapMaybe go
72+
-- | For each Loacation, determine if we have the PositionMapping
73+
-- for the correct file. If not, get the correct position mapping
74+
-- and then apply the position mapping to the location.
75+
toCurrentLocations
76+
:: PositionMapping
77+
-> NormalizedFilePath
78+
-> [Location]
79+
-> IdeAction [Location]
80+
toCurrentLocations mapping file = mapMaybeM go
7181
where
72-
go (Location uri range) = Location uri <$> toCurrentRange mapping range
82+
go :: Location -> IdeAction (Maybe Location)
83+
go (Location uri range) =
84+
-- The Location we are going to might be in a different
85+
-- file than the one we are calling gotoDefinition from.
86+
-- So we check that the location file matches the file
87+
-- we are in.
88+
if nUri == normalizedFilePathToUri file
89+
-- The Location matches the file, so use the PositionMapping
90+
-- we have.
91+
then pure $ Location uri <$> toCurrentRange mapping range
92+
-- The Location does not match the file, so get the correct
93+
-- PositionMapping and use that instead.
94+
else do
95+
otherLocationMapping <- fmap (fmap snd) $ runMaybeT $ do
96+
otherLocationFile <- MaybeT $ pure $ uriToNormalizedFilePath nUri
97+
useE GetHieAst otherLocationFile
98+
pure $ Location uri <$> (flip toCurrentRange range =<< otherLocationMapping)
99+
where
100+
nUri :: NormalizedUri
101+
nUri = toNormalizedUri uri
73102

74103
-- | useE is useful to implement functions that aren’t rules but need shortcircuiting
75104
-- e.g. getDefinition.
@@ -90,15 +119,17 @@ getDefinition file pos = runMaybeT $ do
90119
(HAR _ hf _ _ _, mapping) <- useE GetHieAst file
91120
(ImportMap imports, _) <- useE GetImportMap file
92121
!pos' <- MaybeT (pure $ fromCurrentPosition mapping pos)
93-
toCurrentLocations mapping <$> AtPoint.gotoDefinition withHieDb (lookupMod hiedbWriter) opts imports hf pos'
122+
locations <- AtPoint.gotoDefinition withHieDb (lookupMod hiedbWriter) opts imports hf pos'
123+
MaybeT $ Just <$> toCurrentLocations mapping file locations
94124

95125
getTypeDefinition :: NormalizedFilePath -> Position -> IdeAction (Maybe [Location])
96126
getTypeDefinition file pos = runMaybeT $ do
97127
ide@ShakeExtras{ withHieDb, hiedbWriter } <- ask
98128
opts <- liftIO $ getIdeOptionsIO ide
99129
(hf, mapping) <- useE GetHieAst file
100130
!pos' <- MaybeT (return $ fromCurrentPosition mapping pos)
101-
toCurrentLocations mapping <$> AtPoint.gotoTypeDefinition withHieDb (lookupMod hiedbWriter) opts hf pos'
131+
locations <- AtPoint.gotoTypeDefinition withHieDb (lookupMod hiedbWriter) opts hf pos'
132+
MaybeT $ Just <$> toCurrentLocations mapping file locations
102133

103134
highlightAtPoint :: NormalizedFilePath -> Position -> IdeAction (Maybe [DocumentHighlight])
104135
highlightAtPoint file pos = runMaybeT $ do

test/functional/Definition.hs

+29-4
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,43 @@ import Test.Hls
77
import Test.Hls.Command
88

99
tests :: TestTree
10-
tests = testGroup "definitions" [
10+
tests = testGroup "definitions" [symbolTests, moduleTests]
1111

12-
ignoreTestBecause "Broken: file:///Users/jwindsor/src/haskell-language-server/test/testdata/References.hs" $
13-
testCase "goto's symbols" $ runSession hlsCommand fullCaps "test/testdata" $ do
12+
symbolTests :: TestTree
13+
symbolTests = testGroup "gotoDefinition on symbols"
14+
-- gotoDefinition where the definition is in the same file
15+
[ testCase "gotoDefinition in this file" $ runSession hlsCommand fullCaps "test/testdata" $ do
1416
doc <- openDoc "References.hs" "haskell"
1517
defs <- getDefinitions doc (Position 7 8)
1618
let expRange = Range (Position 4 0) (Position 4 3)
1719
liftIO $ defs @?= InL (Definition (InR [Location (doc ^. uri) expRange]))
1820

21+
-- gotoDefinition where the definition is in a different file
22+
, testCase "gotoDefinition in other file" $ runSession hlsCommand fullCaps "test/testdata/definition" $ do
23+
doc <- openDoc "Foo.hs" "haskell"
24+
defs <- getDefinitions doc (Position 4 11)
25+
let expRange = Range (Position 2 0) (Position 2 1)
26+
liftIO $ do
27+
fp <- canonicalizePath "test/testdata/definition/Bar.hs"
28+
defs @?= InL (Definition (InR [Location (filePathToUri fp) expRange]))
29+
30+
-- gotoDefinition where the definition is in a different file and the
31+
-- definition in the other file is on a line number that is greater
32+
-- than the number of lines in the file we are requesting from
33+
, testCase "gotoDefinition in other file past lines in this file" $ runSession hlsCommand fullCaps "test/testdata/definition" $ do
34+
doc <- openDoc "Foo.hs" "haskell"
35+
defs <- getDefinitions doc (Position 5 13)
36+
let expRange = Range (Position 8 0) (Position 8 1)
37+
liftIO $ do
38+
fp <- canonicalizePath "test/testdata/definition/Bar.hs"
39+
defs @?= InL (Definition (InR [Location (filePathToUri fp) expRange]))
40+
]
41+
1942
-- -----------------------------------
2043

21-
, ignoreTestBecause "Broken: file:///Users/jwindsor/src/haskell-language-server/test/testdata/Bar.hs" $
44+
moduleTests :: TestTree
45+
moduleTests = testGroup "gotoDefinition on modules"
46+
[ ignoreTestBecause "Broken: file:///Users/jwindsor/src/haskell-language-server/test/testdata/Bar.hs" $
2247
testCase "goto's imported modules" $ runSession hlsCommand fullCaps "test/testdata/definition" $ do
2348
doc <- openDoc "Foo.hs" "haskell"
2449
defs <- getDefinitions doc (Position 2 8)

test/testdata/definition/Bar.hs

+6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
11
module Bar where
22

33
a = 42
4+
5+
-- These blank lines are here
6+
-- to ensure that b is defined
7+
-- on a line number larger than
8+
-- the number of lines in Foo.hs.
9+
b = 43

test/testdata/definition/Foo.hs

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
module Foo (module Bar) where
22

33
import Bar
4+
5+
fortyTwo = a
6+
fortyThree = b

test/testdata/definition/hie.yaml

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
cradle:
2+
direct:
3+
arguments:
4+
- "Foo"
5+
- "Bar"

0 commit comments

Comments
 (0)