Skip to content
Merged
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
2 changes: 1 addition & 1 deletion cabal.project
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ packages:
./hls-plugin-api
./hls-test-utils

index-state: 2026-07-11T00:00:00Z
index-state: 2026-07-30T14:41:11Z

tests: True
test-show-details: direct
Expand Down
2 changes: 1 addition & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Here is a list of the additional settings currently supported by `haskell-langua
- Max completions (`haskell.maxCompletions`, default 40): maximum number of completions sent to the LSP client.
- Check project (`haskell.checkProject`, default true): whether to typecheck the entire project on initial load. As it is activated by default could drive to bad performance in large projects.
- Check parents (`haskell.checkParents`, default `CheckOnSave`): when to typecheck reverse dependencies of a file; one of `NeverCheck`, `CheckOnSave` (means dependent/parent modules will only be checked when you save), or `AlwaysCheck` (means re-typechecking them on every change).
- Session loading preference (`haskell.sessionLoading`, default `multipleComponents`): how to load sessions; one of `singleComponent` (means always loading only a single component when a new component is discovered) or `multipleComponents` (means always preferring loading multiple components in the cradle at once). `multipleComponents` might not be always possible, if the tool doesn't support multiple components loading. The cradle can decide how to handle these situations, and whether to honour the preference at all.
- Components loading preference (`haskell.componentsLoading`, default `multi: needed-only`): which components to choose when loading sessions; one of `single` (means always loading only a single component when a new component is discovered), `multi: needed-only` (means preferring loading multiple components in the cradle at once, but only those needed for documents opened), `multi: whole-project` (means preferring loading all the components discovered from the cradle/project; see also `componentsToLoad` cradle config field). `multi: ...` might not be always possible, if the tool doesn't support multiple components loading. The cradle can decide how to handle these situations, and whether to honour the preference at all.

#### Generic plugin configuration

Expand Down
225 changes: 161 additions & 64 deletions ghcide-test/exe/CradleTests.hs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion ghcide/ghcide.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ library
, Glob
, haddock-library >=1.8 && <1.12
, hashable
, hie-bios ^>= 0.19.0
, hie-bios ^>= 0.21.0
, hiedb ^>= 0.8.0.0
, hls-graph == 2.14.0.0
, hls-plugin-api == 2.14.0.0
Expand Down
17 changes: 9 additions & 8 deletions ghcide/session-loader/Development/IDE/Session.hs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ import Ide.Logger (Pretty (pretty),
vcat, viaShow, (<+>))
import Ide.Types (Config,
SessionLoadingPreferenceConfig (..),
sessionLoading)
componentsLoading)
import Language.LSP.Protocol.Message
import Language.LSP.Server
import System.Directory
Expand Down Expand Up @@ -622,11 +622,11 @@ didSessionLoadingPreferenceConfigChange s = do
mLoadingConfig <- liftIO $ readVar biosSessionLoadingVar
case mLoadingConfig of
Nothing -> do
liftIO $ writeVar biosSessionLoadingVar (Just (sessionLoading clientConfig))
liftIO $ writeVar biosSessionLoadingVar (Just (componentsLoading clientConfig))
pure False
Just loadingConfig -> do
liftIO $ writeVar biosSessionLoadingVar (Just (sessionLoading clientConfig))
pure (loadingConfig /= sessionLoading clientConfig)
liftIO $ writeVar biosSessionLoadingVar (Just (componentsLoading clientConfig))
pure (loadingConfig /= componentsLoading clientConfig)

newSessionState :: IO SessionState
newSessionState = do
Expand Down Expand Up @@ -1047,7 +1047,7 @@ loadCradleWithNotifications recorder sessionState hieYaml cfp = do
let progMsg = "Setting up " <> T.pack (takeBaseName (cradleRootDir cradle))
<> " (for " <> T.pack lfpLog <> ")"

sessionPref <- asks (sessionLoading . sessionClientConfig)
sessionPref <- asks (componentsLoading . sessionClientConfig)
extraToLoads <- liftIO $ getExtraFilesToLoad sessionState hieYaml cfp
-- Start loading the file!
eopts <- mRunLspTCallback lspEnv (\act -> withIndefiniteProgress progMsg Nothing NotCancellable (const act)) $
Expand All @@ -1069,7 +1069,7 @@ cradleToOptsAndLibDir recorder loadConfig cradle file old_fps = do
-- noneCradleFoundMessage f = T.pack $ "none cradle found for " <> f <> ", ignoring the file"
-- Start off by getting the session options
logWith recorder Debug $ LogCradle cradle
cradleRes <- HieBios.getCompilerOptions file loadStyle cradle
cradleRes <- HieBios.getCompilerOptions (TargetWithContext file old_fps) loadStyle cradle
case cradleRes of
CradleSuccess r -> do
-- Now get the GHC lib dir
Expand All @@ -1090,8 +1090,9 @@ cradleToOptsAndLibDir recorder loadConfig cradle file old_fps = do

where
loadStyle = case loadConfig of
PreferSingleComponentLoading -> LoadFile
PreferMultiComponentLoading -> LoadWithContext old_fps
PreferSingleComponentLoading -> LoadFile
PreferMultiComponentLoading -> LoadFileWithContext
PreferMultiWholeProjectLoading -> LoadUnitsFromCradle

-- ----------------------------------------------------------------------------
-- Utilities
Expand Down
6 changes: 3 additions & 3 deletions ghcide/session-loader/Development/IDE/Session/Implicit.hs
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,12 @@ stackCradle fp = do
pkgsWithComps <- liftIO $ catMaybes <$> mapM (nestedPkg fp) pkgs
let yaml = fp </> "stack.yaml"
pure $ (,fp) $ case pkgsWithComps of
[] -> Stack (StackType Nothing (Just yaml))
[] -> Stack (StackType Nothing (Just yaml) Nothing)
ps -> StackMulti mempty $ do
Package n cs <- ps
c <- cs
let (prefix, comp) = Implicit.stackComponent n c
pure (prefix, StackType (Just comp) (Just yaml))
pure (prefix, StackType (Just comp) (Just yaml) Nothing)

-- | By default, we generate a simple cabal cradle which is equivalent to the
-- following hie.yaml:
Expand All @@ -95,7 +95,7 @@ stackCradle fp = do
--
-- Note, this only works reliable for reasonably modern cabal versions >= 3.2.
simpleCabalCradle :: FilePath -> (CradleTree a, FilePath)
simpleCabalCradle fp = (Cabal $ CabalType Nothing Nothing, fp)
simpleCabalCradle fp = (Cabal $ CabalType Nothing Nothing Nothing, fp)

cabalExecutable :: MaybeT IO FilePath
cabalExecutable = MaybeT $ findExecutable "cabal"
Expand Down
2 changes: 1 addition & 1 deletion ghcide/src/Development/IDE/Core/Rules.hs
Original file line number Diff line number Diff line change
Expand Up @@ -741,7 +741,7 @@ loadGhcSession recorder ghcSessionDepsConfig = do
[ B.encode (hash (sessionVersion res))
-- When the session version changes, reload all session
-- hsc env sessions
, B.encode (show (sessionLoading config))
, B.encode (show (componentsLoading config))
-- The loading config affects session loading.
-- Invalidate all build nodes.
-- Changing the session loading config will increment
Expand Down
31 changes: 19 additions & 12 deletions hls-plugin-api/src/Ide/Plugin/Config.hs
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,18 @@ module Ide.Plugin.Config
, CheckParents(..)
) where

import Control.Lens (preview)
import Data.Aeson hiding (Error)
import qualified Data.Aeson as A
import Data.Aeson.Lens (_String)
import qualified Data.Aeson.Types as A
import Control.Applicative
import Control.Lens (preview)
import Data.Aeson hiding (Error)
import qualified Data.Aeson as A
import Data.Aeson.Lens (_String)
import qualified Data.Aeson.Types as A
import Data.Default
import qualified Data.Map.Strict as Map
import Data.Maybe (fromMaybe)
import qualified Data.Text as T
import GHC.Exts (toList)
import Data.Functor ((<&>))
import qualified Data.Map.Strict as Map
import Data.Maybe (fromMaybe)
import qualified Data.Text as T
import GHC.Exts (toList)
import Ide.Types

-- ---------------------------------------------------------------------
Expand All @@ -42,11 +44,16 @@ parseConfig idePlugins defValue = A.withObject "settings" $ \o ->
<*> o .:? "formattingProvider" .!= formattingProvider defValue
<*> o .:? "cabalFormattingProvider" .!= cabalFormattingProvider defValue
<*> o .:? "maxCompletions" .!= maxCompletions defValue
<*> o .:? "sessionLoading" .!= sessionLoading defValue
<*> loadingPref o
<*> o .:? "linkSourceTo" .!= linkSourceTo defValue
Comment on lines -45 to 46

@Kleidukos Kleidukos Jul 6, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should definitely not deprecate sessionLoading without warning first. :) Happy to make sure that the next HLS announcement on the Haskell blog includes it.

<*> o .:? "linkDocTo" .!=
linkDocTo defValue
<*> o .:? "linkDocTo" .!= linkDocTo defValue
<*> A.explicitParseFieldMaybe (parsePlugins idePlugins) o "plugin" .!= plugins defValue
where
loadingPref o =
-- We can support "componentsLoading" and the legacy "sessionLoading" option.
(o .:? "componentsLoading" .!= componentsLoading defValue)
<|> (o .:? "sessionLoading" .!= LegacySessionLoadingPreferenceConfig (componentsLoading defValue)
<&> getLegacySessionLoadingPreferenceConfig)

-- | Parse the 'PluginConfig'.
-- Since we need to fall back to default values if we do not find one in the input,
Expand Down
72 changes: 59 additions & 13 deletions hls-plugin-api/src/Ide/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ module Ide.Types
, IdeNotification(..)
, IdePlugins(IdePlugins, ipMap)
, DynFlagsModifications(..)
, Config(..), PluginConfig(..), CheckParents(..), SessionLoadingPreferenceConfig(..)
, Config(..), PluginConfig(..), CheckParents(..)
, SessionLoadingPreferenceConfig(..)
, LegacySessionLoadingPreferenceConfig(..)
, getLegacySessionLoadingPreferenceConfig
, OptLinkTo(..)
, ConfigDescriptor(..), defaultConfigDescriptor, configForPlugin
, CustomConfig(..), mkCustomConfig
Expand Down Expand Up @@ -179,7 +182,7 @@ data Config =
, formattingProvider :: !T.Text
, cabalFormattingProvider :: !T.Text
, maxCompletions :: !Int
, sessionLoading :: !SessionLoadingPreferenceConfig
, componentsLoading :: !SessionLoadingPreferenceConfig
, linkSourceTo :: !OptLinkTo
, linkDocTo :: !OptLinkTo
, plugins :: !(Map.Map PluginId PluginConfig)
Expand All @@ -192,7 +195,7 @@ instance ToJSON Config where
, "formattingProvider" .= formattingProvider
, "cabalFormattingProvider" .= cabalFormattingProvider
, "maxCompletions" .= maxCompletions
, "sessionLoading" .= sessionLoading
, "componentsLoading" .= componentsLoading
, "linkSourceTo" .= linkSourceTo
, "linkDocTo" .= linkDocTo
, "plugin" .= Map.mapKeysMonotonic (\(PluginId p) -> p) plugins
Expand All @@ -208,7 +211,7 @@ instance Default Config where
-- , cabalFormattingProvider = "cabal-fmt"
-- this string value needs to kept in sync with the value provided in HlsPlugins
, maxCompletions = 40
, sessionLoading = PreferMultiComponentLoading
, componentsLoading = PreferMultiComponentLoading
, linkSourceTo = LinkToHackage
, linkDocTo = LinkToHackage
, plugins = mempty
Expand Down Expand Up @@ -240,24 +243,67 @@ data SessionLoadingPreferenceConfig
--
-- The cradle can decide how to handle these situations, and whether
-- to honour the preference at all.
| PreferMultiWholeProjectLoading
-- ^ Prefer loading all the components specified in the cradle, if possible.
deriving stock (Eq, Ord, Show, Generic)

instance Pretty SessionLoadingPreferenceConfig where
pretty PreferSingleComponentLoading = "Prefer Single Component Loading"
pretty PreferMultiComponentLoading = "Prefer Multiple Components Loading"
pretty PreferSingleComponentLoading = "Prefer Single Component Loading"
pretty PreferMultiComponentLoading = "Prefer Multiple Components Loading"
pretty PreferMultiWholeProjectLoading = "Prefer Whole Project Loading"

-- | Labels for @SessionLoadingPreferenceConfig@ json format.
--
-- 'singleComponent' and 'multipleComponents' are outdated but are maintained here
-- for backwards compatibility.
-- We prefer 'single', 'multiNeededOnly' and 'multiWholeProject'
singleComponent, multipleComponents, single, multiNeededOnly, multiWholeProject :: T.Text
singleComponent = "singleComponent"
multipleComponents = "multipleComponents"
single = "single"
multiNeededOnly = "multi: needed-only"
multiWholeProject = "multi: whole-project"

-- | Historical artefact!
-- Before HLS 2.15.0.0, the 'SessionLoadingPreferenceConfig' option was called `sessionLoading` with the values
-- `multipleComponents` and `singleComponent`.
--
-- With HLS 2.15.0.0, we renamed these options and also added some new ones!
-- For backwards compatibility, we support the old naming as well using
-- this backwards compatibility newtype.
newtype LegacySessionLoadingPreferenceConfig = LegacySessionLoadingPreferenceConfig SessionLoadingPreferenceConfig

getLegacySessionLoadingPreferenceConfig :: LegacySessionLoadingPreferenceConfig -> SessionLoadingPreferenceConfig
getLegacySessionLoadingPreferenceConfig (LegacySessionLoadingPreferenceConfig conf) = conf

instance ToJSON SessionLoadingPreferenceConfig where
toJSON PreferSingleComponentLoading =
String "singleComponent"
String single
toJSON PreferMultiComponentLoading =
String "multipleComponents"
String multiNeededOnly
toJSON PreferMultiWholeProjectLoading =
String multiWholeProject

instance FromJSON SessionLoadingPreferenceConfig where
parseJSON (String val) = case val of
"singleComponent" -> pure PreferSingleComponentLoading
"multipleComponents" -> pure PreferMultiComponentLoading
_ -> A.prependFailure "parsing SessionLoadingPreferenceConfig failed, "
(A.parseFail $ "Expected one of \"singleComponent\" or \"multipleComponents\" but got " <> T.unpack val )
parseJSON (String val)
| single == val = pure PreferSingleComponentLoading
| multiNeededOnly == val = pure PreferMultiComponentLoading
| multiWholeProject == val = pure PreferMultiWholeProjectLoading
| otherwise = A.prependFailure "parsing SessionLoadingPreferenceConfig failed, "
(A.parseFail $ unwords ["Expected one of " ++ expected ++ " but got", T.unpack val] )
where
expected = T.unpack $ T.intercalate ", " $ map (\ t -> "\'" <> t <> "\'") [single, multiNeededOnly, multiWholeProject]
parseJSON o = A.prependFailure "parsing SessionLoadingPreferenceConfig failed, "
(A.typeMismatch "String" o)

instance FromJSON LegacySessionLoadingPreferenceConfig where
parseJSON (String val)
| singleComponent == val = pure $ LegacySessionLoadingPreferenceConfig PreferSingleComponentLoading
| multipleComponents == val = pure $ LegacySessionLoadingPreferenceConfig PreferMultiComponentLoading
| otherwise = A.prependFailure "parsing SessionLoadingPreferenceConfig failed, "
(A.parseFail $ "Expected one of " ++ expected ++ " but got " <> T.unpack val )
where
expected = T.unpack $ T.intercalate ", " $ map (\ t -> "\'" <> t <> "\'") [singleComponent, multipleComponents]
parseJSON o = A.prependFailure "parsing SessionLoadingPreferenceConfig failed, "
(A.typeMismatch "String" o)

Expand Down
2 changes: 1 addition & 1 deletion stack-lts24.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ extra-deps:
- hiedb-0.8.0.0
- hie-compat-0.3.1.2
- implicit-hie-0.1.4.0
- hie-bios-0.19.0
- hie-bios-0.21.0
- hw-fingertree-0.1.2.1
- monad-dijkstra-0.1.1.5
- unordered-containers-0.2.21
Expand Down
2 changes: 1 addition & 1 deletion stack.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ extra-deps:
- hiedb-0.8.0.0
- hie-compat-0.3.1.2
- implicit-hie-0.1.4.0
- hie-bios-0.19.0
- hie-bios-0.21.0
- hw-fingertree-0.1.2.1
- monad-dijkstra-0.1.1.5
- unordered-containers-0.2.21
Expand Down
4 changes: 2 additions & 2 deletions test/testdata/schema/ghc910/default-config.golden.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"cabalFormattingProvider": "cabal-gild",
"checkParents": "CheckOnSave",
"checkProject": true,
"componentsLoading": "multi: needed-only",
"formattingProvider": "ormolu",
"linkDocTo": "LinkToHackage",
"linkSourceTo": "LinkToHackage",
Expand Down Expand Up @@ -159,6 +160,5 @@
"stan": {
"globalOn": false
}
},
"sessionLoading": "multipleComponents"
}
}
4 changes: 2 additions & 2 deletions test/testdata/schema/ghc912/default-config.golden.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"cabalFormattingProvider": "cabal-gild",
"checkParents": "CheckOnSave",
"checkProject": true,
"componentsLoading": "multi: needed-only",
"formattingProvider": "ormolu",
"linkDocTo": "LinkToHackage",
"linkSourceTo": "LinkToHackage",
Expand Down Expand Up @@ -163,6 +164,5 @@
"splice": {
"globalOn": true
}
},
"sessionLoading": "multipleComponents"
}
}
4 changes: 2 additions & 2 deletions test/testdata/schema/ghc914/default-config.golden.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"cabalFormattingProvider": "cabal-gild",
"checkParents": "CheckOnSave",
"checkProject": true,
"componentsLoading": "multi: needed-only",
"formattingProvider": "ormolu",
"linkDocTo": "LinkToHackage",
"linkSourceTo": "LinkToHackage",
Expand Down Expand Up @@ -156,6 +157,5 @@
"splice": {
"globalOn": true
}
},
"sessionLoading": "multipleComponents"
}
}
4 changes: 2 additions & 2 deletions test/testdata/schema/ghc96/default-config.golden.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"cabalFormattingProvider": "cabal-gild",
"checkParents": "CheckOnSave",
"checkProject": true,
"componentsLoading": "multi: needed-only",
"formattingProvider": "ormolu",
"linkDocTo": "LinkToHackage",
"linkSourceTo": "LinkToHackage",
Expand Down Expand Up @@ -166,6 +167,5 @@
"stan": {
"globalOn": false
}
},
"sessionLoading": "multipleComponents"
}
}
4 changes: 2 additions & 2 deletions test/testdata/schema/ghc98/default-config.golden.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"cabalFormattingProvider": "cabal-gild",
"checkParents": "CheckOnSave",
"checkProject": true,
"componentsLoading": "multi: needed-only",
"formattingProvider": "ormolu",
"linkDocTo": "LinkToHackage",
"linkSourceTo": "LinkToHackage",
Expand Down Expand Up @@ -166,6 +167,5 @@
"stan": {
"globalOn": false
}
},
"sessionLoading": "multipleComponents"
}
}
Loading