Skip to content

Commit 4c4b25e

Browse files
author
izuzu
committed
Merge branch 'master' into rename-qualified-alias
2 parents cf4b935 + 65173d4 commit 4c4b25e

3 files changed

Lines changed: 93 additions & 12 deletions

File tree

plugins/hls-rename-plugin/src/Ide/Plugin/Rename.hs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,16 +107,22 @@ prepareRenameProvider state _pluginId (PrepareRenameParams (TextDocumentIdentifi
107107
Just (lspRange, _importAlias) ->
108108
pure $ InL $ PrepareRenameResult $ InL $ lspRange
109109
Nothing -> do
110+
HAR{hieAst} <- handleGetHieAst state nfp
111+
let spansWithNamesUnderCursor =
112+
[ srcSpan
113+
| (names, srcSpan) <- getNamesSpansAtPoint' hieAst pos
114+
, not (null names)]
110115
-- When this handler says that rename is invalid, VSCode shows "The element can't be renamed"
111116
-- and doesn't even allow you to create full rename request.
112117
-- This handler deliberately approximates "things that definitely can't be renamed"
113-
-- to mean "there is no Name at given position".
118+
-- to mean "there is no Name at given position" (in which case
119+
-- `spansWithNamesUnderCursor` would be empty).
114120
--
115121
-- In particular it allows some cases through (e.g. cross-module renames),
116122
-- so that the full rename handler can give more informative error about them.
117-
namesUnderCursor <- getNamesAtPos state nfp lspPos
118-
let renameValid = not $ null namesUnderCursor
119-
pure $ InL $ PrepareRenameResult $ InR $ InR $ PrepareRenameDefaultBehavior renameValid
123+
pure $ case spansWithNamesUnderCursor of
124+
[] -> InR Null
125+
srcSpan : _ -> InL $ PrepareRenameResult $ InL (realSrcSpanToRange srcSpan)
120126

121127
renameProvider :: PluginMethodHandler IdeState Method_TextDocumentRename
122128
renameProvider state pluginId (RenameParams _prog (TextDocumentIdentifier uri) lspPos newNameText) = do
@@ -350,6 +356,12 @@ getNamesAtPoint' :: HieASTs a -> Position -> [Name]
350356
getNamesAtPoint' hf pos =
351357
concat $ pointCommand hf pos (rights . M.keys . getNodeIds)
352358

359+
-- | A variant of `getNamesAtPoint'` that also returns source spans.
360+
getNamesSpansAtPoint' :: HieASTs a -> Position -> [([Name], RealSrcSpan)]
361+
getNamesSpansAtPoint' hf pos =
362+
pointCommand hf pos $
363+
\astNode -> (rights . M.keys . getNodeIds $ astNode, nodeSpan astNode)
364+
353365
locToUri :: Location -> Uri
354366
locToUri (Location uri _) = uri
355367

plugins/hls-rename-plugin/test/Main.hs

Lines changed: 63 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@
44

55
module Main (main) where
66

7-
import Control.Lens ((^.))
8-
import Data.Aeson
9-
import Data.Functor (void)
10-
import qualified Data.Map as M
11-
import Data.Text (Text, isInfixOf, pack, unpack)
7+
import Control.Lens ((^.))
8+
import Data.Aeson (KeyValue ((.=)))
9+
import Data.Functor (void)
10+
import qualified Data.Map as M
11+
import Data.Text (Text, isInfixOf, pack, unpack)
1212
import Ide.Plugin.Config
13-
import qualified Ide.Plugin.Rename as Rename
14-
import qualified Language.LSP.Protocol.Lens as L
13+
import qualified Ide.Plugin.Rename as Rename
14+
import qualified Language.LSP.Protocol.Lens as L
15+
import Language.LSP.Protocol.Types (Null (Null))
1516
import System.FilePath
1617
import Test.Hls
1718

@@ -23,10 +24,54 @@ renamePlugin = mkPluginTestDescriptor Rename.descriptor "rename"
2324

2425
tests :: TestTree
2526
tests = testGroup "Rename"
26-
[ renameTests
27+
[ prepareRenameTests
28+
, renameTests
2729
, moduleNameTests
2830
]
2931

32+
prepareRenameTests :: TestTree
33+
prepareRenameTests = testGroup "PrepareRename"
34+
[ testCase "Module name (not yet renameable)" $ runRenameSession "" $ do
35+
doc <- openDoc "PrepareRename.hs" "haskell"
36+
void waitForBuildQueue
37+
result <- prepareRename doc (Position 0 9)
38+
liftIO $ result @?= InR Null
39+
40+
, testCase "Function name" $ runRenameSession "" $ do
41+
doc <- openDoc "PrepareRename.hs" "haskell"
42+
void waitForBuildQueue
43+
result <- prepareRename doc (Position 8 1)
44+
liftIO $ result @?=
45+
InL (PrepareRenameResult (InL (Range (Position 8 0) (Position 8 3))))
46+
47+
, testCase "Imported function name" $ runRenameSession "" $ do
48+
doc <- openDoc "PrepareRename.hs" "haskell"
49+
void waitForBuildQueue
50+
result <- prepareRename doc (Position 10 16)
51+
liftIO $ result @?=
52+
InL (PrepareRenameResult (InL (Range (Position 10 14) (Position 10 19))))
53+
54+
, testCase "Non-renameable position" $ runRenameSession "" $ do
55+
doc <- openDoc "PrepareRename.hs" "haskell"
56+
void waitForBuildQueue
57+
result <- prepareRename doc (Position 6 23)
58+
liftIO $ result @?= InR Null
59+
60+
, testCase "Operator" $ runRenameSession "" $ do
61+
doc <- openDoc "PrepareRename.hs" "haskell"
62+
void waitForBuildQueue
63+
result <- prepareRename doc (Position 10 7)
64+
liftIO $ result @?=
65+
InL (PrepareRenameResult (InL (Range (Position 10 6) (Position 10 9))))
66+
67+
, testCase "Built-in operator" $ runRenameSession "" $ do
68+
doc <- openDoc "PrepareRename.hs" "haskell"
69+
void waitForBuildQueue
70+
result <- prepareRename doc (Position 13 7)
71+
liftIO $ result @?=
72+
InL (PrepareRenameResult (InL (Range (Position 13 7) (Position 13 8))))
73+
]
74+
3075
renameTests :: TestTree
3176
renameTests = testGroup "Identifier"
3277
[ goldenWithRename "Data constructor" "DataConstructor" $ \doc ->
@@ -195,6 +240,16 @@ goldenWithRename title path act =
195240
goldenWithHaskellDoc (def { plugins = M.fromList [("rename", def { plcConfig = "crossModule" .= True })] })
196241
renamePlugin title testDataDir path "expected" "hs" act
197242

243+
-- NOTE: This should eventually be moved upstream to lsp-test (see
244+
-- https://github.com/haskell/lsp/issues/636).
245+
prepareRename :: TextDocumentIdentifier -> Position -> Session (PrepareRenameResult |? Null)
246+
prepareRename doc pos = do
247+
let params = PrepareRenameParams doc pos Nothing
248+
rsp <- request SMethod_TextDocumentPrepareRename params
249+
case rsp ^. L.result of
250+
Left rspError -> liftIO $ assertFailure $ "prepareRename failed: " <> show rspError
251+
Right rspResult -> pure rspResult
252+
198253
renameExpectError :: TResponseError Method_TextDocumentRename -> TextDocumentIdentifier -> Position -> Text -> Session ()
199254
renameExpectError expectedError doc pos newName = do
200255
let params = RenameParams Nothing doc pos newName
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
module PrepareRename where
2+
3+
import qualified Foo as F
4+
5+
main :: IO Int
6+
main = do
7+
x <- return $ foo 42
8+
return (foo x)
9+
foo, bar :: Int -> Int
10+
foo x = x + 1
11+
bar = (+) 1 . F.foo . foo
12+
13+
baz :: a -> [a]
14+
baz = (: [])

0 commit comments

Comments
 (0)