44{-# LANGUAGE OverloadedLabels #-}
55{-# LANGUAGE OverloadedStrings #-}
66{-# LANGUAGE RecordWildCards #-}
7- {-# OPTIONS_GHC -Wno-orphans #-}
7+ {-# OPTIONS_GHC -Wno-orphans #-}
88{-# LANGUAGE LambdaCase #-}
99
1010module Ide.Plugin.Rename (descriptor , Log ) where
1111
12+ import Control.Applicative ((<|>) )
1213import Control.Lens ((^.) )
1314import Control.Monad
1415import Control.Monad.Except (ExceptT , throwError )
1516import Control.Monad.IO.Class (MonadIO , liftIO )
1617import Control.Monad.Trans.Class (lift )
18+ import Control.Monad.Trans.Except (mapExceptT )
19+ import Control.Monad.Trans.Maybe (hoistMaybe ,
20+ maybeToExceptT )
1721import Data.Either (rights )
18- import Data.Foldable (fold )
22+ import Data.Foldable (fold , minimumBy )
1923import Data.Generics
2024import Data.Hashable
2125import Data.HashSet (HashSet )
2226import qualified Data.HashSet as HS
27+ import Data.List (foldl' )
2328import Data.List.NonEmpty (NonEmpty ((:|) ),
2429 groupWith )
30+ import qualified Data.List.NonEmpty as NE
2531import qualified Data.Map as M
32+ import qualified Data.Map.Strict as Map
2633import Data.Maybe
2734import Data.Mod.Word
35+ import Data.Ord (comparing )
2836import qualified Data.Text as T
29- import Development.IDE (Recorder , WithPriority ,
30- usePropertyAction )
3137import Development.IDE.Core.FileStore (getVersionedTextDoc )
3238import Development.IDE.Core.PluginUtils
39+ import Development.IDE.Core.Rules (usePropertyAction )
3340import Development.IDE.Core.RuleTypes
3441import Development.IDE.Core.Service hiding (Log )
3542import Development.IDE.Core.Shake hiding (Log )
@@ -38,8 +45,10 @@ import Development.IDE.GHC.Compat.ExactPrint
3845import Development.IDE.GHC.Error
3946import Development.IDE.GHC.ExactPrint hiding (Log )
4047import qualified Development.IDE.GHC.ExactPrint as E
48+ import Development.IDE.GHC.Util (evalGhcEnv )
4149import Development.IDE.Plugin.CodeAction
4250import Development.IDE.Spans.AtPoint
51+ import Development.IDE.Types.HscEnvEq (HscEnvEq (hscEnv ))
4352import Development.IDE.Types.Location
4453import GHC.Iface.Ext.Types (HieAST (.. ),
4554 HieASTs (.. ),
@@ -49,11 +58,11 @@ import GHC.Iface.Ext.Utils (generateReferencesMap)
4958import HieDb ((:.) (.. ))
5059import HieDb.Query
5160import HieDb.Types (RefRow (refIsGenerated ))
52- import Ide.Logger (Pretty (.. ),
53- cmapWithPrio )
61+ import Ide.Logger
5462import Ide.Plugin.Error
5563import Ide.Plugin.Properties
5664import qualified Ide.Plugin.Rename.ModuleName as ModuleName
65+ import qualified Ide.Plugin.Rename.ModuleRename as ModuleRename
5766import Ide.PluginUtils
5867import Ide.Types
5968import qualified Language.LSP.Protocol.Lens as L
@@ -65,11 +74,13 @@ instance Hashable (Mod a) where hash n = hash (unMod n)
6574data Log
6675 = LogExactPrint E. Log
6776 | LogModuleName ModuleName. Log
77+ | LogModuleRename ModuleRename. Log
6878
6979instance Pretty Log where
7080 pretty = \ case
7181 LogExactPrint msg -> pretty msg
7282 LogModuleName msg -> pretty msg
83+ LogModuleRename msg -> pretty msg
7384
7485descriptor :: Recorder (WithPriority Log ) -> PluginId -> PluginDescriptor IdeState
7586descriptor recorder pluginId = mkExactprintPluginDescriptor exactPrintRecorder $
@@ -78,6 +89,7 @@ descriptor recorder pluginId = mkExactprintPluginDescriptor exactPrintRecorder $
7889 [ mkPluginHandler SMethod_TextDocumentRename renameProvider
7990 , mkPluginHandler SMethod_TextDocumentPrepareRename prepareRenameProvider
8091 , mkPluginHandler SMethod_TextDocumentCodeLens (ModuleName. codeLens moduleNameRecorder)
92+ , mkPluginHandler SMethod_WorkspaceWillRenameFiles (renameModuleProvider recorder)
8193 ]
8294 , pluginCommands = [PluginCommand ModuleName. updateModuleNameCommand " Set name of module to match with file path" (ModuleName. command moduleNameRecorder)]
8395 , pluginConfigDescriptor = defaultConfigDescriptor
@@ -107,6 +119,35 @@ prepareRenameProvider state _pluginId (PrepareRenameParams (TextDocumentIdentifi
107119 [] -> InR Null
108120 srcSpan : _ -> InL $ PrepareRenameResult $ InL (realSrcSpanToRange srcSpan)
109121
122+ renameModuleProvider :: Recorder (WithPriority Log )-> PluginMethodHandler IdeState Method_WorkspaceWillRenameFiles
123+ renameModuleProvider recorder state _ (RenameFilesParams renames) = do
124+ renameResults <- mapM renameFile renames
125+ pure $ InL $ foldl' combineTextEdits (WorkspaceEdit mempty mempty mempty ) $ catMaybes renameResults
126+ where
127+ recorder' = cmapWithPrio LogModuleRename recorder
128+
129+ renameFile (FileRename oldUri newUri) = do
130+ oldNfp <- fmap toNormalizedFilePath $ uriToFilePathE $ Uri oldUri
131+ newNfp <- fmap toNormalizedFilePath $ uriToFilePathE $ Uri newUri
132+ pm <- runActionE " Rename.GetParsedModule" state
133+ (useE GetParsedModule oldNfp)
134+ let oldModuleNameM = moduleNameString . unLoc <$> (hsmodName $ unLoc $ pm_parsed_source pm)
135+ newModulePathM <- guessModuleName newNfp oldNfp
136+ case (oldModuleNameM, newModulePathM) of
137+ (Just oldModulePath, Just newModulePath) -> do
138+ modDeclEdit <- ModuleRename. renameModuleDeclaration recorder' state oldNfp newModulePath
139+ importEdits <- ModuleRename. applyRenameToImports recorder' state (T. pack oldModulePath) newModulePath $ oldNfp
140+ pure $ Just $ combineTextEdits modDeclEdit importEdits
141+ _ -> do
142+ logWith recorder' Info $ ModuleRename. NoModuleName newNfp
143+ pure Nothing
144+
145+ guessModuleName newNfp oldNfp = do
146+ (session, _) <- runActionE " ModuleName.ghcSession" state $ useWithStaleE GhcSession oldNfp
147+ srcPaths <- liftIO $ evalGhcEnv (hscEnv session) $ importPaths <$> getSessionDynFlags
148+ correctNames <- mapExceptT liftIO $ ModuleName. potentialModuleNames (cmapWithPrio LogModuleName recorder) state (fromNormalizedFilePath newNfp) srcPaths
149+ pure $ minimumBy (comparing T. length ) <$> NE. nonEmpty correctNames
150+
110151renameProvider :: PluginMethodHandler IdeState Method_TextDocumentRename
111152renameProvider state pluginId (RenameParams _prog (TextDocumentIdentifier uri) pos newNameText) = do
112153 nfp <- getNormalizedFilePathE uri
@@ -261,6 +302,18 @@ handleGetHieAst state nfp =
261302 -- which is bad (see https://github.com/haskell/haskell-language-server/issues/3799)
262303 fmap removeGenerated $ runActionE " Rename.GetHieAst" state $ useE GetHieAst nfp
263304
305+ combineTextEdits :: WorkspaceEdit -> WorkspaceEdit -> WorkspaceEdit
306+ combineTextEdits (WorkspaceEdit c1 dc1 ca1) (WorkspaceEdit c2 dc2 ca2) =
307+ WorkspaceEdit c dc ca
308+ where
309+ c = liftA2 (Map. unionWith (<>) ) c1 c2 <|> c1 <|> c2
310+ dc = dc1 <> dc2
311+ -- We know this might result in information loss due to the monad instance of map,
312+ -- but we do not expect our use of workspacedit combination to contain two changeAnnotations
313+ -- for the same edit.
314+ ca = ca1 <> ca2
315+
316+
264317{- Note [Generated references]
265318~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
266319GHC inserts `Use`s of record constructor everywhere where its record selectors are used,
0 commit comments