diff --git a/exe/Main.hs b/exe/Main.hs
--- a/exe/Main.hs
+++ b/exe/Main.hs
@@ -100,7 +100,7 @@
     (lspMessageRecorder, cb2) <- Logger.withBacklog Logger.lspClientMessageRecorder
     -- This plugin just installs a handler for the `initialized` notification, which then
     -- picks up the LSP environment and feeds it to our recorders
-    let lspRecorderPlugin = (defaultPluginDescriptor "LSPRecorderCallback")
+    let lspRecorderPlugin = (defaultPluginDescriptor "LSPRecorderCallback" "Internal plugin")
           { pluginNotificationHandlers = mkPluginNotificationHandler LSP.SMethod_Initialized $ \_ _ _ _ -> do
               env <- LSP.getLspEnv
               liftIO $ (cb1 <> cb2) env
diff --git a/ghcide.cabal b/ghcide.cabal
--- a/ghcide.cabal
+++ b/ghcide.cabal
@@ -2,7 +2,7 @@
 build-type:         Simple
 category:           Development
 name:               ghcide
-version:            2.5.0.0
+version:            2.6.0.0
 license:            Apache-2.0
 license-file:       LICENSE
 author:             Digital Asset and Ghcide contributors
@@ -76,13 +76,12 @@
     , Glob
     , haddock-library              >=1.8      && <1.12
     , hashable
-    , hie-bios                     ==0.12.1
+    , hie-bios                     ==0.13.1
     , hie-compat                   ^>=0.3.0.0
-    , hiedb                        >=0.4.4    && <0.4.5
-    , hls-graph                    == 2.5.0.0
-    , hls-plugin-api               == 2.5.0.0
-    , implicit-hie                 <0.1.3
-    , implicit-hie-cradle          ^>=0.3.0.5 || ^>=0.5
+    , hiedb                        ^>= 0.5.0.1
+    , hls-graph                    == 2.6.0.0
+    , hls-plugin-api               == 2.6.0.0
+    , implicit-hie                 >= 0.1.4.0 && < 0.1.5
     , lens
     , list-t
     , lsp                          ^>=2.3.0.0
@@ -111,10 +110,6 @@
     , unordered-containers         >=0.2.10.0
     , vector
 
-  -- implicit-hie 0.1.3.0 introduced an unexpected behavioral change.
-  -- https://github.com/Avi-D-coder/implicit-hie/issues/50
-  -- to make sure ghcide behaves in a desirable way, we put implicit-hie
-  -- fake dependency here.
   if os(windows)
     build-depends: Win32
 
@@ -165,6 +160,7 @@
     Development.IDE.Core.UseStale
     Development.IDE.GHC.Compat
     Development.IDE.GHC.Compat.Core
+    Development.IDE.GHC.Compat.CmdLine
     Development.IDE.GHC.Compat.Env
     Development.IDE.GHC.Compat.Iface
     Development.IDE.GHC.Compat.Logger
@@ -197,6 +193,7 @@
     Development.IDE.Plugin.TypeLenses
     Development.IDE.Session
     Development.IDE.Session.Diagnostics
+    Development.IDE.Session.Implicit
     Development.IDE.Spans.AtPoint
     Development.IDE.Spans.Common
     Development.IDE.Spans.Documentation
@@ -339,6 +336,7 @@
     , containers
     , data-default
     , directory
+    , enummapset
     , extra
     , filepath
     , fuzzy
diff --git a/session-loader/Development/IDE/Session.hs b/session-loader/Development/IDE/Session.hs
--- a/session-loader/Development/IDE/Session.hs
+++ b/session-loader/Development/IDE/Session.hs
@@ -39,7 +39,9 @@
 import           Data.Function
 import           Data.Hashable                        hiding (hash)
 import qualified Data.HashMap.Strict                  as HM
+import           Data.IORef
 import           Data.List
+import           Data.List.Extra                      as L
 import           Data.List.NonEmpty                   (NonEmpty (..))
 import qualified Data.List.NonEmpty                   as NE
 import qualified Data.Map.Strict                      as Map
@@ -72,7 +74,6 @@
 import           HIE.Bios.Environment                 hiding (getCacheDir)
 import           HIE.Bios.Types                       hiding (Log)
 import qualified HIE.Bios.Types                       as HieBios
-import           Hie.Implicit.Cradle                  (loadImplicitHieCradle)
 import           Ide.Logger                           (Pretty (pretty),
                                                        Priority (Debug, Error, Info, Warning),
                                                        Recorder, WithPriority,
@@ -110,12 +111,25 @@
 import qualified System.Random                        as Random
 import           System.Random                        (RandomGen)
 
+import qualified Development.IDE.Session.Implicit     as GhcIde
+
+import           Development.IDE.GHC.Compat.CmdLine
+
+
 -- See Note [Guidelines For Using CPP In GHCIDE Import Statements]
+#if MIN_VERSION_ghc(9,3,0)
+import qualified Data.Set                             as OS
 
-#if !MIN_VERSION_ghc(9,4,0)
-import           Data.IORef
+import           GHC.Data.Bag
+import           GHC.Driver.Env                       (hsc_all_home_unit_ids)
+import           GHC.Driver.Errors.Types
+import           GHC.Driver.Make                      (checkHomeUnitsClosed)
+import           GHC.Types.Error                      (errMsgDiagnostic)
+import           GHC.Unit.State
 #endif
 
+import           GHC.ResponseFile
+
 data Log
   = LogSettingInitialDynFlags
   | LogGetInitialGhcLibDirDefaultCradleFail !CradleError !FilePath !(Maybe FilePath) !(Cradle Void)
@@ -218,7 +232,7 @@
   -- | Load the cradle with an optional 'hie.yaml' location.
   -- If a 'hie.yaml' is given, use it to load the cradle.
   -- Otherwise, use the provided project root directory to determine the cradle type.
-  , loadCradle             :: Maybe FilePath -> FilePath -> IO (HieBios.Cradle Void)
+  , loadCradle             :: Recorder (WithPriority Log) -> Maybe FilePath -> FilePath -> IO (HieBios.Cradle Void)
   -- | Given the project name and a set of command line flags,
   --   return the path for storing generated GHC artifacts,
   --   or 'Nothing' to respect the cradle setting
@@ -255,22 +269,25 @@
 -- using the provided root directory for discovering the project.
 -- The implicit config uses different heuristics to determine the type
 -- of the project that may or may not be accurate.
-loadWithImplicitCradle :: Maybe FilePath
-                          -- ^ Optional 'hie.yaml' location. Will be used if given.
-                          -> FilePath
-                          -- ^ Root directory of the project. Required as a fallback
-                          -- if no 'hie.yaml' location is given.
-                          -> IO (HieBios.Cradle Void)
-loadWithImplicitCradle mHieYaml rootDir = do
+loadWithImplicitCradle
+  :: Recorder (WithPriority Log)
+  -> Maybe FilePath
+  -- ^ Optional 'hie.yaml' location. Will be used if given.
+  -> FilePath
+  -- ^ Root directory of the project. Required as a fallback
+  -- if no 'hie.yaml' location is given.
+  -> IO (HieBios.Cradle Void)
+loadWithImplicitCradle recorder mHieYaml rootDir = do
+  let logger = toCologActionWithPrio (cmapWithPrio LogHieBios recorder)
   case mHieYaml of
-    Just yaml -> HieBios.loadCradle yaml
-    Nothing   -> loadImplicitHieCradle $ addTrailingPathSeparator rootDir
+    Just yaml -> HieBios.loadCradle logger yaml
+    Nothing   -> GhcIde.loadImplicitCradle logger rootDir
 
 getInitialGhcLibDirDefault :: Recorder (WithPriority Log) -> FilePath -> IO (Maybe LibDir)
 getInitialGhcLibDirDefault recorder rootDir = do
   hieYaml <- findCradle def (rootDir </> "a")
-  cradle <- loadCradle def hieYaml rootDir
-  libDirRes <- getRuntimeGhcLibDir (toCologActionWithPrio (cmapWithPrio LogHieBios recorder)) cradle
+  cradle <- loadCradle def recorder hieYaml rootDir
+  libDirRes <- getRuntimeGhcLibDir cradle
   case libDirRes of
       CradleSuccess libdir -> pure $ Just $ LibDir libdir
       CradleFail err -> do
@@ -420,6 +437,7 @@
 
 loadSessionWithOptions :: Recorder (WithPriority Log) -> SessionLoadingOptions -> FilePath -> IO (Action IdeGhcSession)
 loadSessionWithOptions recorder SessionLoadingOptions{..} dir = do
+  cradle_files <- newIORef []
   -- Mapping from hie.yaml file to HscEnv, one per hie.yaml file
   hscEnvs <- newVar Map.empty :: IO (Var HieMap)
   -- Mapping from a Filepath to HscEnv
@@ -461,12 +479,28 @@
         -- files in the project so that `knownFiles` can learn about them and
         -- we can generate a complete module graph
     let extendKnownTargets newTargets = do
-          knownTargets <- forM newTargets $ \TargetDetails{..} ->
+          knownTargets <- concatForM  newTargets $ \TargetDetails{..} ->
             case targetTarget of
-              TargetFile f -> pure (targetTarget, [f])
+              TargetFile f -> do
+                -- If a target file has multiple possible locations, then we
+                -- assume they are all separate file targets.
+                -- This happens with '.hs-boot' files if they are in the root directory of the project.
+                -- GHC reports options such as '-i. A' as 'TargetFile A.hs' instead of 'TargetModule A'.
+                -- In 'fromTargetId', we dutifully look for '.hs-boot' files and add them to the
+                -- targetLocations of the TargetDetails. Then we add everything to the 'knownTargetsVar'.
+                -- However, when we look for a 'Foo.hs-boot' file in 'FindImports.hs', we look for either
+                --
+                --  * TargetFile Foo.hs-boot
+                --  * TargetModule Foo
+                --
+                -- If we don't generate a TargetFile for each potential location, we will only have
+                -- 'TargetFile Foo.hs' in the 'knownTargetsVar', thus not find 'TargetFile Foo.hs-boot'
+                -- and also not find 'TargetModule Foo'.
+                fs <- filterM (IO.doesFileExist . fromNormalizedFilePath) targetLocations
+                pure $ map (\fp -> (TargetFile fp, [fp])) (nubOrd (f:fs))
               TargetModule _ -> do
                 found <- filterM (IO.doesFileExist . fromNormalizedFilePath) targetLocations
-                return (targetTarget, found)
+                return [(targetTarget, found)]
           hasUpdate <- join $ atomically $ do
             known <- readTVar knownTargetsVar
             let known' = flip mapHashed known $ \k ->
@@ -479,33 +513,12 @@
             logWith recorder Debug $ LogKnownFilesUpdated x
 
     -- Create a new HscEnv from a hieYaml root and a set of options
-    -- If the hieYaml file already has an HscEnv, the new component is
-    -- combined with the components in the old HscEnv into a new HscEnv
-    -- which contains the union.
     let packageSetup :: (Maybe FilePath, NormalizedFilePath, ComponentOptions, FilePath)
-                     -> IO (HscEnv, ComponentInfo, [ComponentInfo])
+                     -> IO ([ComponentInfo], [ComponentInfo])
         packageSetup (hieYaml, cfp, opts, libDir) = do
           -- Parse DynFlags for the newly discovered component
           hscEnv <- emptyHscEnv ideNc libDir
-          (df', targets) <- evalGhcEnv hscEnv $ setOptions opts (hsc_dflags hscEnv)
-          let df =
-#if MIN_VERSION_ghc(9,3,0)
-                case unitIdString (homeUnitId_ df') of
-                     -- cabal uses main for the unit id of all executable packages
-                     -- This makes multi-component sessions confused about what
-                     -- options to use for that component.
-                     -- Solution: hash the options and use that as part of the unit id
-                     -- This works because there won't be any dependencies on the
-                     -- executable unit.
-                     "main" ->
-                       let hash = B.unpack $ B16.encode $ H.finalize $ H.updates H.init (map B.pack $ componentOptions opts)
-                           hashed_uid = Compat.toUnitId (Compat.stringToUnit ("main-"++hash))
-                       in setHomeUnitId_ hashed_uid df'
-                     _ -> df'
-#else
-                df'
-#endif
-
+          newTargetDfs <- evalGhcEnv hscEnv $ setOptions cfp opts (hsc_dflags hscEnv)
           let deps = componentDependencies opts ++ maybeToList hieYaml
           dep_info <- getDependencyInfo deps
           -- Now lookup to see whether we are combining with an existing HscEnv
@@ -520,19 +533,18 @@
                   -- We will modify the unitId and DynFlags used for
                   -- compilation but these are the true source of
                   -- information.
-
-                  new_deps = RawComponentInfo (homeUnitId_ df) df targets cfp opts dep_info
-                                :| maybe [] snd oldDeps
+                  new_deps = fmap (\(df, targets) -> RawComponentInfo (homeUnitId_ df) df targets cfp opts dep_info) newTargetDfs
+                  all_deps = new_deps `NE.appendList` maybe [] id oldDeps
                   -- Get all the unit-ids for things in this component
-                  inplace = map rawComponentUnitId $ NE.toList new_deps
+                  _inplace = map rawComponentUnitId $ NE.toList all_deps
 
-              new_deps' <- forM new_deps $ \RawComponentInfo{..} -> do
+              all_deps' <- forM all_deps $ \RawComponentInfo{..} -> do
                   -- Remove all inplace dependencies from package flags for
                   -- components in this HscEnv
 #if MIN_VERSION_ghc(9,3,0)
                   let (df2, uids) = (rawComponentDynFlags, [])
 #else
-                  let (df2, uids) = _removeInplacePackages fakeUid inplace rawComponentDynFlags
+                  let (df2, uids) = _removeInplacePackages fakeUid _inplace rawComponentDynFlags
 #endif
                   let prefix = show rawComponentUnitId
                   -- See Note [Avoiding bad interface files]
@@ -543,80 +555,44 @@
                   -- The final component information, mostly the same but the DynFlags don't
                   -- contain any packages which are also loaded
                   -- into the same component.
-                  pure $ ComponentInfo rawComponentUnitId
-                                       processed_df
-                                       uids
-                                       rawComponentTargets
-                                       rawComponentFP
-                                       rawComponentCOptions
-                                       rawComponentDependencyInfo
-              -- Make a new HscEnv, we have to recompile everything from
-              -- scratch again (for now)
-              -- It's important to keep the same NameCache though for reasons
-              -- that I do not fully understand
-              logWith recorder Info $ LogMakingNewHscEnv inplace
-              hscEnvB <- emptyHscEnv ideNc libDir
-              !newHscEnv <-
-                -- Add the options for the current component to the HscEnv
-                evalGhcEnv hscEnvB $ do
-                  _ <- setSessionDynFlags
-#if !MIN_VERSION_ghc(9,3,0)
-                          $ setHomeUnitId_ fakeUid
-#endif
-                          df
-                  getSession
-
-              -- Modify the map so the hieYaml now maps to the newly created
-              -- HscEnv
+                  pure $ ComponentInfo
+                           { componentUnitId = rawComponentUnitId
+                           , componentDynFlags = processed_df
+                           , componentInternalUnits = uids
+                           , componentTargets = rawComponentTargets
+                           , componentFP = rawComponentFP
+                           , componentCOptions = rawComponentCOptions
+                           , componentDependencyInfo = rawComponentDependencyInfo
+                           }
+              -- Modify the map so the hieYaml now maps to the newly updated
+              -- ComponentInfos
               -- Returns
-              -- . the new HscEnv so it can be used to modify the
-              --   FilePath -> HscEnv map (fileToFlags)
               -- . The information for the new component which caused this cache miss
               -- . The modified information (without -inplace flags) for
               --   existing packages
-              pure (Map.insert hieYaml (newHscEnv, NE.toList new_deps) m, (newHscEnv, NE.head new_deps', NE.tail new_deps'))
+              let (new,old) = NE.splitAt (NE.length new_deps) all_deps'
+              pure (Map.insert hieYaml (NE.toList all_deps) m, (new,old))
 
 
     let session :: (Maybe FilePath, NormalizedFilePath, ComponentOptions, FilePath)
                 -> IO (IdeResult HscEnvEq,[FilePath])
         session args@(hieYaml, _cfp, _opts, _libDir) = do
-          (hscEnv, new, old_deps) <- packageSetup args
-
-          -- Whenever we spin up a session on Linux, dynamically load libm.so.6
-          -- in. We need this in case the binary is statically linked, in which
-          -- case the interactive session will fail when trying to load
-          -- ghc-prim, which happens whenever Template Haskell is being
-          -- evaluated or haskell-language-server's eval plugin tries to run
-          -- some code. If the binary is dynamically linked, then this will have
-          -- no effect.
-          -- See https://github.com/haskell/haskell-language-server/issues/221
-          when (os == "linux") $ do
-            initObjLinker hscEnv
-            res <- loadDLL hscEnv "libm.so.6"
-            case res of
-              Nothing  -> pure ()
-              Just err -> logWith recorder Error $ LogDLLLoadError err
-
-
-          -- Make a map from unit-id to DynFlags, this is used when trying to
-          -- resolve imports. (especially PackageImports)
-          let uids = map (\ci -> (componentUnitId ci, componentDynFlags ci)) (new : old_deps)
+          (new_deps, old_deps) <- packageSetup args
 
           -- For each component, now make a new HscEnvEq which contains the
           -- HscEnv for the hie.yaml file but the DynFlags for that component
+          -- For GHC's supporting multi component sessions, we create a shared
+          -- HscEnv but set the active component accordingly
+          hscEnv <- emptyHscEnv ideNc _libDir
+          let new_cache = newComponentCache recorder optExtensions hieYaml _cfp hscEnv
+          all_target_details <- new_cache old_deps new_deps
 
-          -- New HscEnv for the component in question, returns the new HscEnvEq and
-          -- a mapping from FilePath to the newly created HscEnvEq.
-          let new_cache = newComponentCache recorder optExtensions hieYaml _cfp hscEnv uids
-          (cs, res) <- new_cache new
-          -- Modified cache targets for everything else in the hie.yaml file
-          -- which now uses the same EPS and so on
-          cached_targets <- concatMapM (fmap fst . new_cache) old_deps
+          let all_targets = concatMap fst all_target_details
 
-          let all_targets = cs ++ cached_targets
+          let this_flags_map = HM.fromList (concatMap toFlagsMap all_targets)
 
           void $ modifyVar' fileToFlags $
-              Map.insert hieYaml (HM.fromList (concatMap toFlagsMap all_targets))
+              Map.insert hieYaml this_flags_map
           void $ modifyVar' filesMap $
               flip HM.union (HM.fromList (zip (map fst $ concatMap toFlagsMap all_targets) (repeat hieYaml)))
 
@@ -630,8 +606,8 @@
 
           -- Typecheck all files in the project on startup
           checkProject <- getCheckProject
-          unless (null cs || not checkProject) $ do
-                cfps' <- liftIO $ filterM (IO.doesFileExist . fromNormalizedFilePath) (concatMap targetLocations cs)
+          unless (null new_deps || not checkProject) $ do
+                cfps' <- liftIO $ filterM (IO.doesFileExist . fromNormalizedFilePath) (concatMap targetLocations all_targets)
                 void $ shakeEnqueue extras $ mkDelayedAction "InitialLoad" Debug $ void $ do
                     mmt <- uses GetModificationTime cfps'
                     let cs_exist = catMaybes (zipWith (<$) cfps' mmt)
@@ -641,7 +617,7 @@
                     let !exportsMap' = createExportsMap $ mapMaybe (fmap hirModIface) modIfaces
                     liftIO $ atomically $ modifyTVar' (exportsMap shakeExtras) (exportsMap' <>)
 
-          return (second Map.keys res)
+          return $ second Map.keys $ this_flags_map HM.! _cfp
 
     let consultCradle :: Maybe FilePath -> FilePath -> IO (IdeResult HscEnvEq, [FilePath])
         consultCradle hieYaml cfp = do
@@ -651,7 +627,7 @@
            when (isNothing hieYaml) $
              logWith recorder Warning $ LogCradleNotFound lfpLog
 
-           cradle <- loadCradle hieYaml dir
+           cradle <- loadCradle recorder hieYaml dir
            -- TODO: Why are we repeating the same command we have on line 646?
            lfp <- flip makeRelative cfp <$> getCurrentDirectory
 
@@ -664,7 +640,8 @@
            eopts <- mRunLspTCallback lspEnv (withIndefiniteProgress progMsg NotCancellable) $
               withTrace "Load cradle" $ \addTag -> do
                   addTag "file" lfp
-                  res <- cradleToOptsAndLibDir recorder cradle cfp
+                  old_files <- readIORef cradle_files
+                  res <- cradleToOptsAndLibDir recorder cradle cfp old_files
                   addTag "result" (show res)
                   return res
 
@@ -679,7 +656,8 @@
                      error $ "GHC installation not found in libdir: " <> libdir
                  InstallationMismatch{..} ->
                      return (([renderPackageSetupException cfp GhcVersionMismatch{..}], Nothing),[])
-                 InstallationChecked _compileTime _ghcLibCheck ->
+                 InstallationChecked _compileTime _ghcLibCheck -> do
+                   atomicModifyIORef' cradle_files (\xs -> (cfp:xs,()))
                    session (hieYaml, toNormalizedFilePath' cfp, opts, libDir)
              -- Failure case, either a cradle error or the none cradle
              Left err -> do
@@ -707,7 +685,7 @@
                   -- again.
                   modifyVar_ fileToFlags (const (return Map.empty))
                   -- Keep the same name cache
-                  modifyVar_ hscEnvs (return . Map.adjust (\(h, _) -> (h, [])) hieYaml )
+                  modifyVar_ hscEnvs (return . Map.adjust (\_ -> []) hieYaml )
                   consultCradle hieYaml cfp
                 else return (opts, Map.keys old_di)
             Nothing -> consultCradle hieYaml cfp
@@ -735,19 +713,18 @@
 -- | Run the specific cradle on a specific FilePath via hie-bios.
 -- This then builds dependencies or whatever based on the cradle, gets the
 -- GHC options/dynflags needed for the session and the GHC library directory
-cradleToOptsAndLibDir :: Recorder (WithPriority Log) -> Cradle Void -> FilePath
+cradleToOptsAndLibDir :: Recorder (WithPriority Log) -> Cradle Void -> FilePath -> [FilePath]
                       -> IO (Either [CradleError] (ComponentOptions, FilePath))
-cradleToOptsAndLibDir recorder cradle file = do
+cradleToOptsAndLibDir recorder cradle file old_files = do
     -- let noneCradleFoundMessage :: FilePath -> T.Text
     --     noneCradleFoundMessage f = T.pack $ "none cradle found for " <> f <> ", ignoring the file"
     -- Start off by getting the session options
     logWith recorder Debug $ LogCradle cradle
-    let logger = toCologActionWithPrio $ cmapWithPrio LogHieBios recorder
-    cradleRes <- HieBios.getCompilerOptions logger file cradle
+    cradleRes <- HieBios.getCompilerOptions file old_files cradle
     case cradleRes of
         CradleSuccess r -> do
             -- Now get the GHC lib dir
-            libDirRes <- getRuntimeGhcLibDir logger cradle
+            libDirRes <- getRuntimeGhcLibDir cradle
             case libDirRes of
                 -- This is the successful path
                 CradleSuccess libDir -> pure (Right (r, libDir))
@@ -767,8 +744,18 @@
 emptyHscEnv :: IORef NameCache -> FilePath -> IO HscEnv
 #endif
 emptyHscEnv nc libDir = do
-    env <- runGhc (Just libDir) getSession
+    -- We call setSessionDynFlags so that the loader is initialised
+    -- We need to do this before we call initUnits.
+    env <- runGhc (Just libDir) $
+      getSessionDynFlags >>= setSessionDynFlags >> getSession
+    -- On GHC 9.2 calling setSessionDynFlags caches the unit databases
+    -- for an empty environment. This prevents us from reading the
+    -- package database subsequently. So clear the unit db cache in
+    -- hsc_unit_dbs
     pure $ setNameCache nc (hscSetFlags ((hsc_dflags env){useUnicode = True }) env)
+#if !MIN_VERSION_ghc(9,3,0)
+              {hsc_unit_dbs = Nothing}
+#endif
 
 data TargetDetails = TargetDetails
   {
@@ -796,7 +783,10 @@
 -- For a 'TargetFile' we consider all the possible module names
 fromTargetId _ _ (GHC.TargetFile f _) env deps = do
     nf <- toNormalizedFilePath' <$> makeAbsolute f
-    return [TargetDetails (TargetFile nf) env deps [nf]]
+    let other
+          | "-boot" `isSuffixOf` f = toNormalizedFilePath' (L.dropEnd 5 $ fromNormalizedFilePath nf)
+          | otherwise = toNormalizedFilePath' (fromNormalizedFilePath nf ++ "-boot")
+    return [TargetDetails (TargetFile nf) env deps [nf, other]]
 
 toFlagsMap :: TargetDetails -> [(NormalizedFilePath, (IdeResult HscEnvEq, DependencyInfo))]
 toFlagsMap TargetDetails{..} =
@@ -811,52 +801,97 @@
 setNameCache nc hsc = hsc { hsc_NC = nc }
 
 -- | Create a mapping from FilePaths to HscEnvEqs
+-- This combines all the components we know about into
+-- an appropriate session, which is a multi component
+-- session on GHC 9.4+
 newComponentCache
          :: Recorder (WithPriority Log)
-         -> [String]       -- File extensions to consider
-         -> Maybe FilePath -- Path to cradle
-         -> NormalizedFilePath -- Path to file that caused the creation of this component
-         -> HscEnv
-         -> [(UnitId, DynFlags)]
-         -> ComponentInfo
-         -> IO ( [TargetDetails], (IdeResult HscEnvEq, DependencyInfo))
-newComponentCache recorder exts cradlePath cfp hsc_env uids ci = do
-    let df = componentDynFlags ci
-    hscEnv' <-
+         -> [String]           -- ^ File extensions to consider
+         -> Maybe FilePath     -- ^ Path to cradle
+         -> NormalizedFilePath -- ^ Path to file that caused the creation of this component
+         -> HscEnv             -- ^ An empty HscEnv
+         -> [ComponentInfo]    -- ^ New components to be loaded
+         -> [ComponentInfo]    -- ^ old, already existing components
+         -> IO [ ([TargetDetails], (IdeResult HscEnvEq, DependencyInfo))]
+newComponentCache recorder exts cradlePath _cfp hsc_env old_cis new_cis = do
+    let cis = Map.unionWith unionCIs (mkMap new_cis) (mkMap old_cis)
+        -- When we have multiple components with the same uid,
+        -- prefer the new one over the old.
+        -- However, we might have added some targets to the old unit
+        -- (see special target), so preserve those
+        unionCIs new_ci old_ci = new_ci { componentTargets = componentTargets new_ci ++ componentTargets old_ci }
+        mkMap = Map.fromListWith unionCIs . map (\ci -> (componentUnitId ci, ci))
+    let dfs = map componentDynFlags $ Map.elems cis
+        uids = Map.keys cis
+    logWith recorder Info $ LogMakingNewHscEnv uids
+    hscEnv' <- -- Set up a multi component session with the other units on GHC 9.4
+              Compat.initUnits dfs hsc_env
+
 #if MIN_VERSION_ghc(9,3,0)
-      -- Set up a multi component session with the other units on GHC 9.4
-        Compat.initUnits (map snd uids) (hscSetFlags df hsc_env)
+    let closure_errs = checkHomeUnitsClosed (hsc_unit_env hscEnv') (hsc_all_home_unit_ids hscEnv') pkg_deps
+        pkg_deps = do
+          home_unit_id <- uids
+          home_unit_env <- maybeToList $ unitEnv_lookup_maybe home_unit_id $ hsc_HUG hscEnv'
+          map (home_unit_id,) (map (Compat.toUnitId . fst) $ explicitUnits $ homeUnitEnv_units home_unit_env)
+        multi_errs = map (ideErrorWithSource (Just "cradle") (Just DiagnosticSeverity_Warning) _cfp . T.pack . Compat.printWithoutUniques) closure_errs
+        bad_units = OS.fromList $ concat $ do
+            x <- bagToList $ mapBag errMsgDiagnostic $ unionManyBags $ map Compat.getMessages closure_errs
+            DriverHomePackagesNotClosed us <- pure x
+            pure us
+        isBad ci = (homeUnitId_ (componentDynFlags ci)) `OS.member` bad_units
 #else
-      -- This initializes the units for GHC 9.2
-      -- Add the options for the current component to the HscEnv
-      -- We want to call `setSessionDynFlags` instead of `hscSetFlags`
-      -- because `setSessionDynFlags` also initializes the package database,
-      -- which we need for any changes to the package flags in the dynflags
-      -- to be visible.
-      -- See #2693
-      evalGhcEnv hsc_env $ do
-        _ <- setSessionDynFlags $ df
-        getSession
+    let isBad = const False
+        multi_errs = []
 #endif
+    -- Whenever we spin up a session on Linux, dynamically load libm.so.6
+    -- in. We need this in case the binary is statically linked, in which
+    -- case the interactive session will fail when trying to load
+    -- ghc-prim, which happens whenever Template Haskell is being
+    -- evaluated or haskell-language-server's eval plugin tries to run
+    -- some code. If the binary is dynamically linked, then this will have
+    -- no effect.
+    -- See https://github.com/haskell/haskell-language-server/issues/221
+    -- We need to do this after the call to setSessionDynFlags initialises
+    -- the loader
+    when (os == "linux") $ do
+      initObjLinker hscEnv'
+      res <- loadDLL hscEnv' "libm.so.6"
+      case res of
+        Nothing  -> pure ()
+        Just err -> logWith recorder Error $ LogDLLLoadError err
 
-    let newFunc = maybe newHscEnvEqPreserveImportPaths newHscEnvEq cradlePath
-    henv <- newFunc hscEnv' uids
-    let targetEnv = ([], Just henv)
-        targetDepends = componentDependencyInfo ci
-        res = (targetEnv, targetDepends)
-    logWith recorder Debug $ LogNewComponentCache res
-    evaluate $ liftRnf rwhnf $ componentTargets ci
+    forM (Map.elems cis) $ \ci -> do
+      let df = componentDynFlags ci
+      let createHscEnvEq = maybe newHscEnvEqPreserveImportPaths newHscEnvEq cradlePath
+      thisEnv <- do
+#if MIN_VERSION_ghc(9,3,0)
+            -- In GHC 9.4 we have multi component support, and we have initialised all the units
+            -- above.
+            -- We just need to set the current unit here
+            pure $ hscSetActiveUnitId (homeUnitId_ df) hscEnv'
+#else
+            -- This initializes the units for GHC 9.2
+            -- Add the options for the current component to the HscEnv
+            -- We want to call `setSessionDynFlags` instead of `hscSetFlags`
+            -- because `setSessionDynFlags` also initializes the package database,
+            -- which we need for any changes to the package flags in the dynflags
+            -- to be visible.
+            -- See #2693
+            evalGhcEnv hscEnv' $ do
+              _ <- setSessionDynFlags df
+              getSession
+#endif
+      henv <- createHscEnvEq thisEnv (zip uids dfs)
+      let targetEnv = (if isBad ci then multi_errs else [], Just henv)
+          targetDepends = componentDependencyInfo ci
+          res = ( targetEnv, targetDepends)
+      logWith recorder Debug $ LogNewComponentCache res
+      evaluate $ liftRnf rwhnf $ componentTargets ci
 
-    let mk t = fromTargetId (importPaths df) exts (targetId t) targetEnv targetDepends
-    ctargets <- concatMapM mk (componentTargets ci)
+      let mk t = fromTargetId (importPaths df) exts (targetId t) targetEnv targetDepends
+      ctargets <- concatMapM mk (componentTargets ci)
 
-    -- A special target for the file which caused this wonderful
-    -- component to be created. In case the cradle doesn't list all the targets for
-    -- the component, in which case things will be horribly broken anyway.
-    -- Otherwise, we will immediately attempt to reload this module which
-    -- causes an infinite loop and high CPU usage.
-    let special_target = TargetDetails (TargetFile cfp) targetEnv targetDepends [componentFP ci]
-    return (special_target:ctargets, res)
+      return (L.nubOrdOn targetTarget ctargets, res)
 
 {- Note [Avoiding bad interface files]
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -923,7 +958,7 @@
 
 -- See Note [Multi Cradle Dependency Info]
 type DependencyInfo = Map.Map FilePath (Maybe UTCTime)
-type HieMap = Map.Map (Maybe FilePath) (HscEnv, [RawComponentInfo])
+type HieMap = Map.Map (Maybe FilePath) [RawComponentInfo]
 -- | Maps a "hie.yaml" location to all its Target Filepaths and options.
 type FlagsMap = Map.Map (Maybe FilePath) (HM.HashMap NormalizedFilePath (IdeResult HscEnvEq, DependencyInfo))
 -- | Maps a Filepath to its respective "hie.yaml" location.
@@ -956,13 +991,13 @@
   -- | Internal units, such as local libraries, that this component
   -- is loaded with. These have been extracted from the original
   -- ComponentOptions.
-  , _componentInternalUnits :: [UnitId]
+  , componentInternalUnits  :: [UnitId]
   -- | All targets of this components.
   , componentTargets        :: [GHC.Target]
   -- | Filepath which caused the creation of this component
   , componentFP             :: NormalizedFilePath
   -- | Component Options used to load the component.
-  , _componentCOptions      :: ComponentOptions
+  , componentCOptions       :: ComponentOptions
   -- | Maps cradle dependencies, such as `stack.yaml`, or `.cabal` file
   -- to last modification time. See Note [Multi Cradle Dependency Info]
   , componentDependencyInfo :: DependencyInfo
@@ -1029,31 +1064,88 @@
                 return (Map.insert k res mp, res)
             Just res -> return (mp, res)
 
+unit_flags :: [Flag (CmdLineP [String])]
+unit_flags = [defFlag "unit"  (SepArg addUnit)]
+
+addUnit :: String -> EwM (CmdLineP [String]) ()
+addUnit unit_str = liftEwM $ do
+  units <- getCmdLineState
+  putCmdLineState (unit_str : units)
+
 -- | Throws if package flags are unsatisfiable
-setOptions :: GhcMonad m => ComponentOptions -> DynFlags -> m (DynFlags, [GHC.Target])
-setOptions (ComponentOptions theOpts compRoot _) dflags = do
-    (dflags', targets') <- addCmdOpts theOpts dflags
-    let targets = makeTargetsAbsolute compRoot targets'
-    let dflags'' =
-          disableWarningsAsErrors $
-          -- disabled, generated directly by ghcide instead
-          flip gopt_unset Opt_WriteInterface $
-          -- disabled, generated directly by ghcide instead
-          -- also, it can confuse the interface stale check
-          dontWriteHieFiles $
-          setIgnoreInterfacePragmas $
-          setBytecodeLinkerOptions $
-          disableOptimisation $
-          Compat.setUpTypedHoles $
-          makeDynFlagsAbsolute compRoot dflags'
-    -- initPackages parses the -package flags and
-    -- sets up the visibility for each component.
-    -- Throws if a -package flag cannot be satisfied.
-    -- This only works for GHC <9.2
-    -- For GHC >= 9.2, we need to modify the unit env in the hsc_dflags, which
-    -- is done later in newComponentCache
-    final_flags <- liftIO $ wrapPackageSetupException $ Compat.oldInitUnits dflags''
-    return (final_flags, targets)
+setOptions :: GhcMonad m => NormalizedFilePath -> ComponentOptions -> DynFlags -> m (NonEmpty (DynFlags, [GHC.Target]))
+setOptions cfp (ComponentOptions theOpts compRoot _) dflags = do
+    ((theOpts',_errs,_warns),units) <- processCmdLineP unit_flags [] (map noLoc theOpts)
+    case NE.nonEmpty units of
+      Just us -> initMulti us
+      Nothing -> do
+        (df, targets) <- initOne (map unLoc theOpts')
+        -- A special target for the file which caused this wonderful
+        -- component to be created. In case the cradle doesn't list all the targets for
+        -- the component, in which case things will be horribly broken anyway.
+        -- Otherwise, we will immediately attempt to reload this module which
+        -- causes an infinite loop and high CPU usage.
+        --
+        -- We don't do this when we have multiple components, because each
+        -- component better list all targets or there will be anarchy.
+        -- It is difficult to know which component to add our file to in
+        -- that case.
+        -- Multi unit arguments are likely to come from cabal, which
+        -- does list all targets.
+        abs_fp <- liftIO $ makeAbsolute (fromNormalizedFilePath cfp)
+        let special_target = Compat.mkSimpleTarget df abs_fp
+        pure $ (df, special_target : targets) :| []
+    where
+      initMulti unitArgFiles =
+        forM unitArgFiles $ \f -> do
+          args <- liftIO $ expandResponse [f]
+          initOne args
+      initOne this_opts = do
+        (dflags', targets') <- addCmdOpts this_opts dflags
+        let dflags'' =
+#if MIN_VERSION_ghc(9,3,0)
+                case unitIdString (homeUnitId_ dflags') of
+                     -- cabal uses main for the unit id of all executable packages
+                     -- This makes multi-component sessions confused about what
+                     -- options to use for that component.
+                     -- Solution: hash the options and use that as part of the unit id
+                     -- This works because there won't be any dependencies on the
+                     -- executable unit.
+                     "main" ->
+                       let hash = B.unpack $ B16.encode $ H.finalize $ H.updates H.init (map B.pack $ this_opts)
+                           hashed_uid = Compat.toUnitId (Compat.stringToUnit ("main-"++hash))
+                       in setHomeUnitId_ hashed_uid dflags'
+                     _ -> dflags'
+#else
+                dflags'
+#endif
+
+        let targets = makeTargetsAbsolute root targets'
+            root = case workingDirectory dflags'' of
+              Nothing   -> compRoot
+              Just wdir -> compRoot </> wdir
+        let dflags''' =
+              setWorkingDirectory root $
+              disableWarningsAsErrors $
+              -- disabled, generated directly by ghcide instead
+              flip gopt_unset Opt_WriteInterface $
+              -- disabled, generated directly by ghcide instead
+              -- also, it can confuse the interface stale check
+              dontWriteHieFiles $
+              setIgnoreInterfacePragmas $
+              setBytecodeLinkerOptions $
+              disableOptimisation $
+              Compat.setUpTypedHoles $
+              makeDynFlagsAbsolute compRoot -- makeDynFlagsAbsolute already accounts for workingDirectory
+              dflags''
+        -- initPackages parses the -package flags and
+        -- sets up the visibility for each component.
+        -- Throws if a -package flag cannot be satisfied.
+        -- This only works for GHC <9.2
+        -- For GHC >= 9.2, we need to modify the unit env in the hsc_dflags, which
+        -- is done later in newComponentCache
+        final_flags <- liftIO $ wrapPackageSetupException $ Compat.oldInitUnits dflags'''
+        return (final_flags, targets)
 
 setIgnoreInterfacePragmas :: DynFlags -> DynFlags
 setIgnoreInterfacePragmas df =
diff --git a/session-loader/Development/IDE/Session/Implicit.hs b/session-loader/Development/IDE/Session/Implicit.hs
new file mode 100644
--- /dev/null
+++ b/session-loader/Development/IDE/Session/Implicit.hs
@@ -0,0 +1,154 @@
+module Development.IDE.Session.Implicit
+  ( loadImplicitCradle
+  ) where
+
+
+import           Control.Applicative               ((<|>))
+import           Control.Monad
+import           Control.Monad.Trans.Maybe
+import           Control.Monad.IO.Class
+import           Control.Exception (handleJust)
+import           Data.Bifunctor
+import           Data.Maybe
+import           Data.Void
+import           System.FilePath
+import           System.Directory                  hiding (findFile)
+import           System.IO.Error
+
+import           Colog.Core                        (LogAction (..), WithSeverity (..))
+import           HIE.Bios.Cradle                   (getCradle, defaultCradle)
+import           HIE.Bios.Config
+import           HIE.Bios.Types                    hiding (ActionName(..))
+
+import           Hie.Locate
+import           Hie.Cabal.Parser
+import qualified Hie.Yaml                          as Implicit
+
+loadImplicitCradle :: Show a => LogAction IO (WithSeverity Log) -> FilePath -> IO (Cradle a)
+loadImplicitCradle l wfile = do
+  is_dir <- doesDirectoryExist wfile
+  let wdir | is_dir = wfile
+           | otherwise = takeDirectory wfile
+  cfg <- runMaybeT (implicitConfig wdir)
+  case cfg of
+    Just bc -> getCradle l absurd bc
+    Nothing -> return $ defaultCradle l wdir
+
+-- | Wraps up the cradle inferred by @inferCradleTree@ as a @CradleConfig@ with no dependencies
+implicitConfig :: FilePath -> MaybeT IO (CradleConfig a, FilePath)
+implicitConfig = (fmap . first) (CradleConfig noDeps) . inferCradleTree
+  where
+  noDeps :: [FilePath]
+  noDeps = []
+
+
+inferCradleTree :: FilePath -> MaybeT IO (CradleTree a, FilePath)
+inferCradleTree start_dir =
+       maybeItsBios
+   -- If we have both a config file (cabal.project/stack.yaml) and a work dir
+   -- (dist-newstyle/.stack-work), prefer that
+   <|> (cabalExecutable >> cabalConfigDir start_dir >>= \dir -> cabalWorkDir dir >> pure (simpleCabalCradle dir))
+   <|> (stackExecutable >> stackConfigDir start_dir >>= \dir -> stackWorkDir dir >> stackCradle dir)
+   -- If we have a cabal.project OR we have a .cabal and dist-newstyle, prefer cabal
+   <|> (cabalExecutable >> (cabalConfigDir start_dir <|> cabalFileAndWorkDir) >>= pure . simpleCabalCradle)
+   -- If we have a stack.yaml, use stack
+   <|> (stackExecutable >> stackConfigDir start_dir >>= stackCradle)
+   -- If we have a cabal file, use cabal
+   <|> (cabalExecutable >> cabalFileDir start_dir >>= pure . simpleCabalCradle)
+
+  where
+  maybeItsBios = (\wdir -> (Bios (Program $ wdir </> ".hie-bios") Nothing Nothing, wdir)) <$> biosWorkDir start_dir
+
+  cabalFileAndWorkDir = cabalFileDir start_dir >>= (\dir -> cabalWorkDir dir >> pure dir)
+
+-- | Generate a stack cradle given a filepath.
+--
+-- Since we assume there was proof that this file belongs to a stack cradle
+-- we look immediately for the relevant @*.cabal@ and @stack.yaml@ files.
+-- We do not look for package.yaml, as we assume the corresponding .cabal has
+-- been generated already.
+--
+-- We parse the @stack.yaml@ to find relevant @*.cabal@ file locations, then
+-- we parse the @*.cabal@ files to generate a mapping from @hs-source-dirs@ to
+-- component names.
+stackCradle :: FilePath -> MaybeT IO (CradleTree a, FilePath)
+stackCradle fp = do
+  pkgs <- stackYamlPkgs fp
+  pkgsWithComps <- liftIO $ catMaybes <$> mapM (nestedPkg fp) pkgs
+  let yaml = fp </> "stack.yaml"
+  pure $ (,fp) $ case pkgsWithComps of
+    [] -> Stack (StackType Nothing (Just yaml))
+    ps -> StackMulti mempty $ do
+      Package n cs <- ps
+      c <- cs
+      let (prefix, comp) = Implicit.stackComponent n c
+      pure (prefix, StackType (Just comp) (Just yaml))
+
+-- | By default, we generate a simple cabal cradle which is equivalent to the
+-- following hie.yaml:
+--
+-- @
+--   cradle:
+--     cabal:
+-- @
+--
+-- Note, this only works reliable for reasonably modern cabal versions >= 3.2.
+simpleCabalCradle :: FilePath -> (CradleTree a, FilePath)
+simpleCabalCradle fp = (Cabal $ CabalType Nothing Nothing, fp)
+
+cabalExecutable :: MaybeT IO FilePath
+cabalExecutable = MaybeT $ findExecutable "cabal"
+
+stackExecutable :: MaybeT IO FilePath
+stackExecutable = MaybeT $ findExecutable "stack"
+
+biosWorkDir :: FilePath -> MaybeT IO FilePath
+biosWorkDir = findFileUpwards (".hie-bios" ==)
+
+cabalWorkDir :: FilePath -> MaybeT IO ()
+cabalWorkDir wdir = do
+  check <- liftIO $ doesDirectoryExist (wdir </> "dist-newstyle")
+  unless check $ fail "No dist-newstyle"
+
+stackWorkDir :: FilePath -> MaybeT IO ()
+stackWorkDir wdir = do
+  check <- liftIO $ doesDirectoryExist (wdir </> ".stack-work")
+  unless check $ fail "No .stack-work"
+
+cabalConfigDir :: FilePath -> MaybeT IO FilePath
+cabalConfigDir = findFileUpwards (\fp -> fp == "cabal.project" || fp == "cabal.project.local")
+
+cabalFileDir :: FilePath -> MaybeT IO FilePath
+cabalFileDir = findFileUpwards (\fp -> takeExtension fp == ".cabal")
+
+stackConfigDir :: FilePath -> MaybeT IO FilePath
+stackConfigDir = findFileUpwards isStack
+  where
+    isStack name = name == "stack.yaml"
+
+-- | Searches upwards for the first directory containing a file to match
+-- the predicate.
+findFileUpwards :: (FilePath -> Bool) -> FilePath -> MaybeT IO FilePath
+findFileUpwards p dir = do
+  cnts <-
+    liftIO
+    $ handleJust
+        -- Catch permission errors
+        (\(e :: IOError) -> if isPermissionError e then Just [] else Nothing)
+        pure
+        (findFile p dir)
+
+  case cnts of
+    [] | dir' == dir -> fail "No cabal files"
+            | otherwise   -> findFileUpwards p dir'
+    _ : _ -> return dir
+  where dir' = takeDirectory dir
+
+-- | Sees if any file in the directory matches the predicate
+findFile :: (FilePath -> Bool) -> FilePath -> IO [FilePath]
+findFile p dir = do
+  b <- doesDirectoryExist dir
+  if b then getFiles >>= filterM doesPredFileExist else return []
+  where
+    getFiles = filter p <$> getDirectoryContents dir
+    doesPredFileExist file = doesFileExist $ dir </> file
diff --git a/src/Development/IDE/Core/PositionMapping.hs b/src/Development/IDE/Core/PositionMapping.hs
--- a/src/Development/IDE/Core/PositionMapping.hs
+++ b/src/Development/IDE/Core/PositionMapping.hs
@@ -10,7 +10,7 @@
   , fromCurrentPosition
   , toCurrentPosition
   , PositionDelta(..)
-  , addDelta
+  , addOldDelta
   , idDelta
   , composeDelta
   , mkDelta
@@ -119,9 +119,13 @@
 mkDelta :: [TextDocumentContentChangeEvent] -> PositionDelta
 mkDelta cs = foldl' applyChange idDelta cs
 
--- | Add a new delta onto a Mapping k n to make a Mapping (k - 1) n
-addDelta :: PositionDelta -> PositionMapping -> PositionMapping
-addDelta delta (PositionMapping pm) = PositionMapping (composeDelta delta pm)
+-- | addOldDelta
+-- Add a old delta onto a Mapping k n to make a Mapping (k - 1) n
+addOldDelta ::
+    PositionDelta -- ^ delta from version k - 1 to version k
+    -> PositionMapping -- ^ The input mapping is from version k to version n
+    -> PositionMapping -- ^ The output mapping is from version k - 1 to version n
+addOldDelta delta (PositionMapping pm) = PositionMapping (composeDelta pm delta)
 
 -- TODO: We currently ignore the right hand side (if there is only text), as
 -- that was what was done with lsp* 1.6 packages
diff --git a/src/Development/IDE/Core/RuleTypes.hs b/src/Development/IDE/Core/RuleTypes.hs
--- a/src/Development/IDE/Core/RuleTypes.hs
+++ b/src/Development/IDE/Core/RuleTypes.hs
@@ -238,14 +238,14 @@
 -- | A IntervalMap telling us what is in scope at each point
 type instance RuleResult GetBindings = Bindings
 
-data DocAndKindMap = DKMap {getDocMap :: !DocMap, getKindMap :: !KindMap}
-instance NFData DocAndKindMap where
+data DocAndTyThingMap = DKMap {getDocMap :: !DocMap, getTyThingMap :: !TyThingMap}
+instance NFData DocAndTyThingMap where
     rnf (DKMap a b) = rwhnf a `seq` rwhnf b
 
-instance Show DocAndKindMap where
+instance Show DocAndTyThingMap where
     show = const "docmap"
 
-type instance RuleResult GetDocMap = DocAndKindMap
+type instance RuleResult GetDocMap = DocAndTyThingMap
 
 -- | A GHC session that we reuse.
 type instance RuleResult GhcSession = HscEnvEq
diff --git a/src/Development/IDE/Core/Shake.hs b/src/Development/IDE/Core/Shake.hs
--- a/src/Development/IDE/Core/Shake.hs
+++ b/src/Development/IDE/Core/Shake.hs
@@ -62,6 +62,7 @@
     FileVersion(..),
     Priority(..),
     updatePositionMapping,
+    updatePositionMappingHelper,
     deleteValue, recordDirtyKeys,
     WithProgressFunc, WithIndefiniteProgressFunc,
     ProgressEvent(..),
@@ -266,7 +267,7 @@
     -- ^ Map from a text document version to a PositionMapping that describes how to map
     -- positions in a version of that document to positions in the latest version
     -- First mapping is delta from previous version and second one is an
-    -- accumulation of all previous mappings.
+    -- accumulation to the current version.
     ,progress :: ProgressReporting
     ,ideTesting :: IdeTesting
     -- ^ Whether to enable additional lsp messages used by the test suite for checking invariants
@@ -443,7 +444,7 @@
                               `catch` (\(_ :: IOException) -> pure Nothing)
                 atomicallyNamed "lastValueIO 2" $ do
                   STM.focus (Focus.alter (alterValue $ Stale (Just del) actual_version (toDyn v))) (toKey k file) state
-                  Just . (v,) . addDelta del <$> mappingForVersion positionMapping file actual_version
+                  Just . (v,) . addOldDelta del <$> mappingForVersion positionMapping file actual_version
 
         -- We got a new stale value from the persistent rule, insert it in the map without affecting diagnostics
         alterValue new Nothing = Just (ValueWithDiagnostics new mempty) -- If it wasn't in the map, give it empty diagnostics
@@ -459,7 +460,7 @@
         Succeeded ver (fromDynamic -> Just v) ->
             atomicallyNamed "lastValueIO 5"  $ Just . (v,) <$> mappingForVersion positionMapping file ver
         Stale del ver (fromDynamic -> Just v) ->
-            atomicallyNamed "lastValueIO 6"  $ Just . (v,) . maybe id addDelta del <$> mappingForVersion positionMapping file ver
+            atomicallyNamed "lastValueIO 6"  $ Just . (v,) . maybe id addOldDelta del <$> mappingForVersion positionMapping file ver
         Failed p | not p -> readPersistent
         _ -> pure Nothing
 
@@ -1352,12 +1353,18 @@
     STM.focus (Focus.alter f) uri positionMapping
       where
         uri = toNormalizedUri _uri
-        f = Just . f' . fromMaybe mempty
-        f' mappingForUri = snd $
-                -- Very important to use mapAccum here so that the tails of
-                -- each mapping can be shared, otherwise quadratic space is
-                -- used which is evident in long running sessions.
-                EM.mapAccumRWithKey (\acc _k (delta, _) -> let new = addDelta delta acc in (new, (delta, acc)))
-                  zeroMapping
-                  (EM.insert _version (shared_change, zeroMapping) mappingForUri)
-        shared_change = mkDelta changes
+        f = Just . updatePositionMappingHelper _version changes . fromMaybe mempty
+
+
+updatePositionMappingHelper ::
+    Int32
+    -> [TextDocumentContentChangeEvent]
+    -> EnumMap Int32 (PositionDelta, PositionMapping)
+    -> EnumMap Int32 (PositionDelta, PositionMapping)
+updatePositionMappingHelper ver changes mappingForUri = snd $
+        -- Very important to use mapAccum here so that the tails of
+        -- each mapping can be shared, otherwise quadratic space is
+        -- used which is evident in long running sessions.
+        EM.mapAccumRWithKey (\acc _k (delta, _) -> let new = addOldDelta delta acc in (new, (delta, acc)))
+            zeroMapping
+            (EM.insert ver (mkDelta changes, zeroMapping) mappingForUri)
diff --git a/src/Development/IDE/GHC/Compat/CmdLine.hs b/src/Development/IDE/GHC/Compat/CmdLine.hs
new file mode 100644
--- /dev/null
+++ b/src/Development/IDE/GHC/Compat/CmdLine.hs
@@ -0,0 +1,38 @@
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE RankNTypes #-}
+
+-- | Compat module Interface file relevant code.
+module Development.IDE.GHC.Compat.CmdLine (
+          processCmdLineP
+        , CmdLineP (..)
+        , getCmdLineState
+        , putCmdLineState
+        , Flag(..)
+        , OptKind(..)
+        , EwM
+        , defFlag
+        , liftEwM
+    ) where
+
+#if MIN_VERSION_ghc(9,3,0)
+import GHC.Driver.Session (processCmdLineP, CmdLineP (..), getCmdLineState, putCmdLineState)
+import GHC.Driver.CmdLine
+#else
+import GHC.Driver.CmdLine
+import Control.Monad.IO.Class
+import GHC (Located)
+#endif
+
+#if !MIN_VERSION_ghc(9,3,0)
+-- | A helper to parse a set of flags from a list of command-line arguments, handling
+-- response files.
+processCmdLineP
+    :: forall s m. MonadIO m
+    => [Flag (CmdLineP s)]  -- ^ valid flags to match against
+    -> s                    -- ^ current state
+    -> [Located String]     -- ^ arguments to parse
+    -> m (([Located String], [Err], [Warn]), s)
+                            -- ^ (leftovers, errors, warnings)
+processCmdLineP activeFlags s0 args =
+    pure $ runCmdLine (processArgs activeFlags args) s0
+#endif
diff --git a/src/Development/IDE/GHC/Compat/Core.hs b/src/Development/IDE/GHC/Compat/Core.hs
--- a/src/Development/IDE/GHC/Compat/Core.hs
+++ b/src/Development/IDE/GHC/Compat/Core.hs
@@ -262,6 +262,7 @@
     -- * Driver-Make
     Target(..),
     TargetId(..),
+    mkSimpleTarget,
     mkModuleGraph,
     -- * GHCi
     initObjLinker,
@@ -406,6 +407,7 @@
     field_label,
 #endif
     groupOrigin,
+    isVisibleFunArg,
     ) where
 
 import qualified GHC
@@ -430,13 +432,13 @@
 import qualified GHC.Core.DataCon             as DataCon
 import           GHC.Core.FamInstEnv          hiding (pprFamInst)
 import           GHC.Core.InstEnv
-import           GHC.Types.Unique.FM 
+import           GHC.Types.Unique.FM
 import           GHC.Core.PatSyn
 import           GHC.Core.Predicate
 import           GHC.Core.TyCo.Ppr
 import qualified GHC.Core.TyCo.Rep            as TyCoRep
 import           GHC.Core.TyCon
-import           GHC.Core.Type                
+import           GHC.Core.Type
 import           GHC.Core.Unify
 import           GHC.Core.Utils
 import           GHC.Driver.CmdLine           (Warn (..))
@@ -488,6 +490,8 @@
 import           GHC.Types.Unique.Supply
 import           GHC.Types.Var                (Var (varName), setTyVarUnique,
                                                setVarUnique)
+
+import qualified GHC.Types.Var                as TypesVar
 import           GHC.Unit.Info                (PackageName (..))
 import           GHC.Unit.Module              hiding (ModLocation (..), UnitId,
                                                moduleUnit,
@@ -596,7 +600,7 @@
 pattern AvailTC :: Name -> [Name] -> [FieldLabel] -> Avail.AvailInfo
 #if __GLASGOW_HASKELL__ >= 907
 pattern AvailTC n names pieces <- Avail.AvailTC n ((,[]) -> (names,pieces))
-#else 
+#else
 pattern AvailTC n names pieces <- Avail.AvailTC n ((\gres -> foldr (\gre (names, pieces) -> case gre of
       Avail.NormalGreName name -> (name: names, pieces)
       Avail.FieldGreName label -> (names, label:pieces)) ([], []) gres) -> (names, pieces))
@@ -605,14 +609,14 @@
 pattern AvailName :: Name -> Avail.AvailInfo
 #if __GLASGOW_HASKELL__ >= 907
 pattern AvailName n <- Avail.Avail n
-#else 
+#else
 pattern AvailName n <- Avail.Avail (Avail.NormalGreName n)
 #endif
 
 pattern AvailFL :: FieldLabel -> Avail.AvailInfo
 #if __GLASGOW_HASKELL__ >= 907
 pattern AvailFL fl <- (const Nothing -> Just fl) -- this pattern always fails as this field was removed in 9.7
-#else 
+#else
 pattern AvailFL fl <- Avail.Avail (Avail.FieldGreName fl)
 #endif
 
@@ -629,9 +633,18 @@
 pattern ExposePackage s a mr = DynFlags.ExposePackage s a mr
 #endif
 
-pattern FunTy :: Type -> Type -> Type
-pattern FunTy arg res <- TyCoRep.FunTy {ft_arg = arg, ft_res = res}
+#if __GLASGOW_HASKELL__ >= 906
+isVisibleFunArg = TypesVar.isVisibleFunArg
+type FunTyFlag = TypesVar.FunTyFlag
+#else
+isVisibleFunArg VisArg = True
+isVisibleFunArg _ = False
+type FunTyFlag = TypesVar.AnonArgFlag
+#endif
+pattern FunTy :: Development.IDE.GHC.Compat.Core.FunTyFlag -> Type -> Type -> Type
+pattern FunTy af arg res <- TyCoRep.FunTy {ft_af = af, ft_arg = arg, ft_res = res}
 
+
 -- type HasSrcSpan x a = (GenLocated SrcSpan a ~ x)
 -- type HasSrcSpan x = () :: Constraint
 
@@ -804,4 +817,11 @@
 
 field_label :: a -> a
 field_label = id
+#endif
+
+mkSimpleTarget :: DynFlags -> FilePath -> Target
+#if MIN_VERSION_ghc(9,3,0)
+mkSimpleTarget df fp = Target (TargetFile fp Nothing) True (homeUnitId_ df) Nothing
+#else
+mkSimpleTarget _ fp = Target (TargetFile fp Nothing) True Nothing
 #endif
diff --git a/src/Development/IDE/GHC/Compat/Env.hs b/src/Development/IDE/GHC/Compat/Env.hs
--- a/src/Development/IDE/GHC/Compat/Env.hs
+++ b/src/Development/IDE/GHC/Compat/Env.hs
@@ -19,6 +19,9 @@
     Env.hsc_logger,
     Env.hsc_tmpfs,
     Env.hsc_unit_env,
+#if !MIN_VERSION_ghc(9,3,0)
+    Env.hsc_unit_dbs,
+#endif
     Env.hsc_hooks,
     hscSetHooks,
     TmpFs,
@@ -52,6 +55,10 @@
     setBackend,
     ghciBackend,
     Development.IDE.GHC.Compat.Env.platformDefaultBackend,
+    workingDirectory,
+    setWorkingDirectory,
+    hscSetActiveUnitId,
+    reexportedModules,
     ) where
 
 import           GHC                 (setInteractiveDynFlags)
@@ -73,17 +80,37 @@
 
 #if !MIN_VERSION_ghc(9,3,0)
 import           GHC.Driver.Env      (HscEnv, hsc_EPS)
+import qualified Data.Set            as S
 #endif
 
 #if MIN_VERSION_ghc(9,3,0)
-import           GHC.Driver.Env      (HscEnv)
+import           GHC.Driver.Env      (HscEnv, hscSetActiveUnitId)
 #endif
 
+
+#if !MIN_VERSION_ghc(9,3,0)
+hscSetActiveUnitId :: UnitId -> HscEnv -> HscEnv
+hscSetActiveUnitId _ env = env
+
+reexportedModules :: HscEnv -> S.Set a
+reexportedModules _ = S.empty
+#endif
+
 #if MIN_VERSION_ghc(9,3,0)
 hsc_EPS :: HscEnv -> UnitEnv
 hsc_EPS = Env.hsc_unit_env
 #endif
 
+#if !MIN_VERSION_ghc(9,3,0)
+workingDirectory :: a -> Maybe b
+workingDirectory _ = Nothing
+
+setWorkingDirectory :: FilePath -> DynFlags -> DynFlags
+setWorkingDirectory = const id
+#else
+setWorkingDirectory :: FilePath -> DynFlags -> DynFlags
+setWorkingDirectory p d = d { workingDirectory =  Just p }
+#endif
 
 setHomeUnitId_ :: UnitId -> DynFlags -> DynFlags
 setHomeUnitId_ uid df = df { Session.homeUnitId_ = uid }
diff --git a/src/Development/IDE/GHC/Compat/Units.hs b/src/Development/IDE/GHC/Compat/Units.hs
--- a/src/Development/IDE/GHC/Compat/Units.hs
+++ b/src/Development/IDE/GHC/Compat/Units.hs
@@ -5,9 +5,7 @@
 module Development.IDE.GHC.Compat.Units (
     -- * UnitState
     UnitState,
-#if MIN_VERSION_ghc(9,3,0)
     initUnits,
-#endif
     oldInitUnits,
     unitState,
     getUnitName,
@@ -144,7 +142,11 @@
         , ue_eps             = ue_eps (hsc_unit_env env)
         }
   pure $ hscSetFlags dflags1 $ hscSetUnitEnv unit_env env
+#else
+initUnits :: [DynFlags] -> HscEnv -> IO HscEnv
+initUnits _df env = pure env -- Can't do anything here, oldInitUnits should already be called
 #endif
+
 
 -- | oldInitUnits only needs to modify DynFlags for GHC <9.2
 -- For GHC >= 9.2, we need to set the hsc_unit_env also, that is
diff --git a/src/Development/IDE/GHC/Error.hs b/src/Development/IDE/GHC/Error.hs
--- a/src/Development/IDE/GHC/Error.hs
+++ b/src/Development/IDE/GHC/Error.hs
@@ -17,6 +17,8 @@
   , realSrcSpanToRange
   , realSrcLocToPosition
   , realSrcSpanToLocation
+  , realSrcSpanToCodePointRange
+  , realSrcLocToCodePointPosition
   , srcSpanToFilename
   , rangeToSrcSpan
   , rangeToRealSrcSpan
@@ -45,6 +47,8 @@
 import           Development.IDE.Types.Location
 import           GHC
 import           Language.LSP.Protocol.Types       (isSubrangeOf)
+import           Language.LSP.VFS                  (CodePointPosition (CodePointPosition),
+                                                    CodePointRange (CodePointRange))
 
 
 diagFromText :: T.Text -> D.DiagnosticSeverity -> SrcSpan -> T.Text -> FileDiagnostic
@@ -85,6 +89,28 @@
 realSrcLocToPosition :: RealSrcLoc -> Position
 realSrcLocToPosition real =
   Position (fromIntegral $ srcLocLine real - 1) (fromIntegral $ srcLocCol real - 1)
+
+-- Note [Unicode support]
+-- the current situation is:
+-- LSP Positions use UTF-16 code units(Unicode may count as variable columns);
+-- GHC use Unicode code points(Unicode count as one column).
+-- To support unicode, ideally range should be in lsp standard,
+-- and codePoint should be in ghc standard.
+-- see https://github.com/haskell/lsp/pull/407
+
+-- | Convert a GHC SrcSpan to CodePointRange
+-- see Note [Unicode support]
+realSrcSpanToCodePointRange :: RealSrcSpan -> CodePointRange
+realSrcSpanToCodePointRange real =
+  CodePointRange
+    (realSrcLocToCodePointPosition $ Compat.realSrcSpanStart real)
+    (realSrcLocToCodePointPosition $ Compat.realSrcSpanEnd real)
+
+-- | Convert a GHC RealSrcLoc to CodePointPosition
+-- see Note [Unicode support]
+realSrcLocToCodePointPosition :: RealSrcLoc -> CodePointPosition
+realSrcLocToCodePointPosition real =
+  CodePointPosition (fromIntegral $ srcLocLine real - 1) (fromIntegral $ srcLocCol real - 1)
 
 -- | Extract a file name from a GHC SrcSpan (use message for unhelpful ones)
 -- FIXME This may not be an _absolute_ file name, needs fixing.
diff --git a/src/Development/IDE/Import/FindImports.hs b/src/Development/IDE/Import/FindImports.hs
--- a/src/Development/IDE/Import/FindImports.hs
+++ b/src/Development/IDE/Import/FindImports.hs
@@ -23,7 +23,8 @@
 -- standard imports
 import           Control.Monad.Extra
 import           Control.Monad.IO.Class
-import           Data.List                         (isSuffixOf)
+import           Data.List                         (isSuffixOf, find)
+import qualified Data.Set                          as S
 import           Data.Maybe
 import           System.FilePath
 
@@ -70,19 +71,30 @@
       Just modSum -> isSource (ms_hsc_src modSum)
     mbMod = ms_mod <$> ms
 
+data LocateResult
+  = LocateNotFound
+  | LocateFoundReexport UnitId
+  | LocateFoundFile UnitId NormalizedFilePath
+
 -- | locate a module in the file system. Where we go from *daml to Haskell
 locateModuleFile :: MonadIO m
-             => [(UnitId, [FilePath])]
+             => [(UnitId, [FilePath], S.Set ModuleName)]
              -> [String]
              -> (ModuleName -> NormalizedFilePath -> m (Maybe NormalizedFilePath))
              -> Bool
              -> ModuleName
-             -> m (Maybe (UnitId, NormalizedFilePath))
+             -> m LocateResult
 locateModuleFile import_dirss exts targetFor isSource modName = do
   let candidates import_dirs =
         [ toNormalizedFilePath' (prefix </> moduleNameSlashes modName <.> maybeBoot ext)
            | prefix <- import_dirs , ext <- exts]
-  firstJustM go (concat [map (uid,) (candidates dirs) | (uid, dirs) <- import_dirss])
+  mf <- firstJustM go (concat [map (uid,) (candidates dirs) | (uid, dirs, _) <- import_dirss])
+  case mf of
+    Nothing ->
+      case find (\(_ , _, reexports) -> S.member modName reexports) import_dirss of
+        Just (uid,_,_) -> pure $ LocateFoundReexport uid
+        Nothing -> pure $ LocateNotFound
+    Just (uid,file) -> pure $ LocateFoundFile uid file
   where
     go (uid, candidate) = fmap ((uid,) <$>) $ targetFor modName candidate
     maybeBoot ext
@@ -94,11 +106,11 @@
 -- current module. In particular, it will return Nothing for 'main' components
 -- as they can never be imported into another package.
 #if MIN_VERSION_ghc(9,3,0)
-mkImportDirs :: HscEnv -> (UnitId, DynFlags) -> Maybe (UnitId, [FilePath])
-mkImportDirs _env (i, flags) = Just (i, importPaths flags)
+mkImportDirs :: HscEnv -> (UnitId, DynFlags) -> Maybe (UnitId, ([FilePath], S.Set ModuleName))
+mkImportDirs _env (i, flags) = Just (i, (importPaths flags, reexportedModules flags))
 #else
-mkImportDirs :: HscEnv -> (UnitId, DynFlags) -> Maybe (PackageName, (UnitId, [FilePath]))
-mkImportDirs env (i, flags) = (, (i, importPaths flags)) <$> getUnitName env i
+mkImportDirs :: HscEnv -> (UnitId, DynFlags) -> Maybe (PackageName, (UnitId, [FilePath], S.Set ModuleName))
+mkImportDirs env (i, flags) = (, (i, importPaths flags, S.empty)) <$> getUnitName env i
 #endif
 
 -- | locate a module in either the file system or the package database. Where we go from *daml to
@@ -125,16 +137,16 @@
 #else
     Just "this" -> do
 #endif
-      lookupLocal (homeUnitId_ dflags) (importPaths dflags)
+      lookupLocal (homeUnitId_ dflags) (importPaths dflags) S.empty
     -- if a package name is given we only go look for a package
 #if MIN_VERSION_ghc(9,3,0)
     OtherPkg uid
-      | Just dirs <- lookup uid import_paths
-          -> lookupLocal uid dirs
+      | Just (dirs, reexports) <- lookup uid import_paths
+          -> lookupLocal uid dirs reexports
 #else
     Just pkgName
-      | Just (uid, dirs) <- lookup (PackageName pkgName) import_paths
-          -> lookupLocal uid dirs
+      | Just (uid, dirs, reexports) <- lookup (PackageName pkgName) import_paths
+          -> lookupLocal uid dirs reexports
 #endif
       | otherwise -> lookupInPackageDB
 #if MIN_VERSION_ghc(9,3,0)
@@ -143,10 +155,15 @@
     Nothing -> do
 #endif
 
-      mbFile <- locateModuleFile ((homeUnitId_ dflags, importPaths dflags) : other_imports) exts targetFor isSource $ unLoc modName
+      -- Reexports for current unit have to be empty because they only apply to other units depending on the
+      -- current unit. If we set the reexports to be the actual reexports then we risk looping forever trying
+      -- to find the module from the perspective of the current unit.
+      mbFile <- locateModuleFile ((homeUnitId_ dflags, importPaths dflags, S.empty) : other_imports) exts targetFor isSource $ unLoc modName
       case mbFile of
-        Nothing          -> lookupInPackageDB
-        Just (uid, file) -> toModLocation uid file
+        LocateNotFound -> lookupInPackageDB
+        -- Lookup again with the perspective of the unit reexporting the file
+        LocateFoundReexport uid -> locateModule (hscSetActiveUnitId uid env) comp_info exts targetFor modName noPkgQual isSource
+        LocateFoundFile uid file -> toModLocation uid file
   where
     dflags = hsc_dflags env
     import_paths = mapMaybe (mkImportDirs env) comp_info
@@ -160,7 +177,7 @@
       -- about which module unit a imports.
       -- Without multi-component support it is hard to recontruct the dependency environment so
       -- unit a will have both unit b and unit c in scope.
-      map (\uid -> (uid, importPaths (homeUnitEnv_dflags (ue_findHomeUnitEnv uid ue)))) hpt_deps
+      map (\uid -> let this_df = homeUnitEnv_dflags (ue_findHomeUnitEnv uid ue) in (uid, importPaths this_df, reexportedModules this_df)) hpt_deps
     ue = hsc_unit_env env
     units = homeUnitEnv_units $ ue_findHomeUnitEnv (homeUnitId_ dflags) ue
     hpt_deps :: [UnitId]
@@ -186,11 +203,13 @@
         let genMod = mkModule (RealUnit $ Definite uid) (unLoc modName)  -- TODO support backpack holes
         return $ Right $ FileImport $ ArtifactsLocation file (Just loc) (not isSource) (Just genMod)
 
-    lookupLocal uid dirs = do
-      mbFile <- locateModuleFile [(uid, dirs)] exts targetFor isSource $ unLoc modName
+    lookupLocal uid dirs reexports = do
+      mbFile <- locateModuleFile [(uid, dirs, reexports)] exts targetFor isSource $ unLoc modName
       case mbFile of
-        Nothing   -> return $ Left $ notFoundErr env modName $ LookupNotFound []
-        Just (uid', file) -> toModLocation uid' file
+        LocateNotFound -> return $ Left $ notFoundErr env modName $ LookupNotFound []
+        -- Lookup again with the perspective of the unit reexporting the file
+        LocateFoundReexport uid' -> locateModule (hscSetActiveUnitId uid' env) comp_info exts targetFor modName noPkgQual isSource
+        LocateFoundFile uid' file -> toModLocation uid' file
 
     lookupInPackageDB = do
       case Compat.lookupModuleWithSuggestions env (unLoc modName) mbPkgName of
@@ -219,7 +238,11 @@
              }
         LookupUnusable unusable ->
           let unusables' = map get_unusable unusable
+#if MIN_VERSION_ghc(9,6,4) && !MIN_VERSION_ghc(9,8,1)
+              get_unusable (m, ModUnusable r) = r
+#else
               get_unusable (m, ModUnusable r) = (moduleUnit m, r)
+#endif
               get_unusable (_, r) =
                 pprPanic "findLookupResult: unexpected origin" (ppr r)
            in notFound {fr_unusables = unusables'}
@@ -235,3 +258,11 @@
   , fr_unusables = []
   , fr_suggestions = []
   }
+
+#if MIN_VERSION_ghc(9,3,0)
+noPkgQual :: PkgQual
+noPkgQual = NoPkgQual
+#else
+noPkgQual :: Maybe a
+noPkgQual = Nothing
+#endif
diff --git a/src/Development/IDE/LSP/Notifications.hs b/src/Development/IDE/LSP/Notifications.hs
--- a/src/Development/IDE/LSP/Notifications.hs
+++ b/src/Development/IDE/LSP/Notifications.hs
@@ -54,7 +54,7 @@
 whenUriFile uri act = whenJust (LSP.uriToFilePath uri) $ act . toNormalizedFilePath'
 
 descriptor :: Recorder (WithPriority Log) -> PluginId -> PluginDescriptor IdeState
-descriptor recorder plId = (defaultPluginDescriptor plId) { pluginNotificationHandlers = mconcat
+descriptor recorder plId = (defaultPluginDescriptor plId desc) { pluginNotificationHandlers = mconcat
   [ mkPluginNotificationHandler LSP.SMethod_TextDocumentDidOpen $
       \ide vfs _ (DidOpenTextDocumentParams TextDocumentItem{_uri,_version}) -> liftIO $ do
       atomically $ updatePositionMapping ide (VersionedTextDocumentIdentifier _uri _version) []
@@ -142,6 +142,8 @@
     -- (which restart the Shake build) run after everything else
         pluginPriority = ghcideNotificationsPluginPriority
     }
+  where
+    desc = "Handles basic notifications for ghcide"
 
 ghcideNotificationsPluginPriority :: Natural
 ghcideNotificationsPluginPriority = defaultPluginPriority - 900
diff --git a/src/Development/IDE/Plugin/Completions.hs b/src/Development/IDE/Plugin/Completions.hs
--- a/src/Development/IDE/Plugin/Completions.hs
+++ b/src/Development/IDE/Plugin/Completions.hs
@@ -71,13 +71,15 @@
 ghcideCompletionsPluginPriority = defaultPluginPriority
 
 descriptor :: Recorder (WithPriority Log) -> PluginId -> PluginDescriptor IdeState
-descriptor recorder plId = (defaultPluginDescriptor plId)
+descriptor recorder plId = (defaultPluginDescriptor plId desc)
   { pluginRules = produceCompletions recorder
   , pluginHandlers = mkPluginHandler SMethod_TextDocumentCompletion getCompletionsLSP
                      <> mkResolveHandler SMethod_CompletionItemResolve resolveCompletion
   , pluginConfigDescriptor = defaultConfigDescriptor {configCustomConfig = mkCustomConfig properties}
   , pluginPriority = ghcideCompletionsPluginPriority
   }
+  where
+    desc = "Provides Haskell completions"
 
 
 produceCompletions :: Recorder (WithPriority Log) -> Rules ()
@@ -142,8 +144,8 @@
 #endif
     mdkm <- liftIO $ runIdeAction "CompletionResolve.GetDocMap" (shakeExtras ide) $ useWithStaleFast GetDocMap file
     let (dm,km) = case mdkm of
-          Just (DKMap docMap kindMap, _) -> (docMap,kindMap)
-          Nothing                        -> (mempty, mempty)
+          Just (DKMap docMap tyThingMap, _) -> (docMap,tyThingMap)
+          Nothing                           -> (mempty, mempty)
     doc <- case lookupNameEnv dm name of
       Just doc -> pure $ spanDocToMarkdown doc
       Nothing -> liftIO $ spanDocToMarkdown <$> getDocumentationTryGhc (hscEnv sess) name
@@ -215,7 +217,7 @@
                     plugins = idePlugins $ shakeExtras ide
                 config <- liftIO $ runAction "" ide $ getCompletionsConfig plId
 
-                allCompletions <- liftIO $ getCompletions plugins ideOpts cci' parsedMod astres bindMap pfix clientCaps config moduleExports uri
+                let allCompletions = getCompletions plugins ideOpts cci' parsedMod astres bindMap pfix clientCaps config moduleExports uri
                 pure $ InL (orderedCompletions allCompletions)
           _ -> return (InL [])
       _ -> return (InL [])
diff --git a/src/Development/IDE/Plugin/Completions/Logic.hs b/src/Development/IDE/Plugin/Completions/Logic.hs
--- a/src/Development/IDE/Plugin/Completions/Logic.hs
+++ b/src/Development/IDE/Plugin/Completions/Logic.hs
@@ -559,10 +559,54 @@
     -> CompletionsConfig
     -> ModuleNameEnv (HashSet.HashSet IdentInfo)
     -> Uri
-    -> IO [Scored CompletionItem]
-getCompletions plugins ideOpts CC {allModNamesAsNS, anyQualCompls, unqualCompls, qualCompls, importableModules}
-               maybe_parsed maybe_ast_res (localBindings, bmapping) prefixInfo caps config moduleExportsMap uri = do
-  let PosPrefixInfo { fullLine, prefixScope, prefixText } = prefixInfo
+    -> [Scored CompletionItem]
+getCompletions
+    plugins
+    ideOpts
+    CC {allModNamesAsNS, anyQualCompls, unqualCompls, qualCompls, importableModules}
+    maybe_parsed
+    maybe_ast_res
+    (localBindings, bmapping)
+    prefixInfo@(PosPrefixInfo { fullLine, prefixScope, prefixText })
+    caps
+    config
+    moduleExportsMap
+    uri
+    -- ------------------------------------------------------------------------
+    -- IMPORT MODULENAME (NAM|)
+    | Just (ImportListContext moduleName) <- maybeContext
+    = moduleImportListCompletions moduleName
+
+    | Just (ImportHidingContext moduleName) <- maybeContext
+    = moduleImportListCompletions moduleName
+
+    -- ------------------------------------------------------------------------
+    -- IMPORT MODULENAM|
+    | Just (ImportContext _moduleName) <- maybeContext
+    = filtImportCompls
+
+    -- ------------------------------------------------------------------------
+    -- {-# LA| #-}
+    -- we leave this condition here to avoid duplications and return empty list
+    -- since HLS implements these completions (#haskell-language-server/pull/662)
+    | "{-# " `T.isPrefixOf` fullLine
+    = []
+
+    -- ------------------------------------------------------------------------
+    | otherwise =
+        -- assumes that nubOrdBy is stable
+        let uniqueFiltCompls = nubOrdBy (uniqueCompl `on` snd . Fuzzy.original) filtCompls
+            compls = (fmap.fmap.fmap) (mkCompl pId ideOpts uri) uniqueFiltCompls
+            pId = lookupCommandProvider plugins (CommandId extendImportCommandId)
+        in
+          (fmap.fmap) snd $
+          sortBy (compare `on` lexicographicOrdering) $
+          mergeListsBy (flip compare `on` score)
+            [ (fmap.fmap) (notQual,) filtModNameCompls
+            , (fmap.fmap) (notQual,) filtKeywordCompls
+            , (fmap.fmap.fmap) (toggleSnippets caps config) compls
+            ]
+    where
       enteredQual = if T.null prefixScope then "" else prefixScope <> "."
       fullPrefix  = enteredQual <> prefixText
 
@@ -585,11 +629,9 @@
           $ Fuzzy.simpleFilter chunkSize maxC fullPrefix
           $ (if T.null enteredQual then id else mapMaybe (T.stripPrefix enteredQual))
             allModNamesAsNS
-
-      filtCompls = Fuzzy.filter chunkSize maxC prefixText ctxCompls (label . snd)
-        where
-
-          mcc = case maybe_parsed of
+      -- If we have a parsed module, use it to determine which completion to show.
+      maybeContext :: Maybe Context
+      maybeContext = case maybe_parsed of
             Nothing -> Nothing
             Just (pm, pmapping) ->
               let PositionMapping pDelta = pmapping
@@ -598,7 +640,9 @@
                   hpos = upperRange position'
               in getCContext lpos pm <|> getCContext hpos pm
 
-
+      filtCompls :: [Scored (Bool, CompItem)]
+      filtCompls = Fuzzy.filter chunkSize maxC prefixText ctxCompls (label . snd)
+        where
           -- We need the hieast to be "fresh". We can't get types from "stale" hie files, so hasfield won't work,
           -- since it gets the record fields from the types.
           -- Perhaps this could be fixed with a refactor to GHC's IfaceTyCon, to have it also contain record fields.
@@ -636,7 +680,7 @@
                 })
 
           -- completions specific to the current context
-          ctxCompls' = case mcc of
+          ctxCompls' = case maybeContext of
                         Nothing           -> compls
                         Just TypeContext  -> filter ( isTypeCompl . snd) compls
                         Just ValueContext -> filter (not . isTypeCompl . snd) compls
@@ -677,54 +721,36 @@
         , enteredQual `T.isPrefixOf` original label
         ]
 
+      moduleImportListCompletions :: String -> [Scored CompletionItem]
+      moduleImportListCompletions moduleNameS =
+        let moduleName = T.pack moduleNameS
+            funcs = lookupWithDefaultUFM moduleExportsMap HashSet.empty $ mkModuleName moduleNameS
+            funs = map (show . name) $ HashSet.toList funcs
+        in filterModuleExports moduleName $ map T.pack funs
+
+      filtImportCompls :: [Scored CompletionItem]
       filtImportCompls = filtListWith (mkImportCompl enteredQual) importableModules
+
+      filterModuleExports :: T.Text -> [T.Text] -> [Scored CompletionItem]
       filterModuleExports moduleName = filtListWith $ mkModuleFunctionImport moduleName
+
+      filtKeywordCompls :: [Scored CompletionItem]
       filtKeywordCompls
           | T.null prefixScope = filtListWith mkExtCompl (optKeywords ideOpts)
           | otherwise = []
 
-  if
-    -- TODO: handle multiline imports
-    | "import " `T.isPrefixOf` fullLine
-      && (List.length (words (T.unpack fullLine)) >= 2)
-      && "(" `isInfixOf` T.unpack fullLine
-    -> do
-      let moduleName = words (T.unpack fullLine) !! 1
-          funcs = lookupWithDefaultUFM moduleExportsMap HashSet.empty $ mkModuleName moduleName
-          funs = map (renderOcc . name) $ HashSet.toList funcs
-      return $ filterModuleExports (T.pack moduleName) funs
-    | "import " `T.isPrefixOf` fullLine
-    -> return filtImportCompls
-    -- we leave this condition here to avoid duplications and return empty list
-    -- since HLS implements these completions (#haskell-language-server/pull/662)
-    | "{-# " `T.isPrefixOf` fullLine
-    -> return []
-    | otherwise -> do
-        -- assumes that nubOrdBy is stable
-        let uniqueFiltCompls = nubOrdBy (uniqueCompl `on` snd . Fuzzy.original) filtCompls
-        let compls = (fmap.fmap.fmap) (mkCompl pId ideOpts uri) uniqueFiltCompls
-            pId = lookupCommandProvider plugins (CommandId extendImportCommandId)
-        return $
-          (fmap.fmap) snd $
-          sortBy (compare `on` lexicographicOrdering) $
-          mergeListsBy (flip compare `on` score)
-            [ (fmap.fmap) (notQual,) filtModNameCompls
-            , (fmap.fmap) (notQual,) filtKeywordCompls
-            , (fmap.fmap.fmap) (toggleSnippets caps config) compls
-            ]
-    where
-        -- We use this ordering to alphabetically sort suggestions while respecting
-        -- all the previously applied ordering sources. These are:
-        --  1. Qualified suggestions go first
-        --  2. Fuzzy score ranks next
-        --  3. In-scope completions rank next
-        --  4. label alphabetical ordering next
-        --  4. detail alphabetical ordering (proxy for module)
-        lexicographicOrdering Fuzzy.Scored{score, original} =
-          case original of
-            (isQual, CompletionItem{_label,_detail}) -> do
-              let isLocal = maybe False (":" `T.isPrefixOf`) _detail
-              (Down isQual, Down score, Down isLocal, _label, _detail)
+      -- We use this ordering to alphabetically sort suggestions while respecting
+      -- all the previously applied ordering sources. These are:
+      --  1. Qualified suggestions go first
+      --  2. Fuzzy score ranks next
+      --  3. In-scope completions rank next
+      --  4. label alphabetical ordering next
+      --  4. detail alphabetical ordering (proxy for module)
+      lexicographicOrdering Fuzzy.Scored{score, original} =
+        case original of
+          (isQual, CompletionItem{_label,_detail}) -> do
+            let isLocal = maybe False (":" `T.isPrefixOf`) _detail
+            (Down isQual, Down score, Down isLocal, _label, _detail)
 
 
 
diff --git a/src/Development/IDE/Plugin/HLS.hs b/src/Development/IDE/Plugin/HLS.hs
--- a/src/Development/IDE/Plugin/HLS.hs
+++ b/src/Development/IDE/Plugin/HLS.hs
@@ -23,6 +23,7 @@
 import           Data.List.NonEmpty            (NonEmpty, nonEmpty, toList)
 import qualified Data.List.NonEmpty            as NE
 import qualified Data.Map                      as Map
+import           Data.Maybe                    (mapMaybe)
 import           Data.Some
 import           Data.String
 import           Data.Text                     (Text)
@@ -36,6 +37,7 @@
 import           Ide.Logger
 import           Ide.Plugin.Config
 import           Ide.Plugin.Error
+import           Ide.Plugin.HandleRequestTypes
 import           Ide.PluginUtils               (getClientConfig)
 import           Ide.Types                     as HLS
 import           Language.LSP.Protocol.Message
@@ -65,23 +67,29 @@
     LogResponseError (PluginId pId) err ->
       pretty pId <> ":" <+> pretty err
     LogNoPluginForMethod (Some method) ->
-        "No plugin enabled for " <> pretty method
+        "No plugin handles this " <> pretty method <> " request."
     LogInvalidCommandIdentifier-> "Invalid command identifier"
     ExceptionInPlugin plId (Some method) exception ->
         "Exception in plugin " <> viaShow plId <> " while processing "
           <> pretty method <> ": " <> viaShow exception
 instance Show Log where show = renderString . layoutCompact . pretty
 
-noPluginEnabled :: Recorder (WithPriority Log) -> SMethod m -> [PluginId] -> IO (Either ResponseError c)
-noPluginEnabled recorder m fs' = do
+noPluginHandles :: Recorder (WithPriority Log) -> SMethod m -> [(PluginId, HandleRequestResult)] -> IO (Either ResponseError c)
+noPluginHandles recorder m fs' = do
   logWith recorder Warning (LogNoPluginForMethod $ Some m)
   let err = ResponseError (InR ErrorCodes_MethodNotFound) msg Nothing
-      msg = pluginNotEnabled m fs'
+      msg = noPluginHandlesMsg m fs'
   return $ Left err
-  where pluginNotEnabled :: SMethod m -> [PluginId] -> Text
-        pluginNotEnabled method availPlugins =
-            "No plugin enabled for " <> T.pack (show method) <> ", potentially available: "
-                <> (T.intercalate ", " $ map (\(PluginId plid) -> plid) availPlugins)
+  where noPluginHandlesMsg :: SMethod m -> [(PluginId, HandleRequestResult)] -> Text
+        noPluginHandlesMsg method [] = "No plugins are available to handle this " <> T.pack (show method) <> " request."
+        noPluginHandlesMsg method availPlugins =
+            "No plugins are available to handle this " <> T.pack (show method) <> " request.\n Plugins installed for this method, but not available to handle this request are:\n"
+                <> (T.intercalate "\n" $
+                      map (\(PluginId plid, pluginStatus) ->
+                              plid
+                              <> " "
+                              <> (renderStrict . layoutCompact . pretty) pluginStatus)
+                          availPlugins)
 
 pluginDoesntExist :: PluginId -> Text
 pluginDoesntExist (PluginId pid) = "Plugin " <> pid <> " doesn't exist"
@@ -213,8 +221,8 @@
               res <- runExceptT (f ide a) `catchAny` -- See Note [Exception handling in plugins]
                 (\e -> pure $ Left $ PluginInternalError (exceptionInPlugin p SMethod_WorkspaceExecuteCommand e))
               case res of
-                (Left (PluginRequestRefused _)) ->
-                  liftIO $ noPluginEnabled recorder SMethod_WorkspaceExecuteCommand (fst <$> ecs)
+                (Left (PluginRequestRefused r)) ->
+                  liftIO $ noPluginHandles recorder SMethod_WorkspaceExecuteCommand [(p,DoesNotHandleRequest r)]
                 (Left pluginErr) -> do
                   liftIO $ logErrors recorder [(p, pluginErr)]
                   pure $ Left $ toResponseError (p, pluginErr)
@@ -236,11 +244,13 @@
       (IdeMethod m :=> IdeHandler fs') <- DMap.assocs handlers'
       pure $ requestHandler m $ \ide params -> do
         config <- Ide.PluginUtils.getClientConfig
-        -- Only run plugins that are allowed to run on this request
-        let fs = filter (\(_, desc, _) -> pluginEnabled m params desc config) fs'
+        -- Only run plugins that are allowed to run on this request, save the
+        -- list of disabled plugins incase that's all we have
+        let (fs, dfs) = List.partition (\(_, desc, _) -> handlesRequest m params desc config == HandlesRequest) fs'
+        let disabledPluginsReason = (\(x, desc, _) -> (x, handlesRequest m params desc config)) <$> dfs
         -- Clients generally don't display ResponseErrors so instead we log any that we come across
         case nonEmpty fs of
-          Nothing -> liftIO $ noPluginEnabled recorder m ((\(x, _, _) -> x) <$> fs')
+          Nothing -> liftIO $ noPluginHandles recorder m disabledPluginsReason
           Just neFs -> do
             let  plidsAndHandlers = fmap (\(plid,_,handler) -> (plid,handler)) neFs
             es <- runConcurrently exceptionInPlugin m plidsAndHandlers ide params
@@ -251,9 +261,12 @@
               Nothing -> do
                 let noRefused (_, PluginRequestRefused _) = False
                     noRefused (_, _)                      = True
-                    filteredErrs = filter noRefused errs
-                case nonEmpty filteredErrs of
-                  Nothing -> liftIO $ noPluginEnabled recorder m ((\(x, _, _) -> x) <$> fs')
+                    (asErrors, asRefused) = List.partition noRefused errs
+                    convertPRR (pId, PluginRequestRefused r) = Just (pId, DoesNotHandleRequest r)
+                    convertPRR _ = Nothing
+                    asRefusedReason = mapMaybe convertPRR asRefused
+                case nonEmpty asErrors of
+                  Nothing -> liftIO $ noPluginHandles recorder m  (disabledPluginsReason <> asRefusedReason)
                   Just xs -> pure $ Left $ combineErrors xs
               Just xs -> do
                 pure $ Right $ combineResponses m config caps params xs
@@ -274,8 +287,8 @@
       (IdeNotification m :=> IdeNotificationHandler fs') <- DMap.assocs handlers'
       pure $ notificationHandler m $ \ide vfs params -> do
         config <- Ide.PluginUtils.getClientConfig
-        -- Only run plugins that are allowed to run on this request
-        let fs = filter (\(_, desc, _) -> pluginEnabled m params desc config) fs'
+        -- Only run plugins that are enabled for this request
+        let fs = filter (\(_, desc, _) -> handlesRequest m params desc config == HandlesRequest) fs'
         case nonEmpty fs of
           Nothing -> do
             logWith recorder Warning (LogNoPluginForMethod $ Some m)
diff --git a/src/Development/IDE/Plugin/HLS/GhcIde.hs b/src/Development/IDE/Plugin/HLS/GhcIde.hs
--- a/src/Development/IDE/Plugin/HLS/GhcIde.hs
+++ b/src/Development/IDE/Plugin/HLS/GhcIde.hs
@@ -42,7 +42,7 @@
 -- ---------------------------------------------------------------------
 
 descriptor :: PluginId -> PluginDescriptor IdeState
-descriptor plId = (defaultPluginDescriptor plId)
+descriptor plId = (defaultPluginDescriptor plId desc)
   { pluginHandlers = mkPluginHandler SMethod_TextDocumentHover hover'
                   <> mkPluginHandler SMethod_TextDocumentDocumentSymbol moduleOutline
                   <> mkPluginHandler SMethod_TextDocumentDefinition (\ide _ DefinitionParams{..} ->
@@ -56,6 +56,8 @@
 
     pluginConfigDescriptor = defaultConfigDescriptor
   }
+  where
+    desc = "Provides core IDE features for Haskell"
 
 -- ---------------------------------------------------------------------
 
diff --git a/src/Development/IDE/Plugin/Test.hs b/src/Development/IDE/Plugin/Test.hs
--- a/src/Development/IDE/Plugin/Test.hs
+++ b/src/Development/IDE/Plugin/Test.hs
@@ -77,7 +77,7 @@
     deriving newtype (FromJSON, ToJSON)
 
 plugin :: PluginDescriptor IdeState
-plugin = (defaultPluginDescriptor "test") {
+plugin = (defaultPluginDescriptor "test" "") {
     pluginHandlers = mkPluginHandler (SMethod_CustomMethod (Proxy @"test")) $ \st _ ->
         testRequestHandler' st
     }
@@ -166,7 +166,7 @@
 blockCommandId = "ghcide.command.block"
 
 blockCommandDescriptor :: PluginId -> PluginDescriptor state
-blockCommandDescriptor plId = (defaultPluginDescriptor plId) {
+blockCommandDescriptor plId = (defaultPluginDescriptor plId "") {
     pluginCommands = [PluginCommand (CommandId blockCommandId) "blocks forever" blockCommandHandler]
 }
 
diff --git a/src/Development/IDE/Plugin/TypeLenses.hs b/src/Development/IDE/Plugin/TypeLenses.hs
--- a/src/Development/IDE/Plugin/TypeLenses.hs
+++ b/src/Development/IDE/Plugin/TypeLenses.hs
@@ -94,13 +94,15 @@
 
 descriptor :: Recorder (WithPriority Log) -> PluginId -> PluginDescriptor IdeState
 descriptor recorder plId =
-  (defaultPluginDescriptor plId)
+  (defaultPluginDescriptor plId desc)
     { pluginHandlers = mkPluginHandler SMethod_TextDocumentCodeLens codeLensProvider
                     <> mkResolveHandler SMethod_CodeLensResolve codeLensResolveProvider
     , pluginCommands = [PluginCommand (CommandId typeLensCommandId) "adds a signature" commandHandler]
     , pluginRules = rules recorder
     , pluginConfigDescriptor = defaultConfigDescriptor {configCustomConfig = mkCustomConfig properties}
     }
+  where
+    desc = "Provides code lenses type signatures"
 
 properties :: Properties '[ 'PropertyKey "mode" (TEnum Mode)]
 properties = emptyProperties
diff --git a/src/Development/IDE/Spans/AtPoint.hs b/src/Development/IDE/Spans/AtPoint.hs
--- a/src/Development/IDE/Spans/AtPoint.hs
+++ b/src/Development/IDE/Spans/AtPoint.hs
@@ -209,7 +209,7 @@
 atPoint
   :: IdeOptions
   -> HieAstResult
-  -> DocAndKindMap
+  -> DocAndTyThingMap
   -> HscEnv
   -> Position
   -> IO (Maybe (Maybe Range, [T.Text]))
@@ -346,7 +346,7 @@
 namesInType (AppTy a b)      = getTypes [a,b]
 namesInType (TyConApp tc ts) = tyConName tc : getTypes ts
 namesInType (ForAllTy b t)   = varName (binderVar b) : namesInType t
-namesInType (FunTy a b)      = getTypes [a,b]
+namesInType (FunTy _ a b)    = getTypes [a,b]
 namesInType (CastTy t _)     = namesInType t
 namesInType (LitTy _)        = []
 namesInType _                = []
diff --git a/src/Development/IDE/Spans/Common.hs b/src/Development/IDE/Spans/Common.hs
--- a/src/Development/IDE/Spans/Common.hs
+++ b/src/Development/IDE/Spans/Common.hs
@@ -12,7 +12,7 @@
 , spanDocToMarkdown
 , spanDocToMarkdownForTest
 , DocMap
-, KindMap
+, TyThingMap
 ) where
 
 import           Control.DeepSeq
@@ -31,7 +31,7 @@
 import qualified Documentation.Haddock.Types  as H
 
 type DocMap = NameEnv SpanDoc
-type KindMap = NameEnv TyThing
+type TyThingMap = NameEnv TyThing
 
 -- | Shows IEWrappedName, without any modifier, qualifier or unique identifier.
 #if MIN_VERSION_ghc(9,5,0)
diff --git a/src/Development/IDE/Spans/Documentation.hs b/src/Development/IDE/Spans/Documentation.hs
--- a/src/Development/IDE/Spans/Documentation.hs
+++ b/src/Development/IDE/Spans/Documentation.hs
@@ -39,7 +39,7 @@
   :: HscEnv
   -> RefMap a
   -> TcGblEnv
-  -> IO DocAndKindMap
+  -> IO DocAndTyThingMap
 mkDocMap env rm this_mod =
   do
 #if MIN_VERSION_ghc(9,3,0)
@@ -61,8 +61,7 @@
       doc <- getDocumentationTryGhc env n
       pure $ extendNameEnv nameMap n doc
     getType n nameMap
-      | isTcOcc $ occName n
-      , Nothing <- lookupNameEnv nameMap n
+      | Nothing <- lookupNameEnv nameMap n
       = do kind <- lookupKind env n
            pure $ maybe nameMap (extendNameEnv nameMap n) kind
       | otherwise = pure nameMap
diff --git a/src/Development/IDE/Types/KnownTargets.hs b/src/Development/IDE/Types/KnownTargets.hs
--- a/src/Development/IDE/Types/KnownTargets.hs
+++ b/src/Development/IDE/Types/KnownTargets.hs
@@ -17,7 +17,7 @@
 type KnownTargets = HashMap Target (HashSet NormalizedFilePath)
 
 data Target = TargetModule ModuleName | TargetFile NormalizedFilePath
-  deriving ( Eq, Generic, Show )
+  deriving ( Eq, Ord, Generic, Show )
   deriving anyclass (Hashable, NFData)
 
 toKnownFiles :: KnownTargets -> HashSet NormalizedFilePath
diff --git a/test/data/multi-unit-reexport/a/A.hs b/test/data/multi-unit-reexport/a/A.hs
new file mode 100644
--- /dev/null
+++ b/test/data/multi-unit-reexport/a/A.hs
@@ -0,0 +1,3 @@
+module A(foo) where
+import Data.Text
+foo = ()
diff --git a/test/data/multi-unit-reexport/b/B.hs b/test/data/multi-unit-reexport/b/B.hs
new file mode 100644
--- /dev/null
+++ b/test/data/multi-unit-reexport/b/B.hs
@@ -0,0 +1,3 @@
+module B(module B) where
+import A
+qux = foo
diff --git a/test/data/multi-unit-reexport/c/C.hs b/test/data/multi-unit-reexport/c/C.hs
new file mode 100644
--- /dev/null
+++ b/test/data/multi-unit-reexport/c/C.hs
@@ -0,0 +1,4 @@
+module C(module C) where
+import A
+import B
+cux = foo `seq` qux
diff --git a/test/data/multi-unit-reexport/cabal.project b/test/data/multi-unit-reexport/cabal.project
new file mode 100644
--- /dev/null
+++ b/test/data/multi-unit-reexport/cabal.project
@@ -0,0 +1,2 @@
+packages: a b c
+multi-repl: True
diff --git a/test/data/multi-unit-reexport/hie.yaml b/test/data/multi-unit-reexport/hie.yaml
new file mode 100644
--- /dev/null
+++ b/test/data/multi-unit-reexport/hie.yaml
@@ -0,0 +1,6 @@
+cradle:
+  direct:
+    arguments: ["-unit" ,"@a-1.0.0-inplace"
+               ,"-unit" ,"@b-1.0.0-inplace" 
+               ,"-unit" ,"@c-1.0.0-inplace"
+               ]
diff --git a/test/data/multi-unit/a/A.hs b/test/data/multi-unit/a/A.hs
new file mode 100644
--- /dev/null
+++ b/test/data/multi-unit/a/A.hs
@@ -0,0 +1,3 @@
+module A(foo) where
+import Data.Text
+foo = ()
diff --git a/test/data/multi-unit/b/B.hs b/test/data/multi-unit/b/B.hs
new file mode 100644
--- /dev/null
+++ b/test/data/multi-unit/b/B.hs
@@ -0,0 +1,3 @@
+module B(module B) where
+import A
+qux = foo
diff --git a/test/data/multi-unit/c/C.hs b/test/data/multi-unit/c/C.hs
new file mode 100644
--- /dev/null
+++ b/test/data/multi-unit/c/C.hs
@@ -0,0 +1,3 @@
+module C(module C) where
+import A
+cux = foo
diff --git a/test/data/multi-unit/cabal.project b/test/data/multi-unit/cabal.project
new file mode 100644
--- /dev/null
+++ b/test/data/multi-unit/cabal.project
@@ -0,0 +1,2 @@
+packages: a b c
+multi-repl: True
diff --git a/test/data/multi-unit/hie.yaml b/test/data/multi-unit/hie.yaml
new file mode 100644
--- /dev/null
+++ b/test/data/multi-unit/hie.yaml
@@ -0,0 +1,6 @@
+cradle:
+  direct:
+    arguments: ["-unit" ,"@a-1.0.0-inplace"
+               ,"-unit" ,"@b-1.0.0-inplace" 
+               ,"-unit" ,"@c-1.0.0-inplace"
+               ]
diff --git a/test/data/working-dir/a/A.hs b/test/data/working-dir/a/A.hs
new file mode 100644
--- /dev/null
+++ b/test/data/working-dir/a/A.hs
@@ -0,0 +1,11 @@
+{-# LANGUAGE TemplateHaskell #-}
+module A(th_a) where
+
+import Language.Haskell.TH
+import Language.Haskell.TH.Syntax
+import Control.Monad.IO.Class
+
+th_a :: DecsQ
+th_a = do
+  str <- makeRelativeToProject "wdtest" >>= liftIO . readFile
+  [d| a = $(lift str) |]
diff --git a/test/data/working-dir/a/B.hs b/test/data/working-dir/a/B.hs
new file mode 100644
--- /dev/null
+++ b/test/data/working-dir/a/B.hs
@@ -0,0 +1,6 @@
+{-# LANGUAGE TemplateHaskell #-}
+module B() where
+
+import A
+
+$th_a
diff --git a/test/data/working-dir/a/a.cabal b/test/data/working-dir/a/a.cabal
new file mode 100644
--- /dev/null
+++ b/test/data/working-dir/a/a.cabal
@@ -0,0 +1,11 @@
+name: a
+version: 1.0.0
+build-type: Simple
+cabal-version: >= 1.2
+extra-source-files: wdtest
+
+library
+  build-depends: base, template-haskell
+  exposed-modules: A B
+  ghc-options: -Wmissing-signatures
+  hs-source-dirs: .
diff --git a/test/data/working-dir/cabal.project b/test/data/working-dir/cabal.project
new file mode 100644
--- /dev/null
+++ b/test/data/working-dir/cabal.project
@@ -0,0 +1,1 @@
+packages: a
diff --git a/test/data/working-dir/hie.yaml b/test/data/working-dir/hie.yaml
new file mode 100644
--- /dev/null
+++ b/test/data/working-dir/hie.yaml
@@ -0,0 +1,2 @@
+cradle:
+  cabal:
diff --git a/test/exe/CradleTests.hs b/test/exe/CradleTests.hs
--- a/test/exe/CradleTests.hs
+++ b/test/exe/CradleTests.hs
@@ -40,8 +40,12 @@
     [testGroup "dependencies" [sessionDepsArePickedUp]
     ,testGroup "ignore-fatal" [ignoreFatalWarning]
     ,testGroup "loading" [loadCradleOnlyonce, retryFailedCradle]
-    ,testGroup "multi"   [simpleMultiTest, simpleMultiTest2, simpleMultiTest3, simpleMultiDefTest]
+    ,testGroup "multi"   (multiTests "multi")
+    ,ignoreFor (BrokenForGHC [GHC92]) "multiple units not supported on 9.2"
+       $ testGroup "multi-unit" (multiTests "multi-unit")
     ,testGroup "sub-directory"   [simpleSubDirectoryTest]
+    ,ignoreFor (BrokenForGHC [GHC92]) "multiple units not supported on 9.2"
+      $ testGroup "multi-unit-rexport" [multiRexportTest]
     ]
 
 loadCradleOnlyonce :: TestTree
@@ -113,8 +117,15 @@
       ]
     expectNoMoreDiagnostics 0.5
 
-simpleMultiTest :: TestTree
-simpleMultiTest = testCase "simple-multi-test" $ withLongTimeout $ runWithExtraFiles "multi" $ \dir -> do
+multiTests :: FilePath -> [TestTree]
+multiTests dir =
+  [simpleMultiTest dir, simpleMultiTest2 dir, simpleMultiTest3 dir, simpleMultiDefTest dir]
+
+multiTestName :: FilePath -> String -> String
+multiTestName dir name = "simple-" ++ dir ++ "-" ++ name
+
+simpleMultiTest :: FilePath -> TestTree
+simpleMultiTest variant = testCase (multiTestName variant "test") $ withLongTimeout $ runWithExtraFiles variant $ \dir -> do
     let aPath = dir </> "a/A.hs"
         bPath = dir </> "b/B.hs"
     adoc <- openDoc aPath "haskell"
@@ -129,8 +140,8 @@
     expectNoMoreDiagnostics 0.5
 
 -- Like simpleMultiTest but open the files in the other order
-simpleMultiTest2 :: TestTree
-simpleMultiTest2 = testCase "simple-multi-test2" $ runWithExtraFiles "multi" $ \dir -> do
+simpleMultiTest2 :: FilePath -> TestTree
+simpleMultiTest2 variant = testCase (multiTestName variant "test2") $ runWithExtraFiles variant $ \dir -> do
     let aPath = dir </> "a/A.hs"
         bPath = dir </> "b/B.hs"
     bdoc <- openDoc bPath "haskell"
@@ -143,9 +154,9 @@
     expectNoMoreDiagnostics 0.5
 
 -- Now with 3 components
-simpleMultiTest3 :: TestTree
-simpleMultiTest3 =
-  testCase "simple-multi-test3" $ runWithExtraFiles "multi" $ \dir -> do
+simpleMultiTest3 :: FilePath -> TestTree
+simpleMultiTest3 variant =
+  testCase (multiTestName variant "test3") $ runWithExtraFiles variant $ \dir -> do
     let aPath = dir </> "a/A.hs"
         bPath = dir </> "b/B.hs"
         cPath = dir </> "c/C.hs"
@@ -161,8 +172,8 @@
     expectNoMoreDiagnostics 0.5
 
 -- Like simpleMultiTest but open the files in component 'a' in a separate session
-simpleMultiDefTest :: TestTree
-simpleMultiDefTest = testCase "simple-multi-def-test" $ runWithExtraFiles "multi" $ \dir -> do
+simpleMultiDefTest :: FilePath -> TestTree
+simpleMultiDefTest variant = testCase (multiTestName variant "def-test") $ runWithExtraFiles variant $ \dir -> do
     let aPath = dir </> "a/A.hs"
         bPath = dir </> "b/B.hs"
     adoc <- liftIO $ runInDir dir $ do
@@ -178,6 +189,17 @@
     checkDefs locs (pure [fooL])
     expectNoMoreDiagnostics 0.5
 
+multiRexportTest :: TestTree
+multiRexportTest =
+  testCase "multi-unit-reexport-test"  $ runWithExtraFiles "multi-unit-reexport" $ \dir -> do
+    let cPath = dir </> "c/C.hs"
+    cdoc <- openDoc cPath "haskell"
+    WaitForIdeRuleResult {} <- waitForAction "TypeCheck" cdoc
+    locs <- getDefinitions cdoc (Position 3 7)
+    let aPath = dir </> "a/A.hs"
+    let fooL = mkL (filePathToUri aPath) 2 0 2 3
+    checkDefs locs (pure [fooL])
+    expectNoMoreDiagnostics 0.5
 
 sessionDepsArePickedUp :: TestTree
 sessionDepsArePickedUp = testSession'
diff --git a/test/exe/DiagnosticTests.hs b/test/exe/DiagnosticTests.hs
--- a/test/exe/DiagnosticTests.hs
+++ b/test/exe/DiagnosticTests.hs
@@ -232,6 +232,34 @@
       _ <- createDoc "ModuleB.hs" "haskell" contentB
       _ <- createDoc "ModuleB.hs-boot" "haskell" contentBboot
       expectDiagnostics [("ModuleB.hs", [(DiagnosticSeverity_Warning, (3,0), "Top-level binding")])]
+  , testSession' "bidirectional module dependency with hs-boot" $ \path -> do
+      let cradle = unlines
+            [ "cradle:"
+            , "  direct: {arguments: [ModuleA, ModuleB]}"
+            ]
+      let contentA = T.unlines
+            [ "module ModuleA where"
+            , "import {-# SOURCE #-} ModuleB"
+            ]
+      let contentB = T.unlines
+            [ "{-# OPTIONS -Wmissing-signatures#-}"
+            , "module ModuleB where"
+            , "import {-# SOURCE #-} ModuleA"
+            -- introduce an artificial diagnostic
+            , "foo = ()"
+            ]
+      let contentBboot = T.unlines
+            [ "module ModuleB where"
+            ]
+      let contentAboot = T.unlines
+            [ "module ModuleA where"
+            ]
+      liftIO $ writeFile (path </> "hie.yaml") cradle
+      _ <- createDoc "ModuleA.hs" "haskell" contentA
+      _ <- createDoc "ModuleA.hs-boot" "haskell" contentAboot
+      _ <- createDoc "ModuleB.hs" "haskell" contentB
+      _ <- createDoc "ModuleB.hs-boot" "haskell" contentBboot
+      expectDiagnostics [("ModuleB.hs", [(DiagnosticSeverity_Warning, (3,0), "Top-level binding")])]
   , testSessionWait "correct reference used with hs-boot" $ do
       let contentB = T.unlines
             [ "module ModuleB where"
diff --git a/test/exe/ExceptionTests.hs b/test/exe/ExceptionTests.hs
--- a/test/exe/ExceptionTests.hs
+++ b/test/exe/ExceptionTests.hs
@@ -18,6 +18,7 @@
 import           Ide.Logger                        (Logger, Recorder,
                                                     WithPriority, cmapWithPrio)
 import           Ide.Plugin.Error
+import           Ide.Plugin.HandleRequestTypes     (RejectionReason (DisabledGlobally))
 import           Ide.PluginUtils                   (idePluginsToPluginDesc,
                                                     pluginDescToIdePlugins)
 import           Ide.Types
@@ -41,7 +42,7 @@
       [ testCase "PluginHandlers" $ do
           let pluginId = "plugin-handler-exception"
               plugins = pluginDescToIdePlugins $
-                  [ (defaultPluginDescriptor pluginId)
+                  [ (defaultPluginDescriptor pluginId "")
                       { pluginHandlers = mconcat
                           [ mkPluginHandler SMethod_TextDocumentCodeLens $ \_ _ _-> do
                               _ <- liftIO $ throwIO DivideByZero
@@ -62,7 +63,7 @@
           let pluginId = "command-exception"
               commandId = CommandId "exception"
               plugins = pluginDescToIdePlugins $
-                  [ (defaultPluginDescriptor pluginId)
+                  [ (defaultPluginDescriptor pluginId "")
                       { pluginCommands =
                           [ PluginCommand commandId "Causes an exception" $ \_ (_::Int) -> do
                               _ <- liftIO $ throwIO DivideByZero
@@ -84,7 +85,7 @@
         , testCase "Notification Handlers" $ do
           let pluginId = "notification-exception"
               plugins = pluginDescToIdePlugins $
-                  [ (defaultPluginDescriptor pluginId)
+                  [ (defaultPluginDescriptor pluginId "")
                       { pluginNotificationHandlers = mconcat
                           [  mkPluginNotificationHandler SMethod_TextDocumentDidOpen $ \_ _ _ _ ->
                               liftIO $ throwIO DivideByZero
@@ -106,9 +107,9 @@
                 _ -> liftIO $ assertFailure $ "We should have had an empty list" <> show lens]
 
    , testGroup "Testing PluginError order..."
-      [ pluginOrderTestCase recorder logger  "InternalError over InvalidParams" PluginInternalError PluginInvalidParams
-      , pluginOrderTestCase recorder logger  "InvalidParams over InvalidUserState" PluginInvalidParams PluginInvalidUserState
-      , pluginOrderTestCase recorder logger  "InvalidUserState over RequestRefused" PluginInvalidUserState PluginRequestRefused
+      [ pluginOrderTestCase recorder logger  "InternalError over InvalidParams" (PluginInternalError "error test") (PluginInvalidParams "error test")
+      , pluginOrderTestCase recorder logger  "InvalidParams over InvalidUserState" (PluginInvalidParams "error test") (PluginInvalidUserState "error test")
+      , pluginOrderTestCase recorder logger  "InvalidUserState over RequestRefused" (PluginInvalidUserState "error test") (PluginRequestRefused DisabledGlobally)
       ]
    ]
 
@@ -132,17 +133,17 @@
       , IDE.argsIdeOptions = ideOptions
       }
 
-pluginOrderTestCase :: Recorder (WithPriority Log) -> Logger -> TestName -> (T.Text -> PluginError) -> (T.Text -> PluginError) -> TestTree
+pluginOrderTestCase :: Recorder (WithPriority Log) -> Logger -> TestName -> PluginError -> PluginError -> TestTree
 pluginOrderTestCase recorder logger msg err1 err2 =
   testCase msg $ do
       let pluginId = "error-order-test"
           plugins = pluginDescToIdePlugins $
-              [ (defaultPluginDescriptor pluginId)
+              [ (defaultPluginDescriptor pluginId "")
                   { pluginHandlers = mconcat
                       [ mkPluginHandler SMethod_TextDocumentCodeLens $ \_ _ _-> do
-                          throwError $ err1 "error test"
+                          throwError err1
                         ,mkPluginHandler SMethod_TextDocumentCodeLens $ \_ _ _-> do
-                          throwError $ err2 "error test"
+                          throwError err2
                       ]
                   }]
       testIde recorder (testingLite recorder logger plugins) $ do
@@ -150,6 +151,6 @@
           waitForProgressDone
           (view L.result -> lens) <- request SMethod_TextDocumentCodeLens (CodeLensParams Nothing Nothing doc)
           case lens of
-            Left re | toResponseError (pluginId, err1 "error test") == re -> pure ()
+            Left re | toResponseError (pluginId, err1) == re -> pure ()
                     | otherwise -> liftIO $ assertFailure "We caught an error, but it wasn't ours!"
             _ -> liftIO $ assertFailure $ show lens
diff --git a/test/exe/PositionMappingTests.hs b/test/exe/PositionMappingTests.hs
--- a/test/exe/PositionMappingTests.hs
+++ b/test/exe/PositionMappingTests.hs
@@ -3,6 +3,7 @@
 
 module PositionMappingTests (tests) where
 
+import qualified Data.EnumMap.Strict                  as EM
 import           Data.Row
 import qualified Data.Text                            as T
 import           Data.Text.Utf16.Rope                 (Rope)
@@ -10,7 +11,8 @@
 import           Development.IDE.Core.PositionMapping (PositionResult (..),
                                                        fromCurrent,
                                                        positionResultToMaybe,
-                                                       toCurrent)
+                                                       toCurrent,
+                                                       toCurrentPosition)
 import           Development.IDE.Types.Location
 import           Language.LSP.Protocol.Types          hiding
                                                       (SemanticTokenAbsolute (..),
@@ -20,15 +22,36 @@
 import           Language.LSP.VFS                     (applyChange)
 import           Test.QuickCheck
 -- import Test.QuickCheck.Instances ()
+import           Control.Arrow                        (second)
 import           Data.Functor.Identity                (runIdentity)
+import           Data.Text                            (Text)
+import           Development.IDE.Core.Shake           (updatePositionMappingHelper)
 import           Test.Tasty
 import           Test.Tasty.HUnit
 import           Test.Tasty.QuickCheck
 
+enumMapMappingTest :: TestTree
+enumMapMappingTest = testCase "enumMapMappingTest" $ do
+  let mkChangeEvent :: Range -> Text -> TextDocumentContentChangeEvent
+      mkChangeEvent r t = TextDocumentContentChangeEvent $ InL $ #range .== r .+ #rangeLength .== Nothing .+ #text .== t
+      mkCE :: UInt -> UInt -> UInt -> UInt -> Text -> TextDocumentContentChangeEvent
+      mkCE l1 c1 l2 c2 = mkChangeEvent (Range (Position l1 c1) (Position l2 c2))
+      events :: [(Int32, [TextDocumentContentChangeEvent])]
+      events = map (second return) [(0, mkCE 0 0 0 0 ""), (1, mkCE 0 1 0 1 " "), (2, mkCE 0 2 0 2 " "), (3, mkCE 0 3 0 3 " "), (4, mkCE 0 4 0 4 " "), (5, mkCE 0 5 0 5 " ")]
+      finalMap = Prelude.foldl (\m (i, e) -> updatePositionMappingHelper i e m) mempty events
+  let updatePose fromPos = do
+        mapping <- snd <$> EM.lookup 0 finalMap
+        toCurrentPosition mapping fromPos
+  updatePose (Position 0 4) @?= Just (Position 0 9)
+  updatePose (Position 0 5) @?= Just (Position 0 10)
+
+
 tests ::  TestTree
 tests =
     testGroup "position mapping"
-        [ testGroup "toCurrent"
+        [
+        enumMapMappingTest
+        , testGroup "toCurrent"
               [ testCase "before" $
                 toCurrent
                     (Range (Position 0 1) (Position 0 3))
diff --git a/test/exe/THTests.hs b/test/exe/THTests.hs
--- a/test/exe/THTests.hs
+++ b/test/exe/THTests.hs
@@ -7,7 +7,6 @@
 import           Data.Row
 import qualified Data.Text                   as T
 import           Development.IDE.GHC.Util
-import           Development.IDE.GHC.Compat
 import           Development.IDE.Test        (expectCurrentDiagnostics,
                                               expectDiagnostics,
                                               expectNoMoreDiagnostics)
@@ -72,7 +71,6 @@
     , thReloadingTest False
     , thLoadingTest
     , thCoreTest
-    , ignoreFor (BrokenForGHC [GHC92]) "ghc 9.2 doesn't support -working-dir" thWorkingDirTest
     , ignoreInWindowsBecause "Broken in windows" $ thReloadingTest True
     -- Regression test for https://github.com/haskell/haskell-language-server/issues/891
     , thLinkingTest False
@@ -110,13 +108,6 @@
     expectDiagnostics [ ( cPath, [(DiagnosticSeverity_Warning, (3, 0), "Top-level binding with no type signature: a :: A")] ) ]
     ]
 
-
--- | Test that all modules have linkables
-thWorkingDirTest :: TestTree
-thWorkingDirTest = testCase "working dir is set" $ runWithExtraFiles "working-dir" $ \dir -> do
-    let thb = dir </> "a" </> "B.hs"
-    _ <- openDoc thb "haskell"
-    expectDiagnostics [("a" </> "B.hs", [(DiagnosticSeverity_Warning, (5,thDollarIdx), "Top-level binding")])]
 
 -- | Test that all modules have linkables
 thLoadingTest :: TestTree
diff --git a/test/exe/UnitTests.hs b/test/exe/UnitTests.hs
--- a/test/exe/UnitTests.hs
+++ b/test/exe/UnitTests.hs
@@ -80,7 +80,7 @@
                     }
                     | i <- [1..20]
                 ] ++ Ghcide.descriptors (cmapWithPrio LogGhcIde recorder)
-            priorityPluginDescriptor i = (defaultPluginDescriptor $ fromString $ show i){pluginPriority = i}
+            priorityPluginDescriptor i = (defaultPluginDescriptor (fromString $ show i) ""){pluginPriority = i}
 
         testIde recorder (IDE.testing (cmapWithPrio LogIDEMain recorder) logger plugins) $ do
             _ <- createDoc "A.hs" "haskell" "module A where"
