From ef143683cfaf2e13f86c29e3c89fc27b22b3d2bb Mon Sep 17 00:00:00 2001 From: Alan Zimmerman Date: Sat, 25 Aug 2018 20:10:08 +0200 Subject: [PATCH] Revert #777 and #782, performance was awful Closes #519 --- docs/Architecture.md | 82 ++++++-- .../Haskell/Ide/Engine/GhcModuleCache.hs | 3 +- .../Haskell/Ide/Engine/ModuleCache.hs | 108 ++++++---- hie-plugin-api/Haskell/Ide/Engine/Monad.hs | 2 +- .../Haskell/Ide/Engine/PluginDescriptor.hs | 2 +- .../Haskell/Ide/Engine/PluginUtils.hs | 11 + .../Haskell/Ide/Engine/PluginsIdeMonads.hs | 165 +++++++++------ hie-plugin-api/hie-plugin-api.cabal | 1 - src/Haskell/Ide/Engine/Dispatcher.hs | 83 ++++---- src/Haskell/Ide/Engine/LSP/CodeActions.hs | 4 +- src/Haskell/Ide/Engine/LSP/Reactor.hs | 2 +- src/Haskell/Ide/Engine/Plugin/ApplyRefact.hs | 6 +- src/Haskell/Ide/Engine/Plugin/GhcMod.hs | 190 +++++++++--------- src/Haskell/Ide/Engine/Plugin/HaRe.hs | 8 +- src/Haskell/Ide/Engine/Plugin/Haddock.hs | 6 +- src/Haskell/Ide/Engine/Plugin/HieExtras.hs | 93 +++++---- src/Haskell/Ide/Engine/Plugin/HsImport.hs | 4 +- src/Haskell/Ide/Engine/Plugin/Liquid.hs | 8 +- src/Haskell/Ide/Engine/Plugin/Package.hs | 2 +- src/Haskell/Ide/Engine/Transport/LspStdio.hs | 6 +- src/Haskell/Ide/Engine/Types.hs | 4 +- test/dispatcher/Main.hs | 14 +- test/functional/DeferredSpec.hs | 5 +- test/unit/HaRePluginSpec.hs | 10 +- 24 files changed, 485 insertions(+), 334 deletions(-) diff --git a/docs/Architecture.md b/docs/Architecture.md index 81b57929e..e5d0572a5 100644 --- a/docs/Architecture.md +++ b/docs/Architecture.md @@ -40,7 +40,7 @@ getCachedModule :: Uri -> IdeM (CachedModuleResult) -- along with the cache or error if present data CachedModuleResult = ModuleLoading -- ^ The module has no cache yet and has not failed - | ModuleFailed + | ModuleFailed T.Text -- ^ The module has no cache but something went wrong | ModuleCached CachedModule IsStale -- ^ A cache exists for the module @@ -80,8 +80,7 @@ class Typeable a => ModuleCache a where cacheDataProducer :: CachedModule -> IdeM a withCachedModuleAndData :: forall a b. ModuleCache a - => FilePath -> b - -> (CachedModule -> a -> IdeM b) -> IdeM b + => Uri -> IdeM b -> (CachedModule -> a -> IdeM b) -> IdeM b withCachedModuleAndData uri noCache callback = ... ``` @@ -107,7 +106,7 @@ This data is used to find all references to a symbol, and to find the name corre a particular position in the source. ```haskell -getReferencesInDoc :: Uri -> Position -> IdeM (IdeResult [J.DocumentHighlight]) +getReferencesInDoc :: Uri -> Position -> IdeM (IdeResponse [J.DocumentHighlight]) getReferencesInDoc uri pos = do let noCache = return $ nonExistentCacheErr "getReferencesInDoc" withCachedModuleAndData uri noCache $ @@ -146,7 +145,7 @@ data GhcRequest m = forall a. GhcRequest data IdeRequest m = forall a. IdeRequest { pureReqId :: J.LspId , pureReqCallback :: RequestCallback m a - , pureReq :: IdeM (IdeResult a) + , pureReq :: IdeM (IdeResponse a) } ``` @@ -177,26 +176,75 @@ for handling the "definition" request ... -- HaRePlugin.hs -findDef :: Uri -> Position -> IdeM (IdeResult Location) +findDef :: Uri -> Position -> IdeM (IdeResponse Location) ``` The request uses the `findDef` function in the `HaRe` plugin to get the `Location` of the definition of the symbol at the given position. The callback makes a LSP response message out of the location, and forwards it to thread #4 which sends -it to the IDE via stdout. +it to the IDE via stdout -## Deferred requests +## Responses and results -Should you find yourself wanting to access a typechecked module from within `IdeM`, -use `withCachedModule` to get access to a cached version of that module. -If there is no cached module available, then it will automatically defer your result, -or return a default if that then fails to typecheck: +While working in the `IdeGhcM` thread, you return results back to the dispatcher with +`IdeResult`: ```haskell -withCachedModule file (IdeResultOk []) $ \cm -> do - -- poke about with cm here +runHareCommand :: String -> RefactGhc [ApplyRefacResult] + -> IdeGhcM (IdeResult WorkspaceEdit) +runHareCommand name cmd = do + eitherRes <- runHareCommand' cmd + case eitherRes of + Left err -> + pure (IdeResultFail + (IdeError PluginError + (T.pack $ name <> ": \"" <> err <> "\"") + Null)) + Right res -> do + let changes = getRefactorResult res + refactRes <- makeRefactorResult changes + pure (IdeResultOk refactRes) ``` -Internally, a deferred response is represented by `IdeDefer`, which takes a file path -to a module, and a callback which will be executed with a `UriCache` passed as an -argument as soon as the module is loaded, or a `UriCacheFailed` if it failed. +On `IdeM`, you must wrap any `IdeResult` in an `IdeResponse`: + +```haskell +getDynFlags :: Uri -> IdeM (IdeResponse DynFlags) +getDynFlags uri = + pluginGetFileResponse "getDynFlags: " uri $ \fp -> do + mcm <- getCachedModule fp + case mcm of + ModuleCached cm _ -> return $ + IdeResponseOk $ ms_hspp_opts $ pm_mod_summary $ tm_parsed_module $ tcMod cm + _ -> return $ + IdeResponseFail $ + IdeError PluginError ("getDynFlags: \"" <> "module not loaded" <> "\"") Null +``` + +Sometimes a request may need access to the typechecked module from ghc-mod, but +it is desirable to keep it on the `IdeM` thread. For this a deferred response can +be made: + +```haskell +getDynFlags :: Uri -> IdeM (IdeResponse DynFlags) +getDynFlags uri = + pluginGetFileResponse "getDynFlags: " uri $ \fp -> do + mcm <- getCachedModule fp + return $ case mcm of + ModuleCached cm _ -> IdeResponseOk $ getFlags cm + _ -> IdeResponseDeferred fp getFlags + where getFlags = ms_hspp_opts $ pm_mod_summary $ tm_parsed_module $ tcMod cm +``` + +A deferred response takes a file path to a module, and a callback which will be executed +as with the cached module passed as an argument as soon as the module is loaded. + +This is wrapped with the helper function `withCachedModule` which will immediately return +the cached module if it is already available to use, and only defer it otherwise. + +```haskell +getDynFlags :: Uri -> IdeM (IdeResponse DynFlags) +getDynFlags uri = + pluginGetFileResponse "getDynFlags: " uri $ \fp -> + withCachedModule fp (return . IdeResponseOk . ms_hspp_opts . pm_mod_summary . tm_parsed_module . tcMod) +``` diff --git a/hie-plugin-api/Haskell/Ide/Engine/GhcModuleCache.hs b/hie-plugin-api/Haskell/Ide/Engine/GhcModuleCache.hs index cbeeaa275..d40fd318f 100644 --- a/hie-plugin-api/Haskell/Ide/Engine/GhcModuleCache.hs +++ b/hie-plugin-api/Haskell/Ide/Engine/GhcModuleCache.hs @@ -4,6 +4,7 @@ module Haskell.Ide.Engine.GhcModuleCache where +import qualified Data.Text as T import qualified Data.Map as Map import Data.Dynamic (Dynamic) import Data.Typeable (TypeRep) @@ -22,7 +23,7 @@ data UriCache = UriCache { cachedModule :: !CachedModule , cachedData :: !(Map.Map TypeRep Dynamic) , isStale :: !Bool - } | UriCacheFailed deriving Show + } | UriCacheFailed T.Text deriving Show data CachedModule = CachedModule { tcMod :: !TypecheckedModule diff --git a/hie-plugin-api/Haskell/Ide/Engine/ModuleCache.hs b/hie-plugin-api/Haskell/Ide/Engine/ModuleCache.hs index 3ddcd5d8d..724184a9d 100644 --- a/hie-plugin-api/Haskell/Ide/Engine/ModuleCache.hs +++ b/hie-plugin-api/Haskell/Ide/Engine/ModuleCache.hs @@ -6,11 +6,12 @@ module Haskell.Ide.Engine.ModuleCache where import Control.Monad import Control.Monad.IO.Class import Control.Monad.Trans.Control -import Control.Monad.Trans.Free +import qualified Data.Aeson as J import Data.Dynamic (toDyn, fromDynamic) import Data.Generics (Proxy(..), typeRep, typeOf) import qualified Data.Map as Map import Data.Maybe +import qualified Data.Text as T import Data.Typeable (Typeable) import Exception (ExceptionMonad) import System.Directory @@ -79,22 +80,22 @@ getCradle fp = do -- along with the cache or error if present data CachedModuleResult = ModuleLoading -- ^ The module has no cache yet and has not failed - | ModuleFailed + | ModuleFailed T.Text -- ^ The module has no cache because something went wrong | ModuleCached CachedModule IsStale -- ^ A cache exists for the module type IsStale = Bool -- | looks up a CachedModule for a given URI -getCachedModule :: (MonadIO m, HasGhcModuleCache m) +getCachedModule :: (GM.MonadIO m, HasGhcModuleCache m) => FilePath -> m CachedModuleResult getCachedModule uri = do uri' <- liftIO $ canonicalizePath uri maybeUriCache <- fmap (Map.lookup uri' . uriCaches) getModuleCache return $ case maybeUriCache of Nothing -> ModuleLoading - Just uriCache@UriCache {} -> ModuleCached (cachedModule uriCache) (isStale uriCache) - Just UriCacheFailed -> ModuleFailed + Just uriCache@(UriCache {}) -> ModuleCached (cachedModule uriCache) (isStale uriCache) + Just (UriCacheFailed err) -> ModuleFailed err -- | Returns true if there is a CachedModule for a given URI isCached :: (GM.MonadIO m, HasGhcModuleCache m) @@ -107,10 +108,22 @@ isCached uri = do -- | Version of `withCachedModuleAndData` that doesn't provide -- any extra cached data. -withCachedModule :: FilePath -> a -> (CachedModule -> IdeM a) -> IdeM a -withCachedModule fp def callback = wrap (IdeDefer fp go) - where go UriCache{cachedModule = cm} = callback cm - go _ = return def +withCachedModule :: FilePath -> (CachedModule -> IdeM (IdeResponse b)) -> IdeM (IdeResponse b) +withCachedModule uri callback = withCachedModuleDefault uri Nothing callback + +-- | Version of `withCachedModuleAndData` that doesn't provide +-- any extra cached data. +withCachedModuleDefault :: FilePath -> Maybe (IdeResponse b) + -> (CachedModule -> IdeM (IdeResponse b)) -> IdeM (IdeResponse b) +withCachedModuleDefault uri mdef callback = do + mcm <- getCachedModule uri + uri' <- liftIO $ canonicalizePath uri + case mcm of + ModuleCached cm _ -> callback cm + ModuleLoading -> return $ IdeResponseDeferred uri' callback + ModuleFailed err -> case mdef of + Nothing -> return $ IdeResponseFail (IdeError NoModuleAvailable err J.Null) + Just def -> return def -- | Calls its argument with the CachedModule for a given URI -- along with any data that might be stored in the ModuleCache. @@ -121,26 +134,36 @@ withCachedModule fp def callback = wrap (IdeDefer fp go) -- If the data doesn't exist in the cache, new data is generated -- using by calling the `cacheDataProducer` function. withCachedModuleAndData :: forall a b. ModuleCache a - => FilePath -> b - -> (CachedModule -> a -> IdeM b) -> IdeM b -withCachedModuleAndData fp def callback = wrap (IdeDefer fp go) - where go UriCacheFailed = return def - go UriCache{cachedModule = cm, cachedData = dat} = do - fp' <- liftIO $ canonicalizePath fp - let proxy :: Proxy a - proxy = Proxy - a <- case Map.lookup (typeRep proxy) dat of - Nothing -> do - val <- cacheDataProducer cm - let dat' = Map.insert (typeOf val) (toDyn val) dat - modifyCache (\s -> s {uriCaches = Map.insert fp' (UriCache cm dat' False) - (uriCaches s)}) - return val - Just x -> - case fromDynamic x of - Just val -> return val - Nothing -> error "impossible" - callback cm a + => FilePath -> (CachedModule -> a -> IdeM (IdeResponse b)) -> IdeM (IdeResponse b) +withCachedModuleAndData uri callback = withCachedModuleAndDataDefault uri Nothing callback + +withCachedModuleAndDataDefault :: forall a b. ModuleCache a + => FilePath -> Maybe (IdeResponse b) + -> (CachedModule -> a -> IdeM (IdeResponse b)) -> IdeM (IdeResponse b) +withCachedModuleAndDataDefault uri mdef callback = do + uri' <- liftIO $ canonicalizePath uri + mcache <- getModuleCache + let mc = (Map.lookup uri' . uriCaches) mcache + case mc of + Nothing -> return $ IdeResponseDeferred uri' $ \_ -> withCachedModuleAndData uri callback + Just (UriCacheFailed err) -> case mdef of + Nothing -> return $ IdeResponseFail (IdeError NoModuleAvailable err J.Null) + Just def -> return def + Just UriCache{cachedModule = cm, cachedData = dat} -> do + let proxy :: Proxy a + proxy = Proxy + a <- case Map.lookup (typeRep proxy) dat of + Nothing -> do + val <- cacheDataProducer cm + let dat' = Map.insert (typeOf val) (toDyn val) dat + modifyCache (\s -> s {uriCaches = Map.insert uri' (UriCache cm dat' False) + (uriCaches s)}) + return val + Just x -> + case fromDynamic x of + Just val -> return val + Nothing -> error "impossible" + callback cm a -- | Saves a module to the cache and executes any deferred -- responses waiting on that module. @@ -148,39 +171,40 @@ cacheModule :: FilePath -> CachedModule -> IdeGhcM () cacheModule uri cm = do uri' <- liftIO $ canonicalizePath uri - let uc = UriCache cm Map.empty False - - modifyCache $ \gmc -> - gmc { uriCaches = Map.insert uri' uc (uriCaches gmc) } + modifyCache (\gmc -> + gmc { uriCaches = Map.insert + uri' + (UriCache cm Map.empty False) + (uriCaches gmc) + } + ) -- execute any queued actions for the module - runDeferredActions uri' uc + runDeferredActions uri' (Right cm) -- | Marks a module that it failed to load and triggers -- any deferred responses waiting on it -failModule :: FilePath -> IdeGhcM () -failModule fp = do +failModule :: FilePath -> T.Text -> IdeGhcM () +failModule fp err = do fp' <- liftIO $ canonicalizePath fp maybeUriCache <- fmap (Map.lookup fp' . uriCaches) getModuleCache - let uc = UriCacheFailed - case maybeUriCache of Just _ -> return () - Nothing -> + Nothing -> do -- If there's no cache for the module mark it as failed modifyCache (\gmc -> gmc { - uriCaches = Map.insert fp' uc (uriCaches gmc) + uriCaches = Map.insert fp' (UriCacheFailed err) (uriCaches gmc) } ) -- Fail the queued actions - runDeferredActions fp' uc + runDeferredActions fp' (Left err) -runDeferredActions :: FilePath -> UriCache -> IdeGhcM () +runDeferredActions :: FilePath -> Either T.Text CachedModule -> IdeGhcM () runDeferredActions uri cached = do actions <- fmap (fromMaybe [] . Map.lookup uri) (requestQueue <$> readMTS) liftToGhc $ forM_ actions (\a -> a cached) diff --git a/hie-plugin-api/Haskell/Ide/Engine/Monad.hs b/hie-plugin-api/Haskell/Ide/Engine/Monad.hs index e67fe7367..1d1a7b764 100644 --- a/hie-plugin-api/Haskell/Ide/Engine/Monad.hs +++ b/hie-plugin-api/Haskell/Ide/Engine/Monad.hs @@ -2,7 +2,7 @@ module Haskell.Ide.Engine.Monad where import Control.Exception import Control.Monad.IO.Class -import Control.Monad.Reader +import Control.Monad.Reader import qualified GhcMod.Monad as GM import qualified GhcMod.Types as GM import Haskell.Ide.Engine.MonadTypes diff --git a/hie-plugin-api/Haskell/Ide/Engine/PluginDescriptor.hs b/hie-plugin-api/Haskell/Ide/Engine/PluginDescriptor.hs index 37394fdad..7bd1a5534 100644 --- a/hie-plugin-api/Haskell/Ide/Engine/PluginDescriptor.hs +++ b/hie-plugin-api/Haskell/Ide/Engine/PluginDescriptor.hs @@ -43,7 +43,7 @@ toDynJSON = CD.toDyn -- arguments in the form of a JSON object. runPluginCommand :: PluginId -> CommandName -> Value -> IdeGhcM (IdeResult DynamicJSON) runPluginCommand p com arg = do - (IdePlugins m) <- lift $ lift $ lift getPlugins + (IdePlugins m) <- lift . lift $ getPlugins case Map.lookup p m of Nothing -> return $ IdeResultFail $ IdeError UnknownPlugin ("Plugin " <> p <> " doesn't exist") Null diff --git a/hie-plugin-api/Haskell/Ide/Engine/PluginUtils.hs b/hie-plugin-api/Haskell/Ide/Engine/PluginUtils.hs index 2625cae2c..ae9dff510 100644 --- a/hie-plugin-api/Haskell/Ide/Engine/PluginUtils.hs +++ b/hie-plugin-api/Haskell/Ide/Engine/PluginUtils.hs @@ -8,6 +8,7 @@ module Haskell.Ide.Engine.PluginUtils ( mapEithers , pluginGetFile + , pluginGetFileResponse , makeDiffResult , WithDeletions(..) , makeAdditiveDiffResult @@ -121,6 +122,16 @@ pluginGetFile name uri f = Nothing -> return $ IdeResultFail (IdeError PluginError (name <> "Couldn't resolve uri" <> getUri uri) Null) +-- | @pluginGetFile but for IdeResponse - use with IdeM +pluginGetFileResponse + :: Monad m + => T.Text -> Uri -> (FilePath -> m (IdeResponse a)) -> m (IdeResponse a) +pluginGetFileResponse name uri f = + case uriToFilePath uri of + Just file -> f file + Nothing -> return $ IdeResponseFail (IdeError PluginError + (name <> "Couldn't resolve uri" <> getUri uri) Null) + -- --------------------------------------------------------------------- -- courtesy of http://stackoverflow.com/questions/19891061/mapeithers-function-in-haskell mapEithers :: (a -> Either b c) -> [a] -> Either b [c] diff --git a/hie-plugin-api/Haskell/Ide/Engine/PluginsIdeMonads.hs b/hie-plugin-api/Haskell/Ide/Engine/PluginsIdeMonads.hs index 60523517f..20e1f4855 100644 --- a/hie-plugin-api/Haskell/Ide/Engine/PluginsIdeMonads.hs +++ b/hie-plugin-api/Haskell/Ide/Engine/PluginsIdeMonads.hs @@ -1,5 +1,4 @@ {-# LANGUAGE DeriveGeneric #-} -{-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE MultiParamTypeClasses #-} @@ -32,15 +31,18 @@ module Haskell.Ide.Engine.PluginsIdeMonads , SymbolProvider , IdePlugins(..) -- * The IDE monad - , IdeState(..) , IdeGhcM + , IdeState(..) , IdeM - , iterT , LiftsToGhc(..) - -- * IdeResult + -- * IdeResult and IdeResponse , IdeResult(..) , IdeResultT(..) - , IdeDefer(..) + , pattern IdeResponseOk + , pattern IdeResponseFail + , IdeResponse + , IdeResponse'(..) + , IdeResponseT(..) , IdeError(..) , IdeErrorCode(..) -- * LSP types @@ -62,7 +64,6 @@ module Haskell.Ide.Engine.PluginsIdeMonads import Control.Concurrent.STM import Control.Monad.IO.Class import Control.Monad.Reader -import Control.Monad.Trans.Free import Data.Aeson import Data.Dynamic (Dynamic) @@ -101,7 +102,6 @@ import Language.Haskell.LSP.Types (Command (..), WorkspaceEdit (..), filePathToUri, uriToFilePath) -import System.Directory type PluginId = T.Text @@ -116,7 +116,7 @@ data PluginCommand = forall a b. (FromJSON a, ToJSON b, Typeable b) => } -- --------------------------------------------------------------------- - + class Monad m => HasPidCache m where getPidCache :: m Int @@ -126,7 +126,7 @@ instance HasPidCache IdeM where instance HasPidCache IO where getPidCache = getProcessID -instance HasPidCache m => HasPidCache (IdeResultT m) where +instance HasPidCache m => HasPidCache (IdeResponseT m) where getPidCache = lift getPidCache mkLspCommand :: HasPidCache m => PluginId -> CommandName -> T.Text -> Maybe [Value] -> m Command @@ -152,7 +152,7 @@ type CodeActionProvider = PluginId -> Maybe FilePath -- ^ Project root directory -> Range -> CodeActionContext - -> IdeM (IdeResult [CodeAction]) + -> IdeM (IdeResponse [CodeAction]) -- type DiagnosticProviderFunc = DiagnosticTrigger -> Uri -> IdeM (IdeResponse (Map.Map Uri (S.Set Diagnostic))) type DiagnosticProviderFunc @@ -168,9 +168,9 @@ data DiagnosticTrigger = DiagnosticOnOpen | DiagnosticOnSave deriving (Show,Ord,Eq) -type HoverProvider = Uri -> Position -> IdeM (IdeResult [Hover]) +type HoverProvider = Uri -> Position -> IdeM (IdeResponse [Hover]) -type SymbolProvider = Uri -> IdeM (IdeResult [DocumentSymbol]) +type SymbolProvider = Uri -> IdeM (IdeResponse [DocumentSymbol]) data PluginDescriptor = PluginDescriptor { pluginId :: PluginId @@ -198,67 +198,39 @@ instance ToJSON IdePlugins where -- --------------------------------------------------------------------- -type IdeGhcM = GM.GhcModT IdeBase - --- | A computation that is deferred until the module is cached. --- Note that the module may not typecheck, in which case 'UriCacheFailed' is passed -data IdeDefer a = IdeDefer FilePath (UriCache -> a) deriving Functor -type IdeM = FreeT IdeDefer IdeBase - -type IdeBase = ReaderT ClientCapabilities (MultiThreadState IdeState) - -data IdeState = IdeState - { moduleCache :: GhcModuleCache - -- | A queue of requests to be performed once a module is loaded - , requestQueue :: Map.Map FilePath [UriCache -> IdeBase ()] - , idePlugins :: IdePlugins - , extensibleState :: !(Map.Map TypeRep Dynamic) - , ghcSession :: Maybe (IORef HscEnv) - -- The pid of this instance of hie - , idePidCache :: Int - } +type IdeGhcM = GM.GhcModT IdeM instance MonadMTState IdeState IdeGhcM where readMTS = lift $ lift $ lift readMTS - modifyMTS = lift . lift . lift . modifyMTS - + modifyMTS f = lift $ lift $ lift $ modifyMTS f + +type IdeM = ReaderT ClientCapabilities (MultiThreadState IdeState) + instance MonadMTState IdeState IdeM where - readMTS = lift $ lift readMTS - modifyMTS = lift . lift . modifyMTS + readMTS = lift readMTS + modifyMTS = lift . modifyMTS class (Monad m) => LiftsToGhc m where liftToGhc :: m a -> IdeGhcM a -instance GM.MonadIO IdeM where - liftIO = liftIO - instance LiftsToGhc IdeM where - liftToGhc (FreeT f) = do - x <- liftToGhc f - case x of - Pure a -> return a - Free (IdeDefer fp cb) -> do - fp' <- liftIO $ canonicalizePath fp - muc <- fmap (Map.lookup fp' . uriCaches) getModuleCache - liftToGhc $ case muc of - Just uc -> cb uc - Nothing -> cb UriCacheFailed - -instance LiftsToGhc IdeBase where liftToGhc = lift . lift instance LiftsToGhc IdeGhcM where liftToGhc = id -instance HasGhcModuleCache IdeGhcM where - getModuleCache = lift $ lift getModuleCache - setModuleCache = lift . lift . setModuleCache +data IdeState = IdeState + { moduleCache :: GhcModuleCache + -- | A queue of requests to be performed once a module is loaded + , requestQueue :: Map.Map FilePath [Either T.Text CachedModule -> IdeM ()] + , idePlugins :: IdePlugins + , extensibleState :: !(Map.Map TypeRep Dynamic) + , ghcSession :: Maybe (IORef HscEnv) + -- The pid of this instance of hie + , idePidCache :: Int + } instance HasGhcModuleCache IdeM where - getModuleCache = lift getModuleCache - setModuleCache = lift . setModuleCache - -instance HasGhcModuleCache IdeBase where getModuleCache = do tvar <- lift ask state <- liftIO $ readTVarIO tvar @@ -267,6 +239,12 @@ instance HasGhcModuleCache IdeBase where tvar <- lift ask liftIO $ atomically $ modifyTVar' tvar (\st -> st { moduleCache = mc }) +instance HasGhcModuleCache IdeGhcM where + getModuleCache = lift . lift $ getModuleCache + setModuleCache = lift . lift . setModuleCache + + + -- --------------------------------------------------------------------- @@ -312,12 +290,83 @@ instance (Monad m) => Monad (IdeResultT m) where instance MonadTrans IdeResultT where lift m = IdeResultT (fmap IdeResultOk m) +-- | The IDE response, which wraps around an IdeResult that may be deferred. +-- Used mostly in IdeM. +data IdeResponse' m a = IdeResponseDeferred FilePath (CachedModule -> m (IdeResponse' m a)) + | IdeResponseResult (IdeResult a) + +type IdeResponse a = IdeResponse' IdeM a + +pattern IdeResponseOk :: a -> IdeResponse' m a +pattern IdeResponseOk a = IdeResponseResult (IdeResultOk a) +pattern IdeResponseFail :: IdeError -> IdeResponse' m a +pattern IdeResponseFail err = IdeResponseResult (IdeResultFail err) + +instance (Show a) => Show (IdeResponse' m a) where + show (IdeResponseResult x) = show x + show (IdeResponseDeferred fp _) = "Deferred response waiting on " ++ fp + +instance (Eq a) => Eq (IdeResponse' m a) where + (IdeResponseResult x) == (IdeResponseResult y) = x == y + _ == _ = False + +instance Monad m => Functor (IdeResponse' m) where + fmap f (IdeResponseResult (IdeResultOk x)) = IdeResponseOk (f x) + fmap _ (IdeResponseResult (IdeResultFail err)) = IdeResponseFail err + fmap f (IdeResponseDeferred fp cb) = IdeResponseDeferred fp $ cb >=> (return . fmap f) + +instance Monad m => Applicative (IdeResponse' m) where + pure = return + + (IdeResponseResult (IdeResultFail err)) <*> _ = IdeResponseFail err + _ <*> (IdeResponseResult (IdeResultFail err)) = IdeResponseFail err + + (IdeResponseResult (IdeResultOk f)) <*> (IdeResponseResult (IdeResultOk x)) = IdeResponseOk (f x) + + (IdeResponseResult (IdeResultOk f)) <*> (IdeResponseDeferred fp cb) = IdeResponseDeferred fp $ fmap (fmap f) . cb + + (IdeResponseDeferred fp cb) <*> x = IdeResponseDeferred fp $ \cm -> do + f <- cb cm + pure (f <*> x) + +instance Monad m => Monad (IdeResponse' m) where + (IdeResponseResult (IdeResultOk x)) >>= f = f x + (IdeResponseDeferred fp cb) >>= f = IdeResponseDeferred fp $ \cm -> do + x <- cb cm + return $ x >>= f + (IdeResponseResult (IdeResultFail err)) >>= _ = IdeResponseFail err + return = IdeResponseOk + +newtype IdeResponseT m a = IdeResponseT { runIdeResponseT :: m (IdeResponse' m a) } + +instance Monad m => Functor (IdeResponseT m) where + fmap = liftM + +instance Monad m => Applicative (IdeResponseT m) where + pure = return + (<*>) = ap + +instance (Monad m) => Monad (IdeResponseT m) where + return = IdeResponseT . return . IdeResponseOk + + m >>= f = IdeResponseT $ do + v <- runIdeResponseT m + case v of + IdeResponseResult (IdeResultOk x) -> runIdeResponseT (f x) + IdeResponseResult (IdeResultFail err) -> return $ IdeResponseFail err + IdeResponseDeferred fp cb -> return $ IdeResponseDeferred fp $ \cm -> + runIdeResponseT $ IdeResponseT (cb cm) >>= f + +instance MonadTrans IdeResponseT where + lift m = IdeResponseT (fmap IdeResponseOk m) + -- | Error codes. Add as required data IdeErrorCode = ParameterError -- ^ Wrong parameter type | PluginError -- ^ An error returned by a plugin | InternalError -- ^ Code error (case not handled or deemed -- impossible) + | NoModuleAvailable -- ^ No typechecked module available to use | UnknownPlugin -- ^ Plugin is not registered | UnknownCommand -- ^ Command is not registered | InvalidContext -- ^ Context invalid for command @@ -336,4 +385,4 @@ data IdeError = IdeError deriving (Show,Read,Eq,Generic) instance ToJSON IdeError -instance FromJSON IdeError \ No newline at end of file +instance FromJSON IdeError diff --git a/hie-plugin-api/hie-plugin-api.cabal b/hie-plugin-api/hie-plugin-api.cabal index af5ebfb42..74362c104 100644 --- a/hie-plugin-api/hie-plugin-api.cabal +++ b/hie-plugin-api/hie-plugin-api.cabal @@ -38,7 +38,6 @@ library , directory , filepath , fingertree - , free , ghc , ghc-mod-core >= 5.9.0.0 , haskell-lsp >= 0.5 diff --git a/src/Haskell/Ide/Engine/Dispatcher.hs b/src/Haskell/Ide/Engine/Dispatcher.hs index b81f4ab0e..e95fff03c 100644 --- a/src/Haskell/Ide/Engine/Dispatcher.hs +++ b/src/Haskell/Ide/Engine/Dispatcher.hs @@ -3,7 +3,7 @@ {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE RankNTypes #-} -{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE LambdaCase #-} module Haskell.Ide.Engine.Dispatcher ( dispatcherP @@ -20,6 +20,7 @@ import Control.Monad import Control.Monad.IO.Class import Control.Monad.Reader import Control.Monad.STM +import qualified Data.Aeson as J import qualified Data.Text as T import qualified Data.Map as Map import qualified Data.Set as S @@ -31,7 +32,6 @@ import Haskell.Ide.Engine.Types import Haskell.Ide.Engine.Monad import qualified Language.Haskell.LSP.Types as J import qualified Language.Haskell.LSP.Types.Capabilities as J -import System.Directory data DispatcherEnv = DispatcherEnv { cancelReqsTVar :: !(TVar (S.Set J.LspId)) @@ -66,7 +66,8 @@ dispatcherP inChan plugins ghcModOptions env errorHandler callbackHandler caps = ghcDispatcher env errorHandler callbackHandler ghcChan runIdeDisp = do stateVar <- readMVar stateVarVar - ideDispatcher stateVar caps env errorHandler callbackHandler ideChan + flip runReaderT stateVar $ flip runReaderT caps $ + ideDispatcher env errorHandler callbackHandler ideChan runMainDisp = mainDispatcher inChan ghcChan ideChan runGhcDisp `race_` runIdeDisp `race_` runMainDisp @@ -80,36 +81,46 @@ mainDispatcher inChan ghcChan ideChan = forever $ do Left r -> atomically $ writeTChan ideChan r -ideDispatcher :: forall void m. TVar IdeState -> J.ClientCapabilities - -> DispatcherEnv -> ErrorHandler -> CallbackHandler m - -> TChan (IdeRequest m) -> IO void -ideDispatcher stateVar caps env errorHandler callbackHandler pin = - flip runReaderT stateVar $ flip runReaderT caps $ forever $ do - debugm "ideDispatcher: top of loop" - (IdeRequest tn lid callback action) <- liftIO $ atomically $ readTChan pin - debugm $ "ideDispatcher: got request " ++ show tn ++ " with id: " ++ show lid - - iterT queueDeferred $ - checkCancelled env lid errorHandler $ do - result <- action - checkCancelled env lid errorHandler $ liftIO $ do - completedReq env lid - case result of - IdeResultOk x -> callbackHandler callback x - IdeResultFail (IdeError _ msg _) -> errorHandler lid J.InternalError msg - - where queueDeferred (IdeDefer fp cacheCb) = do - uri' <- liftIO $ canonicalizePath fp - muc <- fmap (Map.lookup uri' . uriCaches) getModuleCache - case muc of - Just uc -> cacheCb uc - Nothing -> lift $ modifyMTState $ \s -> - let oldQueue = requestQueue s - -- add to existing queue if possible - update Nothing = [cacheCb] - update (Just x) = cacheCb : x - newQueue = Map.alter (Just . update) fp oldQueue - in s { requestQueue = newQueue } +ideDispatcher :: forall void m. DispatcherEnv -> ErrorHandler -> CallbackHandler m -> TChan (IdeRequest m) -> IdeM void +ideDispatcher env errorHandler callbackHandler pin = forever $ do + debugm "ideDispatcher: top of loop" + (IdeRequest tn lid callback action) <- liftIO $ atomically $ readTChan pin + debugm $ "ideDispatcher: got request " ++ show tn ++ " with id: " ++ show lid + checkCancelled env lid errorHandler $ do + response <- action + handleResponse lid callback response + + where handleResponse lid callback response = + -- Need to check cancellation twice since cancellation + -- request might have come in during the action + checkCancelled env lid errorHandler $ case response of + IdeResponseResult (IdeResultOk x) -> liftIO $ do + completedReq env lid + callbackHandler callback x + IdeResponseResult (IdeResultFail (IdeError code msg _)) -> liftIO $ do + completedReq env lid + case code of + -- TODO: This isn't actually an internal error + NoModuleAvailable -> errorHandler lid J.InternalError msg + _ -> errorHandler lid J.InternalError msg + IdeResponseDeferred fp cacheCb -> handleDeferred lid fp cacheCb callback + + handleDeferred lid fp cacheCb actualCb = queueAction fp $ \case + Right cm -> do + cacheResponse <- cacheCb cm + handleResponse lid actualCb cacheResponse + Left err -> + handleResponse lid actualCb (IdeResponseFail (IdeError NoModuleAvailable err J.Null)) + + queueAction :: FilePath -> (Either T.Text CachedModule -> IdeM ()) -> IdeM () + queueAction fp action = + lift $ modifyMTState $ \s -> + let oldQueue = requestQueue s + -- add to existing queue if possible + update Nothing = [action] + update (Just x) = action : x + newQueue = Map.alter (Just . update) fp oldQueue + in s { requestQueue = newQueue } ghcDispatcher :: forall void m. DispatcherEnv -> ErrorHandler -> CallbackHandler m -> TChan (GhcRequest m) -> IdeGhcM void ghcDispatcher env@DispatcherEnv{docVersionTVar} errorHandler callbackHandler pin = forever $ do @@ -129,9 +140,11 @@ ghcDispatcher env@DispatcherEnv{docVersionTVar} errorHandler callbackHandler pin result <- runner action liftIO $ case result of IdeResultOk x -> callbackHandler callback x - IdeResultFail err@(IdeError _ msg _) -> + IdeResultFail err@(IdeError code msg _) -> case mid of - Just lid -> errorHandler lid J.InternalError msg + Just lid -> case code of + NoModuleAvailable -> errorHandler lid J.ParseError msg + _ -> errorHandler lid J.InternalError msg Nothing -> debugm $ "ghcDispatcher:Got error for a request: " ++ show err let runIfVersionMatch = case mver of diff --git a/src/Haskell/Ide/Engine/LSP/CodeActions.hs b/src/Haskell/Ide/Engine/LSP/CodeActions.hs index 85fb7761e..8b876434f 100644 --- a/src/Haskell/Ide/Engine/LSP/CodeActions.hs +++ b/src/Haskell/Ide/Engine/LSP/CodeActions.hs @@ -39,8 +39,8 @@ handleCodeActionReq tn req = do let getProvider p = pluginCodeActionProvider p <*> return (pluginId p) getProviders = do - IdePlugins m <- getPlugins - return $ IdeResultOk $ mapMaybe getProvider $ toList m + IdePlugins m <- lift getPlugins + return $ IdeResponseOk $ mapMaybe getProvider $ toList m providersCb providers = let reqs = map (\f -> f docId maybeRootDir range context) providers diff --git a/src/Haskell/Ide/Engine/LSP/Reactor.hs b/src/Haskell/Ide/Engine/LSP/Reactor.hs index 86cd721c2..474f82fba 100644 --- a/src/Haskell/Ide/Engine/LSP/Reactor.hs +++ b/src/Haskell/Ide/Engine/LSP/Reactor.hs @@ -95,7 +95,7 @@ writePluginReq req lid = do writeTChan cin req -- | Execute multiple ide requests sequentially -makeRequests :: [IdeM (IdeResult a)] -- ^ The requests to make +makeRequests :: [IdeM (IdeResponse a)] -- ^ The requests to make -> TrackingNumber -> J.LspId -> ([a] -> R ()) -- ^ Callback with the request inputs and results diff --git a/src/Haskell/Ide/Engine/Plugin/ApplyRefact.hs b/src/Haskell/Ide/Engine/Plugin/ApplyRefact.hs index 59320d4fd..11f061968 100644 --- a/src/Haskell/Ide/Engine/Plugin/ApplyRefact.hs +++ b/src/Haskell/Ide/Engine/Plugin/ApplyRefact.hs @@ -241,7 +241,7 @@ applyHint fp mhint fileMap = do return diff -- | Gets HLint ideas for -getIdeas :: MonadIO m => FilePath -> Maybe OneHint -> ExceptT String m [Idea] +getIdeas :: FilePath -> Maybe OneHint -> ExceptT String IdeM [Idea] getIdeas lintFile mhint = do let hOpts = hlintOpts lintFile (oneHintPos <$> mhint) ideas <- runHlint lintFile hOpts @@ -263,7 +263,7 @@ hlintOpts lintFile mpos = opts = maybe "" posOpt mpos in [lintFile, "--quiet", "--refactor", "--refactor-options=" ++ opts ] -runHlint :: MonadIO m => FilePath -> [String] -> ExceptT String m [Idea] +runHlint :: FilePath -> [String] -> ExceptT String IdeM [Idea] runHlint fp args = do (flags,classify,hint) <- liftIO $ argsSettings args let myflags = flags { hseFlags = (hseFlags flags) { extensions = (EnableExtension TypeApplications:extensions (hseFlags flags))}} @@ -277,7 +277,7 @@ showParseError (Hlint.ParseError location message content) = -- --------------------------------------------------------------------- codeActionProvider :: CodeActionProvider -codeActionProvider plId docId _ _ context = IdeResultOk <$> hlintActions +codeActionProvider plId docId _ _ context = IdeResponseOk <$> hlintActions where hlintActions :: IdeM [LSP.CodeAction] diff --git a/src/Haskell/Ide/Engine/Plugin/GhcMod.hs b/src/Haskell/Ide/Engine/Plugin/GhcMod.hs index ec80ede3f..2576599f2 100644 --- a/src/Haskell/Ide/Engine/Plugin/GhcMod.hs +++ b/src/Haskell/Ide/Engine/Plugin/GhcMod.hs @@ -206,7 +206,7 @@ setTypecheckedModule uri = Nothing -> do debugm $ "setTypecheckedModule: Didn't get typechecked module for: " ++ show fp - failModule fp + failModule fp (T.unlines errs) return $ IdeResultOk (diags,errs) Just tm -> do @@ -336,7 +336,8 @@ splitCaseCmd' uri newPos = return $ oldToNewPositions checkedModule wEdit Nothing -> return mempty Nothing -> return mempty - _ -> return $ IdeResultOk mempty + ModuleFailed errText -> return $ IdeResultFail $ IdeError PluginError (T.append "hie-ghc-mod: " errText) Null + ModuleLoading -> return $ IdeResultOk mempty where -- | Transform all ranges in a WorkspaceEdit from old to new positions. @@ -419,7 +420,7 @@ codeActionProvider' supportsDocChanges _ docId _ _ context = redundantTerms = mapMaybe getRedundantImports diags redundantActions = concatMap (uncurry mkRedundantImportActions) redundantTerms typedHoleActions = concatMap mkTypedHoleActions (mapMaybe getTypedHoles diags) - in return $ IdeResultOk (renameActions ++ redundantActions ++ typedHoleActions) + in return $ IdeResponseOk (renameActions ++ redundantActions ++ typedHoleActions) where @@ -583,9 +584,9 @@ extractHoleSubstitutions diag -- --------------------------------------------------------------------- hoverProvider :: HoverProvider -hoverProvider doc pos = runIdeResultT $ do - info' <- IdeResultT $ newTypeCmd pos doc - names' <- IdeResultT $ Hie.getSymbolsAtPoint doc pos +hoverProvider doc pos = runIdeResponseT $ do + info' <- IdeResponseT $ IdeResponseResult <$> newTypeCmd pos doc + names' <- IdeResponseT $ Hie.getSymbolsAtPoint doc pos let f = (==) `on` (Hie.showName . snd) f' = compare `on` (Hie.showName . snd) @@ -619,92 +620,91 @@ hoverProvider doc pos = runIdeResultT $ do data Decl = Decl LSP.SymbolKind (Located RdrName) [Decl] SrcSpan | Import LSP.SymbolKind (Located ModuleName) [Decl] SrcSpan -symbolProvider :: Uri -> IdeM (IdeResult [LSP.DocumentSymbol]) -symbolProvider uri = pluginGetFile "ghc-mod symbolProvider: " uri $ - \file -> withCachedModule file (IdeResultOk []) $ \cm -> do - let tm = tcMod cm - hsMod = unLoc $ pm_parsed_source $ tm_parsed_module tm - imports = hsmodImports hsMod - imps = concatMap goImport imports - decls = concatMap go $ hsmodDecls hsMod - - go :: LHsDecl GM.GhcPs -> [Decl] - go (L l (TyClD FamDecl { tcdFam = FamilyDecl { fdLName = n } })) = pure (Decl LSP.SkClass n [] l) - go (L l (TyClD SynDecl { tcdLName = n })) = pure (Decl LSP.SkClass n [] l) - go (L l (TyClD DataDecl { tcdLName = n, tcdDataDefn = HsDataDefn { dd_cons = cons } })) = - pure (Decl LSP.SkClass n (concatMap processCon cons) l) - go (L l (TyClD ClassDecl { tcdLName = n, tcdSigs = sigs, tcdATs = fams })) = - pure (Decl LSP.SkInterface n children l) - where children = famDecls ++ sigDecls - famDecls = concatMap (go . fmap (TyClD . FamDecl)) fams - sigDecls = concatMap processSig sigs - - go (L l (ValD FunBind { fun_id = ln, fun_matches = MG { mg_alts = llms } })) = - pure (Decl LSP.SkFunction ln wheres l) - where - wheres = concatMap (gomatch . unLoc) (unLoc llms) - gomatch Match { m_grhss = GRHSs { grhssLocalBinds = lbs } } = golbs (unLoc lbs) - golbs (HsValBinds (ValBindsIn lhsbs _ )) = concatMap (go . fmap ValD) lhsbs - golbs _ = [] - - go (L l (ValD PatBind { pat_lhs = p })) = - map (\n -> Decl LSP.SkVariable n [] l) $ hsNamessRdr p - go (L l (ForD ForeignImport { fd_name = n })) = pure (Decl LSP.SkFunction n [] l) - go _ = [] - - processSig :: LSig GM.GhcPs -> [Decl] - processSig (L l (ClassOpSig False names _)) = - map (\n -> Decl LSP.SkMethod n [] l) names - processSig _ = [] - - processCon :: LConDecl GM.GhcPs -> [Decl] - processCon (L l ConDeclGADT { con_names = names }) = - map (\n -> Decl LSP.SkConstructor n [] l) names - processCon (L l ConDeclH98 { con_name = name, con_details = dets }) = - pure (Decl LSP.SkConstructor name xs l) - where - f (L fl ln) = Decl LSP.SkField ln [] fl - xs = case dets of - RecCon (L _ rs) -> concatMap (map (f . fmap rdrNameFieldOcc) - . cd_fld_names - . unLoc) rs - _ -> [] - - goImport :: LImportDecl GM.GhcPs -> [Decl] - goImport (L l ImportDecl { ideclName = lmn, ideclAs = as, ideclHiding = meis }) = pure im - where - im = Import imKind lmn xs l - imKind - | isJust as = LSP.SkNamespace - | otherwise = LSP.SkModule - xs = case meis of - Just (False, eis) -> concatMap f (unLoc eis) - _ -> [] - f (L l' (IEVar n)) = pure (Decl LSP.SkFunction (ieLWrappedName n) [] l') - f (L l' (IEThingAbs n)) = pure (Decl LSP.SkClass (ieLWrappedName n) [] l') - f (L l' (IEThingAll n)) = pure (Decl LSP.SkClass (ieLWrappedName n) [] l') - f (L l' (IEThingWith n _ vars fields)) = - let funcDecls = map (\n' -> Decl LSP.SkFunction (ieLWrappedName n') [] (getLoc n')) vars - fieldDecls = map (\f' -> Decl LSP.SkField (flSelector <$> f') [] (getLoc f')) fields - children = funcDecls ++ fieldDecls - in pure (Decl LSP.SkClass (ieLWrappedName n) children l') - f _ = [] - - declsToSymbolInf :: Decl -> IdeM [LSP.DocumentSymbol] - declsToSymbolInf (Decl kind (L nl rdrName) children l) = - declToSymbolInf' l kind nl (Hie.showName rdrName) children - declsToSymbolInf (Import kind (L nl modName) children l) = - declToSymbolInf' l kind nl (Hie.showName modName) children - - declToSymbolInf' :: SrcSpan -> LSP.SymbolKind -> SrcSpan -> T.Text -> [Decl] -> IdeM [LSP.DocumentSymbol] - declToSymbolInf' ss kind nss name children = do - childrenSymbols <- concat <$> mapM declsToSymbolInf children - case (srcSpan2Range ss, srcSpan2Range nss) of - (Right r, Right selR) -> - let chList = Just (LSP.List childrenSymbols) - in return $ pure $ - LSP.DocumentSymbol name (Just "") kind Nothing r selR chList - _ -> return childrenSymbols - - symInfs <- concat <$> mapM declsToSymbolInf (imps ++ decls) - return $ IdeResultOk symInfs \ No newline at end of file +symbolProvider :: Uri -> IdeM (IdeResponse [LSP.DocumentSymbol]) +symbolProvider uri = pluginGetFileResponse "ghc-mod symbolProvider: " uri $ \file -> withCachedModule file $ \cm -> do + let tm = tcMod cm + hsMod = unLoc $ pm_parsed_source $ tm_parsed_module tm + imports = hsmodImports hsMod + imps = concatMap goImport imports + decls = concatMap go $ hsmodDecls hsMod + + go :: LHsDecl GM.GhcPs -> [Decl] + go (L l (TyClD FamDecl { tcdFam = FamilyDecl { fdLName = n } })) = pure (Decl LSP.SkClass n [] l) + go (L l (TyClD SynDecl { tcdLName = n })) = pure (Decl LSP.SkClass n [] l) + go (L l (TyClD DataDecl { tcdLName = n, tcdDataDefn = HsDataDefn { dd_cons = cons } })) = + pure (Decl LSP.SkClass n (concatMap processCon cons) l) + go (L l (TyClD ClassDecl { tcdLName = n, tcdSigs = sigs, tcdATs = fams })) = + pure (Decl LSP.SkInterface n children l) + where children = famDecls ++ sigDecls + famDecls = concatMap (go . fmap (TyClD . FamDecl)) fams + sigDecls = concatMap processSig sigs + + go (L l (ValD FunBind { fun_id = ln, fun_matches = MG { mg_alts = llms } })) = + pure (Decl LSP.SkFunction ln wheres l) + where + wheres = concatMap (gomatch . unLoc) (unLoc llms) + gomatch Match { m_grhss = GRHSs { grhssLocalBinds = lbs } } = golbs (unLoc lbs) + golbs (HsValBinds (ValBindsIn lhsbs _ )) = concatMap (go . fmap ValD) lhsbs + golbs _ = [] + + go (L l (ValD PatBind { pat_lhs = p })) = + map (\n -> Decl LSP.SkVariable n [] l) $ hsNamessRdr p + go (L l (ForD ForeignImport { fd_name = n })) = pure (Decl LSP.SkFunction n [] l) + go _ = [] + + processSig :: LSig GM.GhcPs -> [Decl] + processSig (L l (ClassOpSig False names _)) = + map (\n -> Decl LSP.SkMethod n [] l) names + processSig _ = [] + + processCon :: LConDecl GM.GhcPs -> [Decl] + processCon (L l ConDeclGADT { con_names = names }) = + map (\n -> Decl LSP.SkConstructor n [] l) names + processCon (L l ConDeclH98 { con_name = name, con_details = dets }) = + pure (Decl LSP.SkConstructor name xs l) + where + f (L fl ln) = Decl LSP.SkField ln [] fl + xs = case dets of + RecCon (L _ rs) -> concatMap (map (f . fmap rdrNameFieldOcc) + . cd_fld_names + . unLoc) rs + _ -> [] + + goImport :: LImportDecl GM.GhcPs -> [Decl] + goImport (L l ImportDecl { ideclName = lmn, ideclAs = as, ideclHiding = meis }) = pure im + where + im = Import imKind lmn xs l + imKind + | isJust as = LSP.SkNamespace + | otherwise = LSP.SkModule + xs = case meis of + Just (False, eis) -> concatMap f (unLoc eis) + _ -> [] + f (L l' (IEVar n)) = pure (Decl LSP.SkFunction (ieLWrappedName n) [] l') + f (L l' (IEThingAbs n)) = pure (Decl LSP.SkClass (ieLWrappedName n) [] l') + f (L l' (IEThingAll n)) = pure (Decl LSP.SkClass (ieLWrappedName n) [] l') + f (L l' (IEThingWith n _ vars fields)) = + let funcDecls = map (\n' -> Decl LSP.SkFunction (ieLWrappedName n') [] (getLoc n')) vars + fieldDecls = map (\f' -> Decl LSP.SkField (flSelector <$> f') [] (getLoc f')) fields + children = funcDecls ++ fieldDecls + in pure (Decl LSP.SkClass (ieLWrappedName n) children l') + f _ = [] + + declsToSymbolInf :: Decl -> IdeM [LSP.DocumentSymbol] + declsToSymbolInf (Decl kind (L nl rdrName) children l) = + declToSymbolInf' l kind nl (Hie.showName rdrName) children + declsToSymbolInf (Import kind (L nl modName) children l) = + declToSymbolInf' l kind nl (Hie.showName modName) children + + declToSymbolInf' :: SrcSpan -> LSP.SymbolKind -> SrcSpan -> T.Text -> [Decl] -> IdeM [LSP.DocumentSymbol] + declToSymbolInf' ss kind nss name children = do + childrenSymbols <- concat <$> mapM declsToSymbolInf children + case (srcSpan2Range ss, srcSpan2Range nss) of + (Right r, Right selR) -> + let chList = Just (LSP.List childrenSymbols) + in return $ pure $ + LSP.DocumentSymbol name (Just "") kind Nothing r selR chList + _ -> return childrenSymbols + + symInfs <- concat <$> mapM declsToSymbolInf (imps ++ decls) + return $ IdeResponseOk symInfs diff --git a/src/Haskell/Ide/Engine/Plugin/HaRe.hs b/src/Haskell/Ide/Engine/Plugin/HaRe.hs index c16f47172..c78992705 100644 --- a/src/Haskell/Ide/Engine/Plugin/HaRe.hs +++ b/src/Haskell/Ide/Engine/Plugin/HaRe.hs @@ -292,21 +292,21 @@ hoist f a = codeActionProvider :: CodeActionProvider codeActionProvider pId docId _ (J.Range pos _) _ = - pluginGetFile "HaRe codeActionProvider: " (docId ^. J.uri) $ \file -> - withCachedModule file (IdeResultOk mempty) $ \cm -> do + pluginGetFileResponse "HaRe codeActionProvider: " (docId ^. J.uri) $ \file -> + withCachedModuleDefault file (Just (IdeResponseOk mempty)) $ \cm -> do let symbols = getArtifactsAtPos pos (defMap cm) debugm $ show $ map (Hie.showName . snd) symbols if not (null symbols) then let name = Hie.showName $ snd $ head symbols - in IdeResultOk <$> sequence [ + in IdeResponseOk <$> sequence [ mkLiftOneAction name , mkLiftTopAction name , mkDemoteAction name , mkDeleteAction name , mkDuplicateAction name ] - else return (IdeResultOk []) + else return (IdeResponseOk []) where mkLiftOneAction name = do diff --git a/src/Haskell/Ide/Engine/Plugin/Haddock.hs b/src/Haskell/Ide/Engine/Plugin/Haddock.hs index 724ca83df..f3b4d98eb 100644 --- a/src/Haskell/Ide/Engine/Plugin/Haddock.hs +++ b/src/Haskell/Ide/Engine/Plugin/Haddock.hs @@ -217,9 +217,9 @@ renderMarkDown = removeInner x = T.replace "```" "" $ T.replace "```haskell" "" x hoverProvider :: HoverProvider -hoverProvider doc pos = runIdeResultT $ do - df <- IdeResultT $ getDynFlags doc - names' <- IdeResultT $ getSymbolsAtPoint doc pos +hoverProvider doc pos = runIdeResponseT $ do + df <- IdeResponseT $ getDynFlags doc + names' <- IdeResponseT $ getSymbolsAtPoint doc pos let names = mapMaybe pickName $ groupBy f $ sortBy f' names' docs <- forM names $ \(_,name) -> do let sname = showName name diff --git a/src/Haskell/Ide/Engine/Plugin/HieExtras.hs b/src/Haskell/Ide/Engine/Plugin/HieExtras.hs index df8b3c335..3a1865340 100644 --- a/src/Haskell/Ide/Engine/Plugin/HieExtras.hs +++ b/src/Haskell/Ide/Engine/Plugin/HieExtras.hs @@ -32,6 +32,7 @@ import Exception import FastString import Finder import GHC +import qualified GhcMod.Error as GM import qualified GhcMod.LightGhc as GM import Haskell.Ide.Engine.ArtifactMap import Haskell.Ide.Engine.MonadFunctions @@ -51,12 +52,10 @@ import SrcLoc import TcEnv import Var -getDynFlags :: Uri -> IdeM (IdeResult DynFlags) +getDynFlags :: Uri -> IdeM (IdeResponse DynFlags) getDynFlags uri = - pluginGetFile "getDynFlags: " uri $ \fp -> - withCachedModule fp (IdeResultFail err) - (return . IdeResultOk . ms_hspp_opts . pm_mod_summary . tm_parsed_module . tcMod) - where err = IdeError InternalError "Unable to get DynFlags" Null + pluginGetFileResponse "getDynFlags: " uri $ \fp -> + withCachedModule fp (return . IdeResponseOk . ms_hspp_opts . pm_mod_summary . tm_parsed_module . tcMod) -- --------------------------------------------------------------------- @@ -248,23 +247,31 @@ instance ModuleCache CachedCompletions where , qualCompls = quals } -getCompletions :: Uri -> (T.Text, T.Text) -> IdeM (IdeResult [J.CompletionItem]) -getCompletions uri (qualifier, ident) = pluginGetFile "getCompletions: " uri $ \file -> - let enteredQual = if T.null qualifier then "" else qualifier <> "." - fullPrefix = enteredQual <> ident - in withCachedModuleAndData file (IdeResultOk []) $ \_ CC { allModNamesAsNS, unqualCompls, qualCompls } -> - let - filtModNameCompls = map mkModCompl - $ mapMaybe (T.stripPrefix enteredQual) - $ Fuzzy.simpleFilter fullPrefix allModNamesAsNS - - filtCompls = Fuzzy.filterBy label ident compls - where - compls = if T.null qualifier - then unqualCompls - else Map.findWithDefault [] qualifier qualCompls +getCompletions :: Uri -> (T.Text, T.Text) -> IdeM (IdeResponse [J.CompletionItem]) +getCompletions uri (qualifier, ident) = pluginGetFileResponse "getCompletions: " uri $ \file -> + let handlers = + [ GM.GHandler $ \(ex :: SomeException) -> + return $ IdeResponseFail $ IdeError PluginError + (T.pack $ "getCompletions" <> ": " <> (show ex)) + Null + ] + in flip GM.gcatches handlers $ do + -- debugm $ "got prefix" ++ show (qualifier, ident) + let enteredQual = if T.null qualifier then "" else qualifier <> "." + fullPrefix = enteredQual <> ident + withCachedModuleAndData file $ \_ CC { allModNamesAsNS, unqualCompls, qualCompls } -> + let + filtModNameCompls = map mkModCompl + $ mapMaybe (T.stripPrefix enteredQual) + $ Fuzzy.simpleFilter fullPrefix allModNamesAsNS + + filtCompls = Fuzzy.filterBy label ident compls + where + compls = if T.null qualifier + then unqualCompls + else Map.findWithDefault [] qualifier qualCompls - in return $ IdeResultOk $ filtModNameCompls ++ map mkCompl filtCompls + in return $ IdeResponseOk $ filtModNameCompls ++ map mkCompl filtCompls -- --------------------------------------------------------------------- @@ -281,9 +288,9 @@ getTypeForName n = do -- --------------------------------------------------------------------- -getSymbolsAtPoint :: Uri -> Position -> IdeM (IdeResult [(Range, Name)]) -getSymbolsAtPoint uri pos = pluginGetFile "getSymbolsAtPoint: " uri $ \file -> - withCachedModule file (IdeResultOk mempty) $ return . IdeResultOk . getSymbolsAtPointPure pos +getSymbolsAtPoint :: Uri -> Position -> IdeM (IdeResponse [(Range, Name)]) +getSymbolsAtPoint uri pos = pluginGetFileResponse "getSymbolsAtPoint: " uri $ \file -> + withCachedModule file $ return . IdeResponseOk . getSymbolsAtPointPure pos getSymbolsAtPointPure :: Position -> CachedModule -> [(Range,Name)] getSymbolsAtPointPure pos cm = maybe [] (`getArtifactsAtPos` locMap cm) $ newPosToOld cm pos @@ -301,17 +308,17 @@ symbolFromTypecheckedModule lm pos = -- | Find the references in the given doc, provided it has been -- loaded. If not, return the empty list. -getReferencesInDoc :: Uri -> Position -> IdeM (IdeResult [J.DocumentHighlight]) +getReferencesInDoc :: Uri -> Position -> IdeM (IdeResponse [J.DocumentHighlight]) getReferencesInDoc uri pos = - pluginGetFile "getReferencesInDoc: " uri $ \file -> - withCachedModuleAndData file (IdeResultOk []) $ + pluginGetFileResponse "getReferencesInDoc: " uri $ \file -> + withCachedModuleAndDataDefault file (Just (IdeResponseOk [])) $ \cm NMD{inverseNameMap} -> do let lm = locMap cm pm = tm_parsed_module $ tcMod cm cfile = ml_hs_file $ ms_location $ pm_mod_summary pm mpos = newPosToOld cm pos case mpos of - Nothing -> return $ IdeResultOk [] + Nothing -> return $ IdeResponseOk [] Just pos' -> return $ fmap concat $ forM (getArtifactsAtPos pos' lm) $ \(_,name) -> do let usages = fromMaybe [] $ Map.lookup name inverseNameMap @@ -355,9 +362,9 @@ getModule df n = do -- --------------------------------------------------------------------- -- | Return the definition -findDef :: Uri -> Position -> IdeM (IdeResult [Location]) -findDef uri pos = pluginGetFile "findDef: " uri $ \file -> - withCachedModule file (IdeResultOk []) (\cm -> do +findDef :: Uri -> Position -> IdeM (IdeResponse [Location]) +findDef uri pos = pluginGetFileResponse "findDef: " uri $ \file -> + withCachedModuleDefault file (Just (IdeResponseOk [])) (\cm -> do let rfm = revMap cm lm = locMap cm mm = moduleMap cm @@ -366,33 +373,33 @@ findDef uri pos = pluginGetFile "findDef: " uri $ \file -> case (\x -> Just $ getArtifactsAtPos x mm) =<< oldPos of Just ((_,mn):_) -> gotoModule rfm mn _ -> case symbolFromTypecheckedModule lm =<< oldPos of - Nothing -> return $ IdeResultOk [] + Nothing -> return $ IdeResponseOk [] Just (_, n) -> case nameSrcSpan n of - UnhelpfulSpan _ -> return $ IdeResultOk [] + UnhelpfulSpan _ -> return $ IdeResponseOk [] realSpan -> do res <- srcSpan2Loc rfm realSpan case res of Right l@(J.Location luri range) -> case uriToFilePath luri of - Nothing -> return $ IdeResultOk [l] + Nothing -> return $ IdeResponseOk [l] Just fp -> do mcm' <- getCachedModule fp case mcm' of ModuleCached cm' _ -> case oldRangeToNew cm' range of - Just r -> return $ IdeResultOk [J.Location luri r] - Nothing -> return $ IdeResultOk [l] - _ -> return $ IdeResultOk [l] + Just r -> return $ IdeResponseOk [J.Location luri r] + Nothing -> return $ IdeResponseOk [l] + _ -> return $ IdeResponseOk [l] Left x -> do debugm "findDef: name srcspan not found/valid" - pure (IdeResultFail + pure (IdeResponseFail (IdeError PluginError ("hare:findDef" <> ": \"" <> x <> "\"") Null))) where - gotoModule :: (FilePath -> FilePath) -> ModuleName -> IdeM (IdeResult [Location]) + gotoModule :: (FilePath -> FilePath) -> ModuleName -> IdeM (IdeResponse [Location]) gotoModule rfm mn = do - + hscEnvRef <- ghcSession <$> readMTS mHscEnv <- liftIO $ traverse readIORef hscEnvRef @@ -408,8 +415,8 @@ findDef uri pos = pluginGetFile "findDef: " uri $ \file -> let r = Range (Position 0 0) (Position 0 0) loc = Location (filePathToUri fp) r - return (IdeResultOk [loc]) - _ -> return (IdeResultOk []) - Nothing -> return $ IdeResultFail + return (IdeResponseOk [loc]) + _ -> return (IdeResponseOk []) + Nothing -> return $ IdeResponseFail (IdeError PluginError "Couldn't get hscEnv when finding import" Null) diff --git a/src/Haskell/Ide/Engine/Plugin/HsImport.hs b/src/Haskell/Ide/Engine/Plugin/HsImport.hs index fc3353b5a..434ef8daf 100644 --- a/src/Haskell/Ide/Engine/Plugin/HsImport.hs +++ b/src/Haskell/Ide/Engine/Plugin/HsImport.hs @@ -83,8 +83,8 @@ codeActionProvider plId docId _ _ context = do let relaxedTerms = map (bimap id (head . T.words)) terms relaxedRes <- mapM (bimapM return Hoogle.searchModules) relaxedTerms relaxedActions <- catMaybes <$> mapM (uncurry mkImportAction) (concatTerms relaxedRes) - return $ IdeResultOk relaxedActions - else return $ IdeResultOk actions + return $ IdeResponseOk relaxedActions + else return $ IdeResponseOk actions where concatTerms = concatMap (\(d, ts) -> map (d,) ts) diff --git a/src/Haskell/Ide/Engine/Plugin/Liquid.hs b/src/Haskell/Ide/Engine/Plugin/Liquid.hs index 1546afea8..238a3bfa1 100644 --- a/src/Haskell/Ide/Engine/Plugin/Liquid.hs +++ b/src/Haskell/Ide/Engine/Plugin/Liquid.hs @@ -184,12 +184,12 @@ liquidFileFor uri ext = hoverProvider :: HoverProvider hoverProvider uri pos = - pluginGetFile "Liquid.hoverProvider: " uri $ \file -> - withCachedModuleAndData file (IdeResultOk []) $ + pluginGetFileResponse "Liquid.hoverProvider: " uri $ \file -> + withCachedModuleAndDataDefault file (Just (IdeResponseOk [])) $ \cm () -> do merrs <- liftIO $ readVimAnnot uri case merrs of - Nothing -> return (IdeResultOk []) + Nothing -> return $ IdeResponseResult (IdeResultOk []) Just lerrs -> do let perrs = map (\le@(LE s e _) -> (lpToPos s,lpToPos e,le)) lerrs ls = getThingsAtPos cm pos perrs @@ -197,7 +197,7 @@ hoverProvider uri pos = let msgs = T.splitOn "\\n" msg msg' = J.CodeString (J.LanguageString "haskell" (T.unlines msgs)) return $ J.Hover (J.List [msg']) (Just r) - return (IdeResultOk hs) + return $ IdeResponseResult (IdeResultOk hs) -- --------------------------------------------------------------------- diff --git a/src/Haskell/Ide/Engine/Plugin/Package.hs b/src/Haskell/Ide/Engine/Plugin/Package.hs index eecda281c..499bf7aaf 100644 --- a/src/Haskell/Ide/Engine/Plugin/Package.hs +++ b/src/Haskell/Ide/Engine/Plugin/Package.hs @@ -239,7 +239,7 @@ codeActionProvider plId docId mRootDir _ context = do res <- mapM (bimapM return Hoogle.searchPackages) pkgs actions <- catMaybes <$> mapM (uncurry mkAddPackageAction) (concatPkgs res) - return (IdeResultOk actions) + return $ IdeResponseOk actions where concatPkgs = concatMap (\(d, ts) -> map (d,) ts) diff --git a/src/Haskell/Ide/Engine/Transport/LspStdio.hs b/src/Haskell/Ide/Engine/Transport/LspStdio.hs index 4faec86c1..2fddc8b7c 100644 --- a/src/Haskell/Ide/Engine/Transport/LspStdio.hs +++ b/src/Haskell/Ide/Engine/Transport/LspStdio.hs @@ -488,13 +488,13 @@ reactor inp = do hreq :: PluginRequest R hreq = IReq tn (req ^. J.id) callback $ - pluginGetFile "ReqHover:" doc $ \fp -> do + pluginGetFileResponse "ReqHover:" doc $ \fp -> do cached <- isCached fp -- Hover requests need to be instant so don't wait -- for cached module to be loaded if cached then sequence <$> mapM (\hp -> hp doc pos) hps - else return (IdeResultOk []) + else return (IdeResponseOk []) makeRequest hreq liftIO $ U.logs "reactor:HoverRequest done" @@ -610,7 +610,7 @@ reactor inp = do rspMsg = Core.makeResponseMessage req $ origCompl & J.documentation .~ docs reactorSend $ RspCompletionItemResolve rspMsg - hreq = IReq tn (req ^. J.id) callback $ runIdeResultT $ case mquery of + hreq = IReq tn (req ^. J.id) callback $ runIdeResponseT $ case mquery of Nothing -> return Nothing Just query -> do result <- lift $ Hoogle.infoCmd' query diff --git a/src/Haskell/Ide/Engine/Types.hs b/src/Haskell/Ide/Engine/Types.hs index 2d2e59342..116243e60 100644 --- a/src/Haskell/Ide/Engine/Types.hs +++ b/src/Haskell/Ide/Engine/Types.hs @@ -26,7 +26,7 @@ pattern GReq :: TrackingNumber -> PluginRequest m pattern GReq a b c d e f = Right (GhcRequest a b c d e f) -pattern IReq :: TrackingNumber -> J.LspId -> RequestCallback m a -> IdeM (IdeResult a) -> Either (IdeRequest m) b +pattern IReq :: TrackingNumber -> J.LspId -> RequestCallback m a -> IdeM (IdeResponse a) -> Either (IdeRequest m) b pattern IReq a b c d = Left (IdeRequest a b c d) type PluginRequest m = Either (IdeRequest m) (GhcRequest m) @@ -44,7 +44,7 @@ data IdeRequest m = forall a. IdeRequest { pureMsgNum :: TrackingNumber -- ^ Exists to facilitate logging/tracing , pureReqId :: J.LspId , pureReqCallback :: RequestCallback m a - , pureReq :: IdeM (IdeResult a) + , pureReq :: IdeM (IdeResponse a) } -- --------------------------------------------------------------------- diff --git a/test/dispatcher/Main.hs b/test/dispatcher/Main.hs index 0509ae4a1..cf0de4f71 100644 --- a/test/dispatcher/Main.hs +++ b/test/dispatcher/Main.hs @@ -116,7 +116,7 @@ dispatchGhcRequest tn ctx n cin lc plugin com arg = do dispatchIdeRequest :: (Typeable a, ToJSON a) => TrackingNumber -> String -> TChan (PluginRequest IO) - -> TChan LogVal -> LspId -> IdeM (IdeResult a) -> IO () + -> TChan LogVal -> LspId -> IdeM (IdeResponse a) -> IO () dispatchIdeRequest tn ctx cin lc lid f = do let logger :: (Typeable a, ToJSON a) => RequestCallback IO a @@ -180,11 +180,11 @@ funcSpec = describe "functional dispatch" $ do let -- Model a hover request hoverReq tn idVal doc = dispatchIdeRequest tn ("IReq " ++ show idVal) cin logChan idVal $ do - pluginGetFile "hoverReq" doc $ \fp -> do + pluginGetFileResponse "hoverReq" doc $ \fp -> do cached <- isCached fp if cached - then return (IdeResultOk Cached) - else return (IdeResultOk NotCached) + then return (IdeResponseOk Cached) + else return (IdeResponseOk NotCached) unpackRes (r,Right md) = (r, fromDynJSON md) unpackRes r = error $ "unpackRes:" ++ show r @@ -314,14 +314,14 @@ funcSpec = describe "functional dispatch" $ do Nothing )) - it "instantly responds to failed modules with no cache with the default" $ do + it "instantly responds to failed modules with no cache" $ do dispatchIdeRequest 7 "req7" cin logChan (IdInt 7) $ symbolProvider testFailUri dispatchGhcRequest 8 "req8" 8 cin logChan "ghcmod" "check" (toJSON testFailUri) - hr7 <- atomically $ readTChan logChan - unpackRes hr7 `shouldBe` ("req7", Just ([] :: [DocumentSymbol])) + (_, Left symbolError) <- atomically $ readTChan logChan + symbolError `shouldBe` (IdInt 7, Language.Haskell.LSP.Types.InternalError, "") ("req8", Right diags) <- atomically $ readTChan logChan show diags `shouldBe` "((Map Uri (Set Diagnostic)),[Text])" diff --git a/test/functional/DeferredSpec.hs b/test/functional/DeferredSpec.hs index 4d76afe4e..c9c358fc8 100644 --- a/test/functional/DeferredSpec.hs +++ b/test/functional/DeferredSpec.hs @@ -12,7 +12,7 @@ import qualified Data.HashMap.Strict as H import Data.Maybe import Language.Haskell.LSP.Test import Language.Haskell.LSP.Types hiding (message) -import qualified Language.Haskell.LSP.Types as LSP (id) +import qualified Language.Haskell.LSP.Types as LSP (error, id) import Test.Hspec import System.Directory import System.FilePath @@ -95,7 +95,7 @@ spec = do doc <- openDoc "FuncTestFail.hs" "haskell" symbols <- request TextDocumentDocumentSymbol (DocumentSymbolParams doc) :: Session DocumentSymbolsResponse - liftIO $ symbols ^. result `shouldBe` Just (DSDocumentSymbols mempty) + liftIO $ symbols ^. LSP.error `shouldNotBe` Nothing it "returns hints as diagnostics" $ runSession hieCommand fullCaps "test/testdata" $ do _ <- openDoc "FuncTest.hs" "haskell" @@ -162,5 +162,4 @@ spec = do diagsRsp2 <- skipManyTill anyNotification message :: Session PublishDiagnosticsNotification let (List diags2) = diagsRsp2 ^. params . diagnostics - liftIO $ show diags2 `shouldBe` "[]" diff --git a/test/unit/HaRePluginSpec.hs b/test/unit/HaRePluginSpec.hs index 79ccf71d3..6ba6fc908 100644 --- a/test/unit/HaRePluginSpec.hs +++ b/test/unit/HaRePluginSpec.hs @@ -175,29 +175,29 @@ hareSpec = do lreq = setTypecheckedModule u req = liftToGhc $ findDef u (toPos (7,8)) r <- dispatchRequestPGoto $ lreq >> req - r `shouldBe` IdeResultOk [Location (filePathToUri $ cwd "test/testdata/gototest/src/Lib.hs") + r `shouldBe` IdeResponseOk [Location (filePathToUri $ cwd "test/testdata/gototest/src/Lib.hs") (Range (toPos (6,1)) (toPos (6,9)))] let req2 = liftToGhc $ findDef u (toPos (7,20)) r2 <- dispatchRequestPGoto $ lreq >> req2 - r2 `shouldBe` IdeResultOk [Location (filePathToUri $ cwd "test/testdata/gototest/src/Lib2.hs") + r2 `shouldBe` IdeResponseOk [Location (filePathToUri $ cwd "test/testdata/gototest/src/Lib2.hs") (Range (toPos (5,1)) (toPos (5,2)))] it "finds definition in the same component" $ do let u = filePathToUri $ cwd "test/testdata/gototest/src/Lib2.hs" lreq = setTypecheckedModule u req = liftToGhc $ findDef u (toPos (6,5)) r <- dispatchRequestPGoto $ lreq >> req - r `shouldBe` IdeResultOk [Location (filePathToUri $ cwd "test/testdata/gototest/src/Lib.hs") + r `shouldBe` IdeResponseOk [Location (filePathToUri $ cwd "test/testdata/gototest/src/Lib.hs") (Range (toPos (6,1)) (toPos (6,9)))] it "finds local definitions" $ do let u = filePathToUri $ cwd "test/testdata/gototest/src/Lib2.hs" lreq = setTypecheckedModule u req = liftToGhc $ findDef u (toPos (7,11)) r <- dispatchRequestPGoto $ lreq >> req - r `shouldBe` IdeResultOk [Location (filePathToUri $ cwd "test/testdata/gototest/src/Lib2.hs") + r `shouldBe` IdeResponseOk [Location (filePathToUri $ cwd "test/testdata/gototest/src/Lib2.hs") (Range (toPos (10,9)) (toPos (10,10)))] let req2 = liftToGhc $ findDef u (toPos (10,13)) r2 <- dispatchRequestPGoto $ lreq >> req2 - r2 `shouldBe` IdeResultOk [Location (filePathToUri $ cwd "test/testdata/gototest/src/Lib2.hs") + r2 `shouldBe` IdeResponseOk [Location (filePathToUri $ cwd "test/testdata/gototest/src/Lib2.hs") (Range (toPos (9,9)) (toPos (9,10)))]