forked from haskell/haskell-language-server
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathRename.hs
160 lines (138 loc) · 5.93 KB
/
Rename.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE NamedFieldPuns #-}
module Ide.Plugin.Rename (descriptor) where
import Control.Monad
import Control.Monad.IO.Class (MonadIO (liftIO))
import Control.Monad.Trans.Class
import Control.Monad.Trans.Except
import Data.Containers.ListUtils
import Data.Generics
import Data.List.Extra hiding (nubOrd)
import qualified Data.Map as M
import Data.Maybe
import qualified Data.Text as T
import Development.IDE hiding (pluginHandlers)
import Development.IDE.Core.PositionMapping
import Development.IDE.Core.Shake
import Development.IDE.GHC.Compat
import Development.IDE.Spans.AtPoint
#if MIN_VERSION_ghc(9,0,1)
import GHC.Types.Name
#else
import Name
#endif
import HieDb.Query
import Ide.Plugin.Config
import Ide.Plugin.Retrie hiding (descriptor)
import Ide.PluginUtils
import Ide.Types
import Language.Haskell.GHC.ExactPrint
import Language.LSP.Server
import Language.LSP.Types
descriptor :: PluginId -> PluginDescriptor IdeState
descriptor pluginId = (defaultPluginDescriptor pluginId) {
pluginHandlers = mkPluginHandler STextDocumentRename renameProvider
}
renameProvider :: PluginMethodHandler IdeState TextDocumentRename
renameProvider state pluginId (RenameParams (TextDocumentIdentifier uri) pos _prog newNameText) =
response $ do
nfp <- safeUriToNfp uri
oldName <- getNameAtPos state nfp pos
workspaceRefs <- refsAtName state nfp oldName
let filesRefs = groupOn locToUri workspaceRefs
getFileEdits = ap (getSrcEdits state . renameModRefs newNameText) (locToUri . head)
fileEdits <- mapM getFileEdits filesRefs
pure $ foldl1 (<>) fileEdits
-------------------------------------------------------------------------------
-- Source renaming
-- | Compute a `WorkspaceEdit` by applying a given function to the `ParsedModule` for a given `Uri`.
getSrcEdits ::
(MonadLsp config m) =>
IdeState ->
#if MIN_VERSION_ghc(9,0,1)
(HsModule -> HsModule) ->
#else
(HsModule GhcPs -> HsModule GhcPs) ->
#endif
Uri ->
ExceptT String m WorkspaceEdit
getSrcEdits state updateMod uri = do
ccs <- lift getClientCapabilities
nfp <- safeUriToNfp uri
ParsedModule{pm_parsed_source = ps, pm_annotations = apiAnns} <-
handleMaybeM "Error: could not get parsed source" $ liftIO $ runAction
"Rename.GetParsedModuleWithComments"
state
(use GetParsedModuleWithComments nfp)
let anns = relativiseApiAnns ps apiAnns
src = T.pack $ exactPrint ps anns
res = T.pack $ exactPrint (updateMod <$> ps) anns
pure $ diffText ccs (uri, src) res IncludeDeletions
-- | Replace a name at every given `Location` (in a given `HsModule`) with a given new name.
renameModRefs ::
T.Text ->
[Location] ->
#if MIN_VERSION_ghc(9,0,1)
HsModule
-> HsModule
#else
HsModule GhcPs
-> HsModule GhcPs
#endif
renameModRefs newNameText refs = everywhere $ mkT replace
where
replace :: Located RdrName -> Located RdrName
replace (L srcSpan oldRdrName)
| isRef srcSpan = L srcSpan $ newRdrName oldRdrName
replace lOldRdrName = lOldRdrName
newRdrName :: RdrName -> RdrName
newRdrName oldRdrName = case oldRdrName of
Qual modName _ -> Qual modName newOccName
_ -> Unqual newOccName
newOccName = mkTcOcc $ T.unpack newNameText
isRef :: SrcSpan -> Bool
isRef = (`elem` refs) . fromJust . srcSpanToLocation
-------------------------------------------------------------------------------
-- Reference finding
-- | Note: We only find exact name occurences (i.e. type reference "depth" is 0).
refsAtName :: IdeState -> NormalizedFilePath -> Name -> ExceptT [Char] (LspT Config IO) [Location]
refsAtName state nfp name = do
ShakeExtras{hiedb} <- liftIO $ runAction "Rename.HieDb" state getShakeExtras
ast <- safeGetHieAst state nfp
astRefs <- handleMaybe "Error: Could not get name AST references" $ getNameAstLocations name ast
dbRefs <- case nameModule_maybe name of
Nothing -> pure []
Just mod -> liftIO $ mapMaybe rowToLoc <$>
findReferences
hiedb
True
(nameOccName name)
(Just $ moduleName mod)
(Just $ moduleUnitId mod)
[fromNormalizedFilePath nfp]
pure $ nubOrd $ astRefs ++ dbRefs
getNameAstLocations :: Name -> (HieAstResult, PositionMapping) -> Maybe [Location]
getNameAstLocations name (HAR _ _ rm _ _, mapping) =
mapMaybe (toCurrentLocation mapping . realSrcSpanToLocation . fst) <$> M.lookup (Right name) rm
-------------------------------------------------------------------------------
-- Util
getNameAtPos :: IdeState -> NormalizedFilePath -> Position -> ExceptT String (LspT Config IO) Name
getNameAtPos state nfp pos = do
(HAR{hieAst}, mapping) <- safeGetHieAst state nfp
handleMaybe "Error: could not find name at position" $ listToMaybe $
getAstNamesAtPoint hieAst pos mapping
nfpToUri :: NormalizedFilePath -> Uri
nfpToUri = filePathToUri . fromNormalizedFilePath
safeUriToNfp :: (Monad m) => Uri -> ExceptT String m NormalizedFilePath
safeUriToNfp = handleMaybe "Error: Could not get uri" . fmap toNormalizedFilePath . uriToFilePath
safeGetHieAst ::
MonadIO m =>
IdeState ->
NormalizedFilePath ->
ExceptT String m (HieAstResult, PositionMapping)
safeGetHieAst state = handleMaybeM "Error: Could not get AST" . liftIO .
runAction "Rename.GetHieAst" state . useWithStale GetHieAst
locToUri :: Location -> Uri
locToUri (Location uri _) = uri