From 187322610a1f2479bdb0add0c5902f30f6f7ba66 Mon Sep 17 00:00:00 2001 From: VeryMilkyJoe Date: Sun, 7 Jun 2026 10:38:11 +0200 Subject: [PATCH 1/3] Change of module decl and imports on rename Add handling of WillRename Notification which changes the module declaration and all imports of the module to rename to the new name. --- ghcide/src/Development/IDE/Core/FileStore.hs | 10 ++ haskell-language-server.cabal | 3 + .../src/Ide/Plugin/Rename.hs | 65 ++++++++- .../src/Ide/Plugin/Rename/ModuleName.hs | 44 +++--- .../src/Ide/Plugin/Rename/ModuleRename.hs | 131 ++++++++++++++++++ 5 files changed, 228 insertions(+), 25 deletions(-) create mode 100644 plugins/hls-rename-plugin/src/Ide/Plugin/Rename/ModuleRename.hs diff --git a/ghcide/src/Development/IDE/Core/FileStore.hs b/ghcide/src/Development/IDE/Core/FileStore.hs index 7d253131d6..21a49156ff 100644 --- a/ghcide/src/Development/IDE/Core/FileStore.hs +++ b/ghcide/src/Development/IDE/Core/FileStore.hs @@ -7,6 +7,7 @@ module Development.IDE.Core.FileStore( getFileContents, getUriContents, getVersionedTextDoc, + getVersionedTextDocForNormalizedFilePath, setFileModified, setSomethingModified, fileStoreRules, @@ -25,6 +26,7 @@ module Development.IDE.Core.FileStore( ) where import Control.Concurrent.STM.Stats (STM, atomically) +import Control.Concurrent.STM.TQueue (writeTQueue) import Control.Exception import Control.Lens ((^.)) import Control.Monad.Extra @@ -256,6 +258,14 @@ getVersionedTextDoc doc = do Nothing -> 0 return (VersionedTextDocumentIdentifier uri ver) +getVersionedTextDocForNormalizedFilePath :: NormalizedFilePath -> Action VersionedTextDocumentIdentifier +getVersionedTextDocForNormalizedFilePath nfp = do + mvf <- getVirtualFile nfp + let ver = case mvf of + Just (VirtualFile lspver _ _ _) -> lspver + Nothing -> 0 + return (VersionedTextDocumentIdentifier (fromNormalizedUri $ filePathToUri' nfp) ver) + fileStoreRules :: Recorder (WithPriority Log) -> (NormalizedFilePath -> Action Bool) -> Rules () fileStoreRules recorder isWatched = do getModificationTimeRule recorder diff --git a/haskell-language-server.cabal b/haskell-language-server.cabal index def5d32e13..08b0c13a48 100644 --- a/haskell-language-server.cabal +++ b/haskell-language-server.cabal @@ -595,9 +595,12 @@ library hls-rename-plugin exposed-modules: Ide.Plugin.Rename Ide.Plugin.Rename.ModuleName + Ide.Plugin.Rename.ModuleRename hs-source-dirs: plugins/hls-rename-plugin/src build-depends: + , extra ^>=1.8.1 , aeson + , text-rope ^>=0.3 , containers , filepath , ghc diff --git a/plugins/hls-rename-plugin/src/Ide/Plugin/Rename.hs b/plugins/hls-rename-plugin/src/Ide/Plugin/Rename.hs index d22e6b6913..0c0f908eb0 100644 --- a/plugins/hls-rename-plugin/src/Ide/Plugin/Rename.hs +++ b/plugins/hls-rename-plugin/src/Ide/Plugin/Rename.hs @@ -4,32 +4,39 @@ {-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} -{-# OPTIONS_GHC -Wno-orphans #-} +{-# OPTIONS_GHC -Wno-orphans #-} {-# LANGUAGE LambdaCase #-} module Ide.Plugin.Rename (descriptor, Log) where +import Control.Applicative ((<|>)) import Control.Lens ((^.)) import Control.Monad import Control.Monad.Except (ExceptT, throwError) import Control.Monad.IO.Class (MonadIO, liftIO) import Control.Monad.Trans.Class (lift) +import Control.Monad.Trans.Except (mapExceptT) +import Control.Monad.Trans.Maybe (hoistMaybe, + maybeToExceptT) import Data.Either (rights) -import Data.Foldable (fold) +import Data.Foldable (fold, minimumBy) import Data.Generics import Data.Hashable import Data.HashSet (HashSet) import qualified Data.HashSet as HS +import Data.List (foldl') import Data.List.NonEmpty (NonEmpty ((:|)), groupWith) +import qualified Data.List.NonEmpty as NE import qualified Data.Map as M +import qualified Data.Map.Strict as Map import Data.Maybe import Data.Mod.Word +import Data.Ord (comparing) import qualified Data.Text as T -import Development.IDE (Recorder, WithPriority, - usePropertyAction) import Development.IDE.Core.FileStore (getVersionedTextDoc) import Development.IDE.Core.PluginUtils +import Development.IDE.Core.Rules (usePropertyAction) import Development.IDE.Core.RuleTypes import Development.IDE.Core.Service hiding (Log) import Development.IDE.Core.Shake hiding (Log) @@ -38,8 +45,10 @@ import Development.IDE.GHC.Compat.ExactPrint import Development.IDE.GHC.Error import Development.IDE.GHC.ExactPrint hiding (Log) import qualified Development.IDE.GHC.ExactPrint as E +import Development.IDE.GHC.Util (evalGhcEnv) import Development.IDE.Plugin.CodeAction import Development.IDE.Spans.AtPoint +import Development.IDE.Types.HscEnvEq (HscEnvEq (hscEnv)) import Development.IDE.Types.Location import GHC.Iface.Ext.Types (HieAST (..), HieASTs (..), @@ -49,11 +58,11 @@ import GHC.Iface.Ext.Utils (generateReferencesMap) import HieDb ((:.) (..)) import HieDb.Query import HieDb.Types (RefRow (refIsGenerated)) -import Ide.Logger (Pretty (..), - cmapWithPrio) +import Ide.Logger import Ide.Plugin.Error import Ide.Plugin.Properties import qualified Ide.Plugin.Rename.ModuleName as ModuleName +import qualified Ide.Plugin.Rename.ModuleRename as ModuleRename import Ide.PluginUtils import Ide.Types import qualified Language.LSP.Protocol.Lens as L @@ -65,11 +74,13 @@ instance Hashable (Mod a) where hash n = hash (unMod n) data Log = LogExactPrint E.Log | LogModuleName ModuleName.Log + | LogModuleRename ModuleRename.Log instance Pretty Log where pretty = \ case LogExactPrint msg -> pretty msg LogModuleName msg -> pretty msg + LogModuleRename msg -> pretty msg descriptor :: Recorder (WithPriority Log) -> PluginId -> PluginDescriptor IdeState descriptor recorder pluginId = mkExactprintPluginDescriptor exactPrintRecorder $ @@ -78,6 +89,7 @@ descriptor recorder pluginId = mkExactprintPluginDescriptor exactPrintRecorder $ [ mkPluginHandler SMethod_TextDocumentRename renameProvider , mkPluginHandler SMethod_TextDocumentPrepareRename prepareRenameProvider , mkPluginHandler SMethod_TextDocumentCodeLens (ModuleName.codeLens moduleNameRecorder) + , mkPluginHandler SMethod_WorkspaceWillRenameFiles (renameModuleProvider recorder) ] , pluginCommands = [PluginCommand ModuleName.updateModuleNameCommand "Set name of module to match with file path" (ModuleName.command moduleNameRecorder)] , pluginConfigDescriptor = defaultConfigDescriptor @@ -107,6 +119,35 @@ prepareRenameProvider state _pluginId (PrepareRenameParams (TextDocumentIdentifi [] -> InR Null srcSpan : _ -> InL $ PrepareRenameResult $ InL (realSrcSpanToRange srcSpan) +renameModuleProvider :: Recorder (WithPriority Log)-> PluginMethodHandler IdeState Method_WorkspaceWillRenameFiles +renameModuleProvider recorder state _ (RenameFilesParams renames) = do + renameResults <- mapM renameFile renames + pure $ InL $ foldl' combineTextEdits (WorkspaceEdit mempty mempty mempty) $ catMaybes renameResults + where + recorder' = cmapWithPrio LogModuleRename recorder + + renameFile (FileRename oldUri newUri) = do + oldNfp <- fmap toNormalizedFilePath $ uriToFilePathE $ Uri oldUri + newNfp <- fmap toNormalizedFilePath $ uriToFilePathE $ Uri newUri + pm <- runActionE "Rename.GetParsedModule" state + (useE GetParsedModule oldNfp) + let oldModuleNameM = moduleNameString . unLoc <$> (hsmodName $ unLoc $ pm_parsed_source pm) + newModulePathM <- guessModuleName newNfp oldNfp + case (oldModuleNameM, newModulePathM) of + (Just oldModulePath, Just newModulePath) -> do + modDeclEdit <- ModuleRename.renameModuleDeclaration recorder' state oldNfp newModulePath + importEdits <- ModuleRename.applyRenameToImports recorder' state (T.pack oldModulePath) newModulePath $ oldNfp + pure $ Just $ combineTextEdits modDeclEdit importEdits + _ -> do + logWith recorder' Info $ ModuleRename.NoModuleName newNfp + pure Nothing + + guessModuleName newNfp oldNfp = do + (session, _) <- runActionE "ModuleName.ghcSession" state $ useWithStaleE GhcSession oldNfp + srcPaths <- liftIO $ evalGhcEnv (hscEnv session) $ importPaths <$> getSessionDynFlags + correctNames <- mapExceptT liftIO $ ModuleName.potentialModuleNames (cmapWithPrio LogModuleName recorder) state (fromNormalizedFilePath newNfp) srcPaths + pure $ minimumBy (comparing T.length) <$> NE.nonEmpty correctNames + renameProvider :: PluginMethodHandler IdeState Method_TextDocumentRename renameProvider state pluginId (RenameParams _prog (TextDocumentIdentifier uri) pos newNameText) = do nfp <- getNormalizedFilePathE uri @@ -261,6 +302,18 @@ handleGetHieAst state nfp = -- which is bad (see https://github.com/haskell/haskell-language-server/issues/3799) fmap removeGenerated $ runActionE "Rename.GetHieAst" state $ useE GetHieAst nfp +combineTextEdits :: WorkspaceEdit -> WorkspaceEdit -> WorkspaceEdit +combineTextEdits (WorkspaceEdit c1 dc1 ca1) (WorkspaceEdit c2 dc2 ca2) = + WorkspaceEdit c dc ca + where + c = liftA2 (Map.unionWith (<>)) c1 c2 <|> c1 <|> c2 + dc = dc1 <> dc2 + -- We know this might result in information loss due to the monad instance of map, + -- but we do not expect our use of workspacedit combination to contain two changeAnnotations + -- for the same edit. + ca = ca1 <> ca2 + + {- Note [Generated references] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GHC inserts `Use`s of record constructor everywhere where its record selectors are used, diff --git a/plugins/hls-rename-plugin/src/Ide/Plugin/Rename/ModuleName.hs b/plugins/hls-rename-plugin/src/Ide/Plugin/Rename/ModuleName.hs index 530a8e0d85..ce9fbd1aa1 100644 --- a/plugins/hls-rename-plugin/src/Ide/Plugin/Rename/ModuleName.hs +++ b/plugins/hls-rename-plugin/src/Ide/Plugin/Rename/ModuleName.hs @@ -15,6 +15,8 @@ module Ide.Plugin.Rename.ModuleName ( codeLens, updateModuleNameCommand, command, + potentialModuleNames, + codeModuleName, ) where import Control.Monad (forM_, void) @@ -102,12 +104,11 @@ data Action = Replace action :: Recorder (WithPriority Log) -> IdeState -> Uri -> ExceptT PluginError (HandlerM c) [Action] action recorder state uri = do nfp <- getNormalizedFilePathE uri - fp <- uriToFilePathE uri contents <- liftIO $ runAction "ModuleName.getFileContents" state $ getFileContents nfp let emptyModule = maybe True (T.null . T.strip . Rope.toText) contents - correctNames <- mapExceptT liftIO $ pathModuleNames recorder state nfp fp + correctNames <- mapExceptT liftIO $ pathModuleNames recorder state nfp logWith recorder Debug (CorrectNames correctNames) let bestName = minimumBy (comparing T.length) <$> NE.nonEmpty correctNames logWith recorder Debug (BestName bestName) @@ -127,33 +128,34 @@ action recorder state uri = do -- | Possible module names, as derived by the position of the module in the -- source directories. There may be more than one possible name, if the source -- directories are nested inside each other. -pathModuleNames :: Recorder (WithPriority Log) -> IdeState -> NormalizedFilePath -> FilePath -> ExceptT PluginError IO [T.Text] -pathModuleNames recorder state normFilePath filePath +pathModuleNames :: Recorder (WithPriority Log) -> IdeState -> NormalizedFilePath -> ExceptT PluginError IO [T.Text] +pathModuleNames recorder state nfp | firstLetter isLower $ takeFileName filePath = return ["Main"] | otherwise = do - (session, _) <- runActionE "ModuleName.ghcSession" state $ useWithStaleE GhcSession normFilePath + (session, _) <- runActionE "ModuleName.ghcSession" state $ useWithStaleE GhcSession nfp srcPaths <- liftIO $ evalGhcEnv (hscEnv session) $ importPaths <$> getSessionDynFlags logWith recorder Debug (SrcPaths srcPaths) - + potentialModuleNames recorder state filePath srcPaths -- Append a `pathSeparator` to make the path looks like a directory, -- and then we can drop it uniformly. -- See https://github.com/haskell/haskell-language-server/pull/3092 for details. - let paths = map (normalise . (<> pure pathSeparator)) srcPaths - logWith recorder Debug (NormalisedPaths paths) + where + filePath = fromNormalizedFilePath nfp - -- TODO, this can be avoid if the filePath is already absolute, - -- we can avoid the toAbsolute call in the future. - -- see Note [Root Directory] - let mdlPath = (toAbsolute $ rootDir state) filePath - logWith recorder Debug (AbsoluteFilePath mdlPath) +potentialModuleNames :: Recorder (WithPriority Log) -> IdeState -> [Char] -> [FilePath] -> ExceptT PluginError IO [T.Text] +potentialModuleNames recorder state filePath srcPaths = do + let paths = map (normalise . (<> pure pathSeparator)) srcPaths + logWith recorder Debug (NormalisedPaths paths) - let suffixes = mapMaybe (`stripPrefix` mdlPath) paths - pure (map moduleNameFrom suffixes) - where - firstLetter :: (Char -> Bool) -> FilePath -> Bool - firstLetter _ [] = False - firstLetter pred (c:_) = pred c + -- TODO, this can be avoid if the filePath is already absolute, + -- we can avoid the toAbsolute call in the future. + -- see Note [Root Directory] + let mdlPath = (toAbsolute $ rootDir state) filePath + logWith recorder Debug (AbsoluteFilePath mdlPath) + let suffixes = mapMaybe (`stripPrefix` mdlPath) paths + pure (map moduleNameFrom suffixes) + where moduleNameFrom = T.pack . intercalate "." @@ -163,6 +165,10 @@ pathModuleNames recorder state normFilePath filePath . splitDirectories . dropExtension +firstLetter :: (Char -> Bool) -> FilePath -> Bool +firstLetter _ [] = False +firstLetter pred (c:_) = pred c + -- | The module name, as stated in the module codeModuleName :: IdeState -> NormalizedFilePath -> IO (Maybe (Range, T.Text)) codeModuleName state nfp = runMaybeT $ do diff --git a/plugins/hls-rename-plugin/src/Ide/Plugin/Rename/ModuleRename.hs b/plugins/hls-rename-plugin/src/Ide/Plugin/Rename/ModuleRename.hs new file mode 100644 index 0000000000..4a8415d33f --- /dev/null +++ b/plugins/hls-rename-plugin/src/Ide/Plugin/Rename/ModuleRename.hs @@ -0,0 +1,131 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE PatternSynonyms #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE ViewPatterns #-} + +module Ide.Plugin.Rename.ModuleRename (renameModuleDeclaration, applyRenameToImports, Log(..)) where + +import Control.Lens (re) +import Control.Lens.Getter ((^.)) +import Control.Monad (guard, (<=<)) +import qualified Control.Monad.Extra as Maybe +import Control.Monad.Trans +import Control.Monad.Trans.Except (ExceptT) +import Control.Monad.Trans.Maybe +import qualified Data.Maybe as Maybe +import qualified Data.Text as T +import Development.IDE (NormalizedFilePath) +import Development.IDE.Core.FileStore (getVersionedTextDocForNormalizedFilePath) +import Development.IDE.Core.PluginUtils (runActionE) +import Development.IDE.Core.PositionMapping (toCurrentRange) +import Development.IDE.Core.Rules hiding (Log) +import qualified Development.IDE.Core.Rules as Shake +import Development.IDE.Core.RuleTypes (GetModuleGraph (..)) +import qualified Development.IDE.Core.Shake as Shake +import qualified Development.IDE.GHC.Compat as GHC +import Development.IDE.GHC.Compat.Core +import Development.IDE.GHC.Error (realSrcSpanToRange, + srcSpanToRange) +import Development.IDE.Import.DependencyInformation (immediateReverseDependencies) +import Ide.Logger +import Ide.Plugin.Error +import Language.LSP.Protocol.Types (Range, + TextDocumentEdit (..), + TextEdit (..), + VersionedTextDocumentIdentifier, + WorkspaceEdit (..), + _versionedTextDocumentIdentifier, + fromNormalizedFilePath, + type (|?) (InL)) + +data Log + = CorrectNames [T.Text] + | LogRenameDependencies T.Text [NormalizedFilePath] + | NoModuleName NormalizedFilePath + | LogRenameModuleDeclaration NormalizedFilePath + deriving (Show) + +instance Pretty Log where + pretty log = + "ModuleRename." <> case log of + CorrectNames log -> "CorrectNames" <> colon <+> pretty log + LogRenameDependencies oldName fps -> "Rename of" <+> pretty oldName <+> "in" <+> (pretty $ map fromNormalizedFilePath fps) + NoModuleName nfp -> "Could not execute rename of" <+> pretty (fromNormalizedFilePath nfp) <+> "as no module path could be determined." + LogRenameModuleDeclaration nfp -> "Renaming module declaration for file" <+> pretty (fromNormalizedFilePath nfp) + +-- | Apply rename to the given module's declaration +-- +-- Rename the module in the given file's module declaration. +-- Fails if . +renameModuleDeclaration :: (MonadIO m) => Recorder (WithPriority Log) -> IdeState -> NormalizedFilePath -> T.Text -> ExceptT PluginError m WorkspaceEdit +renameModuleDeclaration recorder ideState oldHaskellFilePath newModulePath = do + logWith recorder Info $ LogRenameModuleDeclaration oldHaskellFilePath + verTextDocId <- runActionE "cabal-plugin.getUriContents" ideState $ lift $ getVersionedTextDocForNormalizedFilePath $ oldHaskellFilePath + rangeToRename <- + maybeToExceptT PluginStaleResolve $ + MaybeT $ + liftIO $ + moduleNameRange ideState $ + oldHaskellFilePath + let + edit = mkTextEditInRange newModulePath verTextDocId rangeToRename + pure $ WorkspaceEdit Nothing (Just [InL edit]) Nothing + +-- | Apply rename to all imports of the given module +-- +-- Replaces all imports of the given old module name with the given new module name. +applyRenameToImports :: + (MonadIO m) => + Recorder (WithPriority Log) -> + IdeState -> + -- | The module name before the rename. + T.Text -> + -- | The new module name after the rename. + T.Text -> + -- | The old path to the renamed haskell file. + NormalizedFilePath -> + ExceptT e m WorkspaceEdit +applyRenameToImports recorder ideState oldModulePath newModulePath oldHaskellFilePath = do + moduleGraph <- runActionE "applyRenameToImports" ideState $ lift $ Shake.useNoFile_ GetModuleGraph + let + invertedDepsM = immediateReverseDependencies oldHaskellFilePath moduleGraph + case invertedDepsM of + Just depFilePaths -> do + logWith recorder Debug $ LogRenameDependencies oldModulePath depFilePaths + modImportRanges <- liftIO $ Maybe.mapMaybeM (getRangesForModuleImports ideState oldModulePath) depFilePaths + let + textEdits = concatMap (\(verTextDocId, ranges) -> map (mkTextEditInRange newModulePath verTextDocId) ranges) modImportRanges + pure $ WorkspaceEdit Nothing (Just $ map InL textEdits) Nothing + Nothing -> pure $ WorkspaceEdit Nothing Nothing Nothing + +-- | The module declaration range of the given file path + +-- | Determines all ranges in the given file where the module name is imported +-- +-- Returns the identifier of the file and a list of ranges of the imports if none of the rule applications fail. +-- Otherwise will return Nothing. +getRangesForModuleImports :: IdeState -> T.Text -> NormalizedFilePath -> IO (Maybe (VersionedTextDocumentIdentifier, [Range])) +getRangesForModuleImports state moduleName nfp = runMaybeT $ do + verTextDocId <- MaybeT . fmap Just $ runAction "cabal-plugin.getUriContents" state $ getVersionedTextDocForNormalizedFilePath nfp + (pm, mp) <- MaybeT . runAction "ModuleName.GetParsedModule" state $ Shake.useWithStale GetParsedModule nfp + let + allImports = hsmodImports . unLoc $ GHC.pm_parsed_source pm + modNameImports = + Maybe.mapMaybe + (\imp -> GHC.getLoc (ideclName $ GHC.unLoc imp) <$ guard ((== (mkModuleName $ T.unpack moduleName)) . GHC.unLoc . ideclName $ GHC.unLoc imp)) + allImports + pure $ (verTextDocId, Maybe.mapMaybe (toCurrentRange mp <=< srcSpanToRange) modNameImports) + +-- +-- Inspired by `codeModuleName` in the hls-module-name-plugin. +moduleNameRange :: Shake.IdeState -> NormalizedFilePath -> IO (Maybe Range) +moduleNameRange state nfp = runMaybeT $ do + (pm, mp) <- MaybeT . runAction "ModuleName.GetParsedModule" state $ Shake.useWithStale GetParsedModule nfp + L (locA -> (RealSrcSpan l _)) _ <- MaybeT . pure . hsmodName . unLoc $ GHC.pm_parsed_source pm + range <- MaybeT . pure $ toCurrentRange mp (realSrcSpanToRange l) + pure range + +mkTextEditInRange :: T.Text -> VersionedTextDocumentIdentifier -> Range -> TextDocumentEdit +mkTextEditInRange newText verTextDocId range = + TextDocumentEdit (verTextDocId ^. re _versionedTextDocumentIdentifier) $ fmap InL [TextEdit range newText] From 674a1b9fab8b7e1dd2fde15533965fbe5e46c25d Mon Sep 17 00:00:00 2001 From: VeryMilkyJoe Date: Sun, 7 Jun 2026 10:38:11 +0200 Subject: [PATCH 2/3] Change of module decl and imports on rename Add handling of WillRename Notification which changes the module declaration and all imports of the module to rename to the new name. --- plugins/hls-rename-plugin/src/Ide/Plugin/Rename.hs | 6 ++---- .../hls-rename-plugin/src/Ide/Plugin/Rename/ModuleRename.hs | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/plugins/hls-rename-plugin/src/Ide/Plugin/Rename.hs b/plugins/hls-rename-plugin/src/Ide/Plugin/Rename.hs index 0c0f908eb0..8fab3ced54 100644 --- a/plugins/hls-rename-plugin/src/Ide/Plugin/Rename.hs +++ b/plugins/hls-rename-plugin/src/Ide/Plugin/Rename.hs @@ -16,15 +16,13 @@ import Control.Monad.Except (ExceptT, throwError) import Control.Monad.IO.Class (MonadIO, liftIO) import Control.Monad.Trans.Class (lift) import Control.Monad.Trans.Except (mapExceptT) -import Control.Monad.Trans.Maybe (hoistMaybe, - maybeToExceptT) import Data.Either (rights) import Data.Foldable (fold, minimumBy) import Data.Generics import Data.Hashable import Data.HashSet (HashSet) import qualified Data.HashSet as HS -import Data.List (foldl') +import qualified Data.List as List import Data.List.NonEmpty (NonEmpty ((:|)), groupWith) import qualified Data.List.NonEmpty as NE @@ -122,7 +120,7 @@ prepareRenameProvider state _pluginId (PrepareRenameParams (TextDocumentIdentifi renameModuleProvider :: Recorder (WithPriority Log)-> PluginMethodHandler IdeState Method_WorkspaceWillRenameFiles renameModuleProvider recorder state _ (RenameFilesParams renames) = do renameResults <- mapM renameFile renames - pure $ InL $ foldl' combineTextEdits (WorkspaceEdit mempty mempty mempty) $ catMaybes renameResults + pure $ InL $ List.foldl' combineTextEdits (WorkspaceEdit mempty mempty mempty) $ catMaybes renameResults where recorder' = cmapWithPrio LogModuleRename recorder diff --git a/plugins/hls-rename-plugin/src/Ide/Plugin/Rename/ModuleRename.hs b/plugins/hls-rename-plugin/src/Ide/Plugin/Rename/ModuleRename.hs index 4a8415d33f..5006db87f7 100644 --- a/plugins/hls-rename-plugin/src/Ide/Plugin/Rename/ModuleRename.hs +++ b/plugins/hls-rename-plugin/src/Ide/Plugin/Rename/ModuleRename.hs @@ -50,7 +50,7 @@ instance Pretty Log where pretty log = "ModuleRename." <> case log of CorrectNames log -> "CorrectNames" <> colon <+> pretty log - LogRenameDependencies oldName fps -> "Rename of" <+> pretty oldName <+> "in" <+> (pretty $ map fromNormalizedFilePath fps) + LogRenameDependencies oldName fps -> "Rename of" <+> pretty oldName <+> "in files:" <+> (pretty $ map fromNormalizedFilePath fps) NoModuleName nfp -> "Could not execute rename of" <+> pretty (fromNormalizedFilePath nfp) <+> "as no module path could be determined." LogRenameModuleDeclaration nfp -> "Renaming module declaration for file" <+> pretty (fromNormalizedFilePath nfp) From ff82f14d3abeedfdbbf8eb2d460138c77ad1f947 Mon Sep 17 00:00:00 2001 From: VeryMilkyJoe Date: Thu, 6 Aug 2026 21:45:35 +0200 Subject: [PATCH 3/3] Update plugins/hls-rename-plugin/src/Ide/Plugin/Rename/ModuleName.hs Co-authored-by: AndreasPK --- plugins/hls-rename-plugin/src/Ide/Plugin/Rename/ModuleName.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/hls-rename-plugin/src/Ide/Plugin/Rename/ModuleName.hs b/plugins/hls-rename-plugin/src/Ide/Plugin/Rename/ModuleName.hs index ce9fbd1aa1..00d7844628 100644 --- a/plugins/hls-rename-plugin/src/Ide/Plugin/Rename/ModuleName.hs +++ b/plugins/hls-rename-plugin/src/Ide/Plugin/Rename/ModuleName.hs @@ -147,7 +147,7 @@ potentialModuleNames recorder state filePath srcPaths = do let paths = map (normalise . (<> pure pathSeparator)) srcPaths logWith recorder Debug (NormalisedPaths paths) - -- TODO, this can be avoid if the filePath is already absolute, + -- TODO, this can be avoided if the filePath is already absolute, -- we can avoid the toAbsolute call in the future. -- see Note [Root Directory] let mdlPath = (toAbsolute $ rootDir state) filePath