Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cabal.project
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ packages:
./ghcide
./hls-plugin-api
./hls-test-utils
../lsp/lsp


index-state: 2025-08-08T12:31:54Z
Expand Down
9 changes: 9 additions & 0 deletions ghcide/src/Development/IDE/Core/FileStore.hs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ module Development.IDE.Core.FileStore(
getFileContents,
getUriContents,
getVersionedTextDoc,
getVersionedTextDocForNormalizedFilePath,
setFileModified,
setSomethingModified,
fileStoreRules,
Expand Down Expand Up @@ -256,6 +257,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
Expand Down
1 change: 1 addition & 0 deletions ghcide/src/Development/IDE/Core/Shake.hs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ module Development.IDE.Core.Shake(
GetModificationTime(GetModificationTime, GetModificationTime_, missingFileDiagnostics),
shakeOpen, shakeShut,
shakeEnqueue,
shakeRestart,
newSession,
use, useNoFile, uses, useWithStaleFast, useWithStaleFast', delayedAction,
useWithSeparateFingerprintRule,
Expand Down
1 change: 1 addition & 0 deletions ghcide/src/Development/IDE/GHC/Compat/Core.hs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ module Development.IDE.GHC.Compat.Core (
unLocA,
LocatedAn,
GHC.LocatedA,
GHC.SrcSpanAnnA,
GHC.AnnListItem(..),
GHC.NameAnn(..),
SrcLoc.RealLocated,
Expand Down
10 changes: 10 additions & 0 deletions ghcide/src/Development/IDE/LSP/Notifications.hs
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,12 @@ import Development.IDE.Core.OfInterest hiding (Log, LogShake)
import Development.IDE.Core.Service hiding (Log, LogShake)
import Development.IDE.Core.Shake hiding (Log)
import qualified Development.IDE.Core.Shake as Shake
import qualified Development.IDE.Types.Shake as Shake
import Development.IDE.Types.Location
import Ide.Logger
import Ide.Types
import Numeric.Natural
import Development.IDE.Core.RuleTypes (GhcSessionIO(..))

data Log
= LogShake Shake.Log
Expand All @@ -46,6 +48,7 @@ data Log
| LogSavedTextDocument !Uri
| LogClosedTextDocument !Uri
| LogWatchedFileEvents !Text.Text
| LogSessionRestart
| LogWarnNoWatchedFilesSupport
deriving Show

Expand All @@ -59,6 +62,7 @@ instance Pretty Log where
LogClosedTextDocument uri -> "Closed text document:" <+> pretty (getUri uri)
LogWatchedFileEvents msg -> "Watched file events:" <+> pretty msg
LogWarnNoWatchedFilesSupport -> "Client does not support watched files. Falling back to OS polling"
LogSessionRestart -> "Restarting shake session globally"

whenUriFile :: Uri -> (NormalizedFilePath -> IO ()) -> IO ()
whenUriFile uri act = whenJust (LSP.uriToFilePath uri) $ act . toNormalizedFilePath'
Expand Down Expand Up @@ -147,6 +151,12 @@ descriptor recorder plId = (defaultPluginDescriptor plId desc) { pluginNotificat
success <- registerFileWatches globs
unless success $
liftIO $ logWith recorder Warning LogWarnNoWatchedFilesSupport
, mkPluginNotificationHandler LSP.SMethod_WorkspaceDidRenameFiles $
\ide vfs _ _ -> liftIO $ do
logWith recorder Debug LogSessionRestart
Shake.shakeRestart (cmapWithPrio LogShake recorder) ide (VFSModified vfs) "" [] $ do
return [Shake.toNoFileKey GhcSessionIO]
pure ()
],

-- The ghcide descriptors should come last'ish so that the notification handlers
Expand Down
57 changes: 56 additions & 1 deletion ghcide/src/Development/IDE/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ import System.Process (readProcessWithExitCo
import System.Random (newStdGen)
import System.Time.Extra (Seconds, offsetTime,
showDuration)
import qualified Language.LSP.Protocol.Types as LSP

data Log
= LogHeapStats !HeapStats.Log
Expand Down Expand Up @@ -300,7 +301,61 @@ defaultMain recorder Arguments{..} = withHeapStats (cmapWithPrio LogHeapStats re
let hlsPlugin = asGhcIdePlugin (cmapWithPrio LogPluginHLS recorder) argsHlsPlugins
hlsCommands = allLspCmdIds' pid argsHlsPlugins
plugins = hlsPlugin <> argsGhcidePlugin
options = argsLspOptions { LSP.optExecuteCommandCommands = LSP.optExecuteCommandCommands argsLspOptions <> Just hlsCommands }
options = argsLspOptions {
LSP.optExecuteCommandCommands = LSP.optExecuteCommandCommands argsLspOptions <> Just hlsCommands,
LSP.optWorkspaceWillRenameFileOperationRegistrationOptions = Just $
LSP.FileOperationRegistrationOptions
[LSP.FileOperationFilter
{_scheme= Just "file", _pattern = LSP.FileOperationPattern
{ {-|
The glob pattern to match. Glob patterns can have the following syntax:
- `*` to match one or more characters in a path segment
- `?` to match on one character in a path segment
- `**` to match any number of path segments, including none
- `{}` to group sub patterns into an OR expression. (e.g. `**​/*.{ts,js}` matches all TypeScript and JavaScript files)
- `[]` to declare a range of characters to match in a path segment (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …)
- `[!...]` to negate a range of characters to match in a path segment (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but not `example.0`)
-}
_glob = "**/*.hs"
, {-|
Whether to match files or folders with this pattern.

Matches both if undefined.
-}
_matches = Nothing
, {-|
Additional options used during matching.
-}
_options = Nothing
}
}],
LSP.optWorkspaceDidRenameFileOperationRegistrationOptions = Just $
LSP.FileOperationRegistrationOptions
[LSP.FileOperationFilter
{_scheme= Just "file", _pattern = LSP.FileOperationPattern
{ {-|
The glob pattern to match. Glob patterns can have the following syntax:
- `*` to match one or more characters in a path segment
- `?` to match on one character in a path segment
- `**` to match any number of path segments, including none
- `{}` to group sub patterns into an OR expression. (e.g. `**​/*.{ts,js}` matches all TypeScript and JavaScript files)
- `[]` to declare a range of characters to match in a path segment (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …)
- `[!...]` to negate a range of characters to match in a path segment (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but not `example.0`)
-}
_glob = "**/*.hs"
, {-|
Whether to match files or folders with this pattern.

Matches both if undefined.
-}
_matches = Nothing
, {-|
Additional options used during matching.
-}
_options = Nothing
}
}]
}
argsParseConfig = getConfigFromNotification argsHlsPlugins
rules = do
argsRules
Expand Down
2 changes: 1 addition & 1 deletion ghcide/src/Development/IDE/Types/KnownTargets.hs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ data KnownTargets = KnownTargets
-- | 'normalisingMap' is a cached copy of `HMap.mapKey const targetMap`
--
-- At startup 'GetLocatedImports' is called on all known files. Say you have 10000
-- modules in your project then this leads to 10000 calls to 'GetLocatedImports'
-- modules in your projecknownTargetsVart then this leads to 10000 calls to 'GetLocatedImports'
-- running concurrently.
--
-- In `GetLocatedImports` the known targets are consulted and the targetsMap
Expand Down
2 changes: 2 additions & 0 deletions haskell-language-server.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ library hls-cabal-plugin
Ide.Plugin.Cabal.CabalAdd.Command
Ide.Plugin.Cabal.CabalAdd.CodeAction
Ide.Plugin.Cabal.CabalAdd.Types
Ide.Plugin.Cabal.CabalAdd.Rename
Ide.Plugin.Cabal.Orphans
Ide.Plugin.Cabal.Outline
Ide.Plugin.Cabal.Parse
Expand Down Expand Up @@ -597,6 +598,7 @@ library hls-rename-plugin
exposed-modules: Ide.Plugin.Rename
hs-source-dirs: plugins/hls-rename-plugin/src
build-depends:
, text-rope ^>=0.3
, containers
, ghc
, ghcide == 2.12.0.0
Expand Down
16 changes: 16 additions & 0 deletions hls-plugin-api/src/Ide/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,9 @@ instance PluginMethod Request Method_WorkspaceExecuteCommand where
instance PluginMethod Request (Method_CustomMethod m) where
handlesRequest _ _ _ _ = HandlesRequest

instance PluginMethod Request Method_WorkspaceWillRenameFiles where
handlesRequest _ _ desc conf = pluginEnabledGlobally desc conf

-- Plugin Notifications

instance PluginMethod Notification Method_TextDocumentDidOpen where
Expand All @@ -629,6 +632,14 @@ instance PluginMethod Notification Method_WorkspaceDidChangeConfiguration where
-- This method has no URI parameter, thus no call to 'pluginResponsible'.
handlesRequest _ _ desc conf = pluginEnabledGlobally desc conf

instance PluginMethod Notification Method_WorkspaceDidRenameFiles where
handlesRequest :: SMethod Method_WorkspaceDidRenameFiles
-> MessageParams Method_WorkspaceDidRenameFiles
-> PluginDescriptor c
-> Config
-> HandleRequestResult
handlesRequest _ _ desc conf = pluginEnabledGlobally desc conf

instance PluginMethod Notification Method_Initialized where
-- This method has no URI parameter, thus no call to 'pluginResponsible'.
handlesRequest _ _ desc conf = pluginEnabledGlobally desc conf
Expand Down Expand Up @@ -838,6 +849,8 @@ instance PluginRequestMethod Method_TextDocumentSemanticTokensFullDelta where
instance PluginRequestMethod Method_TextDocumentInlayHint where
combineResponses _ _ _ _ x = sconcat x

instance PluginRequestMethod Method_WorkspaceWillRenameFiles where

takeLefts :: [a |? b] -> [a]
takeLefts = mapMaybe (\x -> [res | (InL res) <- Just x])

Expand Down Expand Up @@ -909,6 +922,8 @@ instance PluginNotificationMethod Method_WorkspaceDidChangeConfiguration where

instance PluginNotificationMethod Method_Initialized where

instance PluginNotificationMethod Method_WorkspaceDidRenameFiles where

-- ---------------------------------------------------------------------

-- | Methods which have a PluginMethod instance
Expand Down Expand Up @@ -1239,6 +1254,7 @@ instance HasTracing CompletionItem
instance HasTracing DocumentLink
instance HasTracing InlayHint
instance HasTracing WorkspaceSymbol
instance HasTracing RenameFilesParams
-- ---------------------------------------------------------------------
--Experimental resolve refactoring
{-# NOINLINE pROCESS_ID #-}
Expand Down
54 changes: 52 additions & 2 deletions plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@ import qualified Data.List as List
import qualified Data.Maybe as Maybe
import qualified Data.Text ()
import qualified Data.Text as T
import Data.Text.Utf16.Rope.Mixed as Rope

import Control.Monad.Except (runExceptT)
import Development.IDE as D
import Development.IDE.Core.FileStore (getVersionedTextDoc)
import Development.IDE.Core.FileStore (getVersionedTextDoc,
getVersionedTextDocForNormalizedFilePath)
import Development.IDE.Core.PluginUtils
import Development.IDE.Core.Shake (restartShakeSession)
import Development.IDE.Graph (Key)
Expand All @@ -33,6 +37,7 @@ import Distribution.PackageDescription.Configuration (flattenPackageDe
import qualified Distribution.Parsec.Position as Syntax
import qualified Ide.Plugin.Cabal.CabalAdd.CodeAction as CabalAdd
import qualified Ide.Plugin.Cabal.CabalAdd.Command as CabalAdd
import qualified Ide.Plugin.Cabal.CabalAdd.Rename as Rename
import Ide.Plugin.Cabal.Completion.CabalFields as CabalFields
import qualified Ide.Plugin.Cabal.Completion.Completer.Types as CompleterTypes
import qualified Ide.Plugin.Cabal.Completion.Completions as Completions
Expand All @@ -57,6 +62,11 @@ import qualified Language.LSP.VFS as VFS
import qualified Text.Fuzzy.Levenshtein as Fuzzy
import qualified Text.Fuzzy.Parallel as Fuzzy
import Text.Regex.TDFA
import Data.Text.Encoding (encodeUtf8)
import Debug.Trace (traceShowM)
import Control.Monad.Trans.Except (ExceptT)
import qualified Development.IDE.Core.Shake as Shake
import qualified Data.Text.IO as Text

data Log
= LogModificationTime NormalizedFilePath FileVersion
Expand All @@ -70,6 +80,9 @@ data Log
| LogCompletionContext Types.Context Position
| LogCompletions Types.Log
| LogCabalAdd CabalAdd.Log
| LogDidRename Rename.Log
| LogShake Shake.Log
| LogSessionRestart
deriving (Show)

instance Pretty Log where
Expand All @@ -95,6 +108,9 @@ instance Pretty Log where
<+> pretty position
LogCompletions logs -> pretty logs
LogCabalAdd logs -> pretty logs
LogDidRename logs -> pretty logs
LogSessionRestart -> "Restarting shake session globally"
LogShake logs -> pretty logs

{- | Some actions in cabal files can be triggered from haskell files.
This descriptor allows us to hook into the diagnostics of haskell source files and
Expand Down Expand Up @@ -128,6 +144,11 @@ descriptor recorder plId =
, mkPluginHandler LSP.SMethod_TextDocumentCodeAction $ fieldSuggestCodeAction recorder
, mkPluginHandler LSP.SMethod_TextDocumentDefinition gotoDefinition
, mkPluginHandler LSP.SMethod_TextDocumentHover hover
, mkPluginHandler LSP.SMethod_WorkspaceWillRenameFiles $
\ide _ (RenameFilesParams renames) -> do
case renames of
(fileRename:_) -> renameModuleHandler recorder ide fileRename
_ -> error "cannot handle multiple file renames"
]
, pluginNotificationHandlers =
mconcat
Expand Down Expand Up @@ -165,7 +186,6 @@ descriptor recorder plId =
log' = logWith recorder
ruleRecorder = cmapWithPrio LogRule recorder
ofInterestRecorder = cmapWithPrio LogOfInterest recorder

whenUriFile :: Uri -> (NormalizedFilePath -> IO ()) -> IO ()
whenUriFile uri act = whenJust (uriToFilePath uri) $ act . toNormalizedFilePath'

Expand Down Expand Up @@ -300,6 +320,36 @@ cabalAddModuleCodeAction recorder state plId (CodeActionParams _ _ (TextDocument
pure $ InL $ fmap InR actions
Nothing -> pure $ InL []

renameModuleHandler :: Recorder (WithPriority Log) -> IdeState -> FileRename -> ExceptT PluginError (HandlerM Config) (WorkspaceEdit |? Null)
renameModuleHandler recorder state (FileRename oldUri newUri) = do
caps <- lift pluginGetClientCapabilities
renameResult <- runExceptT $ do
oldHaskellFilePath <- uriToFilePathE $ Uri oldUri
newHaskellFilePath <- uriToFilePathE $ Uri newUri
mbCabalFile <- liftIO $ CabalAdd.findResponsibleCabalFile oldHaskellFilePath
case mbCabalFile of
Nothing -> pure undefined -- todo log this maybe
Just cabalFilePath -> do
(contents, fields, gpd, verTextDocId) <- runActionE "cabal-plugin.getUriContents" state $ do -- todo mv to handler
let nuri = toNormalizedUri $ filePathToUri cabalFilePath
nfp = toNormalizedFilePath cabalFilePath
mContent <- lift $ getUriContents nuri
content <- case mContent of
Just content -> pure content
Nothing -> liftIO $ Rope.fromText <$> Text.readFile cabalFilePath
verTextDocId <- lift $ getVersionedTextDocForNormalizedFilePath nfp
(fields, _) <- useWithStaleE ParseCabalFields nfp
(gpd, _) <- useWithStaleE ParseCabalFile nfp
pure (content, fields, gpd, verTextDocId)
Rename.renameHandler (cmapWithPrio LogDidRename recorder) state (caps, verTextDocId) oldHaskellFilePath newHaskellFilePath cabalFilePath (encodeUtf8 $ Rope.toText contents) fields gpd

case renameResult of
Left err -> do
traceShowM ("BANANA", pretty err) --todo better error handling pls
pure $ InR Null
Right edit -> do
pure $ InL edit

{- | Handler for hover messages.

If the cursor is hovering on a dependency, add a documentation link to that dependency.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ addDependencySuggestCodeAction ::
GenericPackageDescription ->
IO [J.CodeAction]
addDependencySuggestCodeAction plId verTxtDocId suggestions haskellFilePath cabalFilePath gpd = do
buildTargets <- liftIO $ getBuildTargets gpd cabalFilePath haskellFilePath
buildTargets <- liftIO $ getBuildTargets (flattenPackageDescription gpd) cabalFilePath haskellFilePath
case buildTargets of
-- If there are no build targets found, run the `cabal-add` command with default behaviour
[] -> pure $ mkCodeActionForDependency cabalFilePath Nothing <$> suggestions
Expand All @@ -263,17 +263,6 @@ addDependencySuggestCodeAction plId verTxtDocId suggestions haskellFilePath caba
-}
buildTargetToStringRepr target = render $ CabalPretty.pretty $ buildTargetComponentName target

{- | Finds the build targets that are used in `cabal-add`.
Note the unorthodox usage of `readBuildTargets`:
If the relative path to the haskell file is provided,
`readBuildTargets` will return the build targets, this
module is mentioned in (either exposed-modules or other-modules).
-}
getBuildTargets :: GenericPackageDescription -> FilePath -> FilePath -> IO [BuildTarget]
getBuildTargets gpd cabalFilePath haskellFilePath = do
let haskellFileRelativePath = makeRelative (dropFileName cabalFilePath) haskellFilePath
readBuildTargets (verboseNoStderr silent) (flattenPackageDescription gpd) [haskellFileRelativePath]

mkCodeActionForDependency :: FilePath -> Maybe String -> (T.Text, T.Text) -> J.CodeAction
mkCodeActionForDependency cabalFilePath target (suggestedDep, suggestedVersion) =
let
Expand All @@ -296,6 +285,17 @@ addDependencySuggestCodeAction plId verTxtDocId suggestions haskellFilePath caba
in
J.CodeAction title (Just CodeActionKind_QuickFix) (Just []) Nothing Nothing Nothing (Just command) Nothing

{- | Finds the build targets that are used in `cabal-add`.
Note the unorthodox usage of `readBuildTargets`:
If the relative path to the haskell file is provided,
`readBuildTargets` will return the build targets, this
module is mentioned in (either exposed-modules or other-modules).
-}
getBuildTargets :: PackageDescription -> FilePath -> FilePath -> IO [BuildTarget]
getBuildTargets pd cabalFilePath haskellFilePath = do
let haskellFileRelativePath = makeRelative (dropFileName cabalFilePath) haskellFilePath
readBuildTargets (verboseNoStderr silent) pd [haskellFileRelativePath]

{- | Gives a mentioned number of @(dependency, version)@ pairs
found in the "hidden package" diagnostic message.

Expand Down
Loading