Skip to content

Typed rule inputs #4449

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 14 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions ghcide/ghcide.cabal
Original file line number Diff line number Diff line change
@@ -135,6 +135,7 @@ library
Development.IDE.Core.FileStore
Development.IDE.Core.FileUtils
Development.IDE.Core.IdeConfiguration
Development.IDE.Core.InputPath
Development.IDE.Core.OfInterest
Development.IDE.Core.PluginUtils
Development.IDE.Core.PositionMapping
6 changes: 4 additions & 2 deletions ghcide/session-loader/Development/IDE/Session.hs
Original file line number Diff line number Diff line change
@@ -125,6 +125,7 @@ import GHC.Driver.Errors.Types
import GHC.Types.Error (errMsgDiagnostic,
singleMessage)
import GHC.Unit.State
import Development.IDE.Core.InputPath (generalizeProjectInput, classifyProjectHaskellInputs)

data Log
= LogSettingInitialDynFlags
@@ -592,8 +593,9 @@ loadSessionWithOptions recorder SessionLoadingOptions{..} rootDir que = do
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)
let cfps'' = classifyProjectHaskellInputs cfps'
mmt <- uses GetModificationTime $ generalizeProjectInput <$> cfps''
let cs_exist = catMaybes (zipWith (<$) cfps'' mmt)
modIfaces <- uses GetModIface cs_exist
-- update exports map
shakeExtras <- getShakeExtras
33 changes: 23 additions & 10 deletions ghcide/src/Development/IDE/Core/Actions.hs
Original file line number Diff line number Diff line change
@@ -32,6 +32,7 @@ import Language.LSP.Protocol.Types (DocumentHighlight (..),
SymbolInformation (..),
normalizedFilePathToUri,
uriToNormalizedFilePath)
import Development.IDE.Core.InputPath (classifyProjectHaskellInputs, InputPath (InputPath))


-- | Eventually this will lookup/generate URIs for files in dependencies, but not in the
@@ -59,12 +60,22 @@ getAtPoint file pos = runMaybeT $ do
ide <- ask
opts <- liftIO $ getIdeOptionsIO ide

(hf, mapping) <- useWithStaleFastMT GetHieAst file
env <- hscEnv . fst <$> useWithStaleFastMT GhcSession file
dkMap <- lift $ maybe (DKMap mempty mempty) fst <$> runMaybeT (useWithStaleFastMT GetDocMap file)
(hf, mapping) <- useWithStaleFastMT GetHieAst $ InputPath file
-- The HscEnv and DKMap are not strictly necessary for hover
-- to work, so we only calculate them for project files, not
-- for dependency files. They provide information that will
-- not be displayed in dependency files. See the atPoint
-- function in ghcide/src/Development/IDE/Spans/AtPoint.hs
-- for the specifics of how they are used.
(mEnv, mDkMap) <- case classifyProjectHaskellInputs [file] of
[] -> pure (Nothing, Nothing)
projectInput:_ -> do
env <- hscEnv . fst <$> useWithStaleFastMT GhcSession projectInput
dkMap <- lift $ maybe (DKMap mempty mempty) fst <$> runMaybeT (useWithStaleFastMT GetDocMap projectInput)
pure (Just env, Just dkMap)

!pos' <- MaybeT (return $ fromCurrentPosition mapping pos)
MaybeT $ liftIO $ fmap (first (toCurrentRange mapping =<<)) <$> AtPoint.atPoint opts hf dkMap env pos'
MaybeT $ liftIO $ fmap (first (toCurrentRange mapping =<<)) <$> AtPoint.atPoint opts hf mDkMap mEnv pos'

-- | Converts locations in the source code to their current positions,
-- taking into account changes that may have occurred due to edits.
@@ -87,7 +98,7 @@ toCurrentLocation mapping file (Location uri range) =
else do
otherLocationMapping <- fmap (fmap snd) $ runMaybeT $ do
otherLocationFile <- MaybeT $ pure $ uriToNormalizedFilePath nUri
useWithStaleFastMT GetHieAst otherLocationFile
useWithStaleFastMT GetHieAst $ InputPath otherLocationFile
pure $ Location uri <$> (flip toCurrentRange range =<< otherLocationMapping)
where
nUri :: NormalizedUri
@@ -98,8 +109,10 @@ getDefinition :: NormalizedFilePath -> Position -> IdeAction (Maybe [(Location,
getDefinition file pos = runMaybeT $ do
ide@ShakeExtras{ withHieDb, hiedbWriter } <- ask
opts <- liftIO $ getIdeOptionsIO ide
(HAR _ hf _ _ _, mapping) <- useWithStaleFastMT GetHieAst file
(ImportMap imports, _) <- useWithStaleFastMT GetImportMap file
(HAR _ hf _ _ _, mapping) <- useWithStaleFastMT GetHieAst $ InputPath file
(ImportMap imports, _) <- case classifyProjectHaskellInputs [file] of
[] -> pure (ImportMap mempty, PositionMapping idDelta)
(projectInput: _) -> useWithStaleFastMT GetImportMap projectInput
!pos' <- MaybeT (pure $ fromCurrentPosition mapping pos)
locationsWithIdentifier <- AtPoint.gotoDefinition withHieDb (lookupMod hiedbWriter) opts imports hf pos'
mapMaybeM (\(location, identifier) -> do
@@ -112,7 +125,7 @@ getTypeDefinition :: NormalizedFilePath -> Position -> IdeAction (Maybe [(Locati
getTypeDefinition file pos = runMaybeT $ do
ide@ShakeExtras{ withHieDb, hiedbWriter } <- ask
opts <- liftIO $ getIdeOptionsIO ide
(hf, mapping) <- useWithStaleFastMT GetHieAst file
(hf, mapping) <- useWithStaleFastMT GetHieAst $ InputPath file
!pos' <- MaybeT (return $ fromCurrentPosition mapping pos)
locationsWithIdentifier <- AtPoint.gotoTypeDefinition withHieDb (lookupMod hiedbWriter) opts hf pos'
mapMaybeM (\(location, identifier) -> do
@@ -122,7 +135,7 @@ getTypeDefinition file pos = runMaybeT $ do

highlightAtPoint :: NormalizedFilePath -> Position -> IdeAction (Maybe [DocumentHighlight])
highlightAtPoint file pos = runMaybeT $ do
(HAR _ hf rf _ _,mapping) <- useWithStaleFastMT GetHieAst file
(HAR _ hf rf _ _,mapping) <- useWithStaleFastMT GetHieAst $ InputPath file
!pos' <- MaybeT (return $ fromCurrentPosition mapping pos)
let toCurrentHighlight (DocumentHighlight range t) = flip DocumentHighlight t <$> toCurrentRange mapping range
mapMaybe toCurrentHighlight <$>AtPoint.documentHighlight hf rf pos'
@@ -132,7 +145,7 @@ refsAtPoint :: NormalizedFilePath -> Position -> Action [Location]
refsAtPoint file pos = do
ShakeExtras{withHieDb} <- getShakeExtras
fs <- HM.keys <$> getFilesOfInterestUntracked
asts <- HM.fromList . mapMaybe sequence . zip fs <$> usesWithStale GetHieAst fs
asts <- HM.fromList . mapMaybe sequence . zip fs <$> usesWithStale GetHieAst (map InputPath fs)
AtPoint.referencesAtPoint withHieDb file pos (AtPoint.FOIReferences asts)

workspaceSymbols :: T.Text -> IdeAction (Maybe [SymbolInformation])
16 changes: 12 additions & 4 deletions ghcide/src/Development/IDE/Core/FileExists.hs
Original file line number Diff line number Diff line change
@@ -39,6 +39,8 @@ import Language.LSP.Server hiding (getVirtualFile)
import qualified StmContainers.Map as STM
import qualified System.Directory as Dir
import qualified System.FilePath.Glob as Glob
import Development.IDE.Core.InputPath (InputPath (InputPath))
import Development.IDE.Graph.Internal.Rules (InputClass(AllHaskellFiles))

{- Note [File existence cache and LSP file watchers]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -133,7 +135,7 @@ fromChange FileChangeType_Changed = Nothing
-------------------------------------------------------------------------------------

-- | Returns True if the file exists
getFileExists :: NormalizedFilePath -> Action Bool
getFileExists :: InputPath AllHaskellFiles -> Action Bool
getFileExists fp = use_ GetFileExists fp

{- Note [Which files should we watch?]
@@ -192,12 +194,15 @@ fileExistsRules recorder lspEnv = do
then fileExistsRulesFast recorder isWatched
else fileExistsRulesSlow recorder

fileStoreRules (cmapWithPrio LogFileStore recorder) isWatched
fileStoreRules (cmapWithPrio LogFileStore recorder) (\(InputPath f) -> isWatched f)

-- Requires an lsp client that provides WatchedFiles notifications, but assumes that this has already been checked.
fileExistsRulesFast :: Recorder (WithPriority Log) -> (NormalizedFilePath -> Action Bool) -> Rules ()
fileExistsRulesFast recorder isWatched =
defineEarlyCutoff (cmapWithPrio LogShake recorder) $ RuleNoDiagnostics $ \GetFileExists file -> do
defineEarlyCutoff (cmapWithPrio LogShake recorder) $ RuleNoDiagnostics runGetFileExists
where
runGetFileExists :: GetFileExists -> InputPath AllHaskellFiles -> Action (Maybe BS.ByteString, Maybe Bool)
runGetFileExists GetFileExists (InputPath file) = do
isWF <- isWatched file
if isWF
then fileExistsFast file
@@ -238,7 +243,10 @@ summarizeExists x = Just $ if x then BS.singleton 1 else BS.empty

fileExistsRulesSlow :: Recorder (WithPriority Log) -> Rules ()
fileExistsRulesSlow recorder =
defineEarlyCutoff (cmapWithPrio LogShake recorder) $ RuleNoDiagnostics $ \GetFileExists file -> fileExistsSlow file
defineEarlyCutoff (cmapWithPrio LogShake recorder) $ RuleNoDiagnostics runGetFileExists
where
runGetFileExists :: GetFileExists -> InputPath AllHaskellFiles -> Action (Maybe BS.ByteString, Maybe Bool)
runGetFileExists GetFileExists (InputPath file) = fileExistsSlow file

fileExistsSlow :: NormalizedFilePath -> Action (Maybe BS.ByteString, Maybe Bool)
fileExistsSlow file = do
44 changes: 27 additions & 17 deletions ghcide/src/Development/IDE/Core/FileStore.hs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
-- Copyright (c) 2019 The DAML Authors. All rights reserved.
-- SPDX-License-Identifier: Apache-2.0
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE TypeFamilies #-}

module Development.IDE.Core.FileStore(
@@ -69,6 +70,8 @@ import Language.LSP.VFS
import System.FilePath
import System.IO.Error
import System.IO.Unsafe
import Development.IDE.Core.InputPath (InputPath (unInputPath), classifyProjectHaskellInputs)
import Development.IDE.Graph.Internal.Rules (InputClass(AllHaskellFiles))


data Log
@@ -88,31 +91,34 @@ instance Pretty Log where
<+> pretty (fmap (fmap show) reverseDepPaths)
LogShake msg -> pretty msg

addWatchedFileRule :: Recorder (WithPriority Log) -> (NormalizedFilePath -> Action Bool) -> Rules ()
addWatchedFileRule :: Recorder (WithPriority Log) -> (InputPath AllHaskellFiles -> Action Bool) -> Rules ()
addWatchedFileRule recorder isWatched = defineNoDiagnostics (cmapWithPrio LogShake recorder) $ \AddWatchedFile f -> do
isAlreadyWatched <- isWatched f
isWp <- isWorkspaceFile f
isWp <- isWorkspaceFile $ unInputPath f
if isAlreadyWatched then pure (Just True) else
if not isWp then pure (Just False) else do
ShakeExtras{lspEnv} <- getShakeExtras
case lspEnv of
Just env -> fmap Just $ liftIO $ LSP.runLspT env $
registerFileWatches [fromNormalizedFilePath f]
registerFileWatches [fromNormalizedFilePath (unInputPath f)]
Nothing -> pure $ Just False


getModificationTimeRule :: Recorder (WithPriority Log) -> Rules ()
getModificationTimeRule recorder = defineEarlyCutoff (cmapWithPrio LogShake recorder) $ Rule $ \(GetModificationTime_ missingFileDiags) file ->
getModificationTimeImpl missingFileDiags file
getModificationTimeRule recorder = defineEarlyCutoff (cmapWithPrio LogShake recorder) $ Rule runGetModificationTimeImpl
where
runGetModificationTimeImpl :: GetModificationTime -> InputPath AllHaskellFiles -> Action (Maybe BS.ByteString, ([FileDiagnostic], Maybe FileVersion))
runGetModificationTimeImpl (GetModificationTime_ missingFileDiags) input =
getModificationTimeImpl missingFileDiags input

getModificationTimeImpl
:: Bool
-> NormalizedFilePath
-> InputPath AllHaskellFiles
-> Action (Maybe BS.ByteString, ([FileDiagnostic], Maybe FileVersion))
getModificationTimeImpl missingFileDiags file = do
let file' = fromNormalizedFilePath file
let file' = fromNormalizedFilePath $ unInputPath file
let wrap time = (Just $ LBS.toStrict $ B.encode $ toRational time, ([], Just $ ModificationTime time))
mbVf <- getVirtualFile file
mbVf <- getVirtualFile $ unInputPath file
case mbVf of
Just (virtualFileVersion -> ver) -> do
alwaysRerun
@@ -124,7 +130,7 @@ getModificationTimeImpl missingFileDiags file = do
-- but also need a dependency on IsFileOfInterest to reinstall
-- alwaysRerun when the file becomes VFS
void (use_ IsFileOfInterest file)
else if isInterface file
else if isInterface (unInputPath file)
then -- interface files are tracked specially using the closed world assumption
pure ()
else -- in all other cases we will need to freshly check the file system
@@ -134,7 +140,7 @@ getModificationTimeImpl missingFileDiags file = do
`catch` \(e :: IOException) -> do
let err | isDoesNotExistError e = "File does not exist: " ++ file'
| otherwise = "IO error while reading " ++ file' ++ ", " ++ displayException e
diag = ideErrorText file (T.pack err)
diag = ideErrorText (unInputPath file) (T.pack err)
if isDoesNotExistError e && not missingFileDiags
then return (Nothing, ([], Nothing))
else return (Nothing, ([diag], Nothing))
@@ -171,22 +177,25 @@ modificationTime VFSVersion{} = Nothing
modificationTime (ModificationTime posix) = Just $ posixSecondsToUTCTime posix

getFileContentsRule :: Recorder (WithPriority Log) -> Rules ()
getFileContentsRule recorder = define (cmapWithPrio LogShake recorder) $ \GetFileContents file -> getFileContentsImpl file
getFileContentsRule recorder = define (cmapWithPrio LogShake recorder) runGetFileContentsImpl
where
runGetFileContentsImpl :: GetFileContents -> InputPath AllHaskellFiles -> Action ([FileDiagnostic], Maybe (FileVersion, Maybe T.Text))
runGetFileContentsImpl GetFileContents input = getFileContentsImpl input

getFileContentsImpl
:: NormalizedFilePath
:: InputPath AllHaskellFiles
-> Action ([FileDiagnostic], Maybe (FileVersion, Maybe T.Text))
getFileContentsImpl file = do
-- need to depend on modification time to introduce a dependency with Cutoff
time <- use_ GetModificationTime file
res <- do
mbVirtual <- getVirtualFile file
mbVirtual <- getVirtualFile $ unInputPath file
pure $ virtualFileText <$> mbVirtual
pure ([], Just (time, res))

-- | Returns the modification time and the contents.
-- For VFS paths, the modification time is the current time.
getFileContents :: NormalizedFilePath -> Action (UTCTime, Maybe T.Text)
getFileContents :: InputPath AllHaskellFiles -> Action (UTCTime, Maybe T.Text)
getFileContents f = do
(fv, txt) <- use_ GetFileContents f
modTime <- case modificationTime fv of
@@ -196,11 +205,11 @@ getFileContents f = do
liftIO $ case foi of
IsFOI Modified{} -> getCurrentTime
_ -> do
posix <- getModTime $ fromNormalizedFilePath f
posix <- getModTime $ fromNormalizedFilePath $ unInputPath f
pure $ posixSecondsToUTCTime posix
return (modTime, txt)

fileStoreRules :: Recorder (WithPriority Log) -> (NormalizedFilePath -> Action Bool) -> Rules ()
fileStoreRules :: Recorder (WithPriority Log) -> (InputPath AllHaskellFiles -> Action Bool) -> Rules ()
fileStoreRules recorder isWatched = do
getModificationTimeRule recorder
getFileContentsRule recorder
@@ -239,7 +248,8 @@ typecheckParentsAction recorder nfp = do
Nothing -> logWith recorder Info $ LogCouldNotIdentifyReverseDeps nfp
Just rs -> do
logWith recorder Info $ LogTypeCheckingReverseDeps nfp revs
void $ uses GetModIface rs
let classifiedInputs = classifyProjectHaskellInputs rs
void $ uses GetModIface classifiedInputs

-- | Note that some keys have been modified and restart the session
-- Only valid if the virtual file system was initialised by LSP, as that
46 changes: 46 additions & 0 deletions ghcide/src/Development/IDE/Core/InputPath.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{-# LANGUAGE DerivingStrategies #-}

module Development.IDE.Core.InputPath where

import Control.DeepSeq
import Data.Hashable
import Data.List (isInfixOf)
import Data.Typeable
import Development.IDE.Graph.Internal.Rules (InputClass(..))
import Language.LSP.Protocol.Types (NormalizedFilePath, fromNormalizedFilePath)
import System.FilePath (splitDirectories)

newtype InputPath (i :: InputClass) =
InputPath { unInputPath :: NormalizedFilePath }
deriving newtype (Eq, Hashable, NFData, Typeable, Show)

-- All Haskell files are valid, and we assume all
-- files are Haskell files (for now) so there is
-- no need to filter out any FilePaths.
classifyAllHaskellInputs :: [NormalizedFilePath] -> [InputPath AllHaskellFiles]
classifyAllHaskellInputs = map InputPath

-- Dependency files should not be considered
-- ProjectHaskellFiles, so we filter them out
-- before classifying all other files as
-- ProjectHaskellFiles.
classifyProjectHaskellInputs :: [NormalizedFilePath] -> [InputPath ProjectHaskellFiles]
classifyProjectHaskellInputs = foldr classifyInputPath []
where
classifyInputPath :: NormalizedFilePath -> [InputPath ProjectHaskellFiles] -> [InputPath ProjectHaskellFiles]
classifyInputPath nfp projectInputs =
case dependencyDirectory `isInfixOf` rawInput of
-- The input is a dependency, so don't include
-- it in the project inputs.
True -> projectInputs
-- The input is not a depencency, so include it
-- in the project inputs
False -> InputPath nfp : projectInputs
where
dependencyDirectory :: [FilePath]
dependencyDirectory = [".hls", "dependencies"]
rawInput :: [FilePath]
rawInput = splitDirectories (fromNormalizedFilePath nfp)

generalizeProjectInput :: InputPath ProjectHaskellFiles -> InputPath AllHaskellFiles
generalizeProjectInput = InputPath . unInputPath
14 changes: 9 additions & 5 deletions ghcide/src/Development/IDE/Core/OfInterest.hs
Original file line number Diff line number Diff line change
@@ -50,6 +50,7 @@ import Ide.Logger (Pretty (pretty),
logWith)
import qualified Language.LSP.Protocol.Message as LSP
import qualified Language.LSP.Server as LSP
import Development.IDE.Core.InputPath (classifyProjectHaskellInputs, classifyAllHaskellInputs, InputPath (unInputPath))

data Log = LogShake Shake.Log
deriving Show
@@ -67,10 +68,11 @@ ofInterestRules :: Recorder (WithPriority Log) -> Rules ()
ofInterestRules recorder = do
addIdeGlobal . OfInterestVar =<< liftIO (newVar HashMap.empty)
addIdeGlobal . GarbageCollectVar =<< liftIO (newVar False)
defineEarlyCutoff (cmapWithPrio LogShake recorder) $ RuleNoDiagnostics $ \IsFileOfInterest f -> do
defineEarlyCutoff (cmapWithPrio LogShake recorder) $ RuleNoDiagnostics $ \IsFileOfInterest input -> do
alwaysRerun
filesOfInterest <- getFilesOfInterestUntracked
let foi = maybe NotFOI IsFOI $ f `HashMap.lookup` filesOfInterest
let rawFile = unInputPath input
let foi = maybe NotFOI IsFOI $ rawFile `HashMap.lookup` filesOfInterest
fp = summarize foi
res = (Just fp, Just foi)
return res
@@ -134,6 +136,8 @@ scheduleGarbageCollection state = do
kick :: Action ()
kick = do
files <- HashMap.keys <$> getFilesOfInterestUntracked
let classifiedHaskellFiles = classifyAllHaskellInputs files
classifiedProjectFiles = classifyProjectHaskellInputs files
ShakeExtras{exportsMap, ideTesting = IdeTesting testing, lspEnv, progress} <- getShakeExtras
let signal :: KnownSymbol s => Proxy s -> Action ()
signal msg = when testing $ liftIO $
@@ -145,11 +149,11 @@ kick = do
liftIO $ progressUpdate progress ProgressNewStarted

-- Update the exports map
results <- uses GenerateCore files
<* uses GetHieAst files
results <- uses GenerateCore classifiedProjectFiles
<* uses GetHieAst classifiedHaskellFiles
-- needed to have non local completions on the first edit
-- when the first edit breaks the module header
<* uses NonLocalCompletions files
<* uses NonLocalCompletions classifiedProjectFiles
let mguts = catMaybes results
void $ liftIO $ atomically $ modifyTVar' exportsMap (updateExportsMapMg mguts)

22 changes: 11 additions & 11 deletions ghcide/src/Development/IDE/Core/PluginUtils.hs
Original file line number Diff line number Diff line change
@@ -40,11 +40,11 @@ import Development.IDE.Core.Shake (IdeAction, IdeRule,
import qualified Development.IDE.Core.Shake as Shake
import Development.IDE.GHC.Orphans ()
import Development.IDE.Graph hiding (ShakeValue)
import Development.IDE.Types.Location (NormalizedFilePath)
import qualified Development.IDE.Types.Location as Location
import qualified Ide.Logger as Logger
import Ide.Plugin.Error
import qualified Language.LSP.Protocol.Types as LSP
import Development.IDE.Core.InputPath (InputPath)

-- ----------------------------------------------------------------------------
-- Action wrappers
@@ -63,30 +63,30 @@ runActionMT herald ide act =
join $ shakeEnqueue (shakeExtras ide) (mkDelayedAction herald Logger.Debug $ runMaybeT act)

-- |ExceptT version of `use` that throws a PluginRuleFailed upon failure
useE :: IdeRule k v => k -> NormalizedFilePath -> ExceptT PluginError Action v
useE :: IdeRule k i v => k -> InputPath i -> ExceptT PluginError Action v
useE k = maybeToExceptT (PluginRuleFailed (T.pack $ show k)) . useMT k

-- |MaybeT version of `use`
useMT :: IdeRule k v => k -> NormalizedFilePath -> MaybeT Action v
useMT :: IdeRule k i v => k -> InputPath i -> MaybeT Action v
useMT k = MaybeT . Shake.use k

-- |ExceptT version of `uses` that throws a PluginRuleFailed upon failure
usesE :: (Traversable f, IdeRule k v) => k -> f NormalizedFilePath -> ExceptT PluginError Action (f v)
usesE :: (Traversable f, IdeRule k i v) => k -> f (InputPath i) -> ExceptT PluginError Action (f v)
usesE k = maybeToExceptT (PluginRuleFailed (T.pack $ show k)) . usesMT k

-- |MaybeT version of `uses`
usesMT :: (Traversable f, IdeRule k v) => k -> f NormalizedFilePath -> MaybeT Action (f v)
usesMT :: (Traversable f, IdeRule k i v) => k -> f (InputPath i) -> MaybeT Action (f v)
usesMT k xs = MaybeT $ sequence <$> Shake.uses k xs

-- |ExceptT version of `useWithStale` that throws a PluginRuleFailed upon
-- failure
useWithStaleE :: IdeRule k v
=> k -> NormalizedFilePath -> ExceptT PluginError Action (v, PositionMapping)
useWithStaleE :: IdeRule k i v
=> k -> InputPath i -> ExceptT PluginError Action (v, PositionMapping)
useWithStaleE key = maybeToExceptT (PluginRuleFailed (T.pack $ show key)) . useWithStaleMT key

-- |MaybeT version of `useWithStale`
useWithStaleMT :: IdeRule k v
=> k -> NormalizedFilePath -> MaybeT Action (v, PositionMapping)
useWithStaleMT :: IdeRule k i v
=> k -> InputPath i -> MaybeT Action (v, PositionMapping)
useWithStaleMT key file = MaybeT $ runIdentity <$> Shake.usesWithStale key (Identity file)

-- ----------------------------------------------------------------------------
@@ -103,11 +103,11 @@ runIdeActionMT _herald s i = MaybeT $ liftIO $ runReaderT (Shake.runIdeActionT $

-- |ExceptT version of `useWithStaleFast` that throws a PluginRuleFailed upon
-- failure
useWithStaleFastE :: IdeRule k v => k -> NormalizedFilePath -> ExceptT PluginError IdeAction (v, PositionMapping)
useWithStaleFastE :: IdeRule k i v => k -> InputPath i -> ExceptT PluginError IdeAction (v, PositionMapping)
useWithStaleFastE k = maybeToExceptT (PluginRuleFailed (T.pack $ show k)) . useWithStaleFastMT k

-- |MaybeT version of `useWithStaleFast`
useWithStaleFastMT :: IdeRule k v => k -> NormalizedFilePath -> MaybeT IdeAction (v, PositionMapping)
useWithStaleFastMT :: IdeRule k i v => k -> InputPath i -> MaybeT IdeAction (v, PositionMapping)
useWithStaleFastMT k = MaybeT . Shake.useWithStaleFast k

-- ----------------------------------------------------------------------------
29 changes: 29 additions & 0 deletions ghcide/src/Development/IDE/Core/RuleTypes.hs
Original file line number Diff line number Diff line change
@@ -29,6 +29,7 @@ import Development.IDE.GHC.Compat.Util
import Development.IDE.GHC.CoreFile
import Development.IDE.GHC.Util
import Development.IDE.Graph
import Development.IDE.Graph.Internal.Rules (InputClass(..))
import Development.IDE.Import.DependencyInformation
import Development.IDE.Types.HscEnvEq (HscEnvEq)
import Development.IDE.Types.KnownTargets
@@ -65,28 +66,34 @@ encodeLinkableType (Just ObjectLinkable) = "2"

-- | The parse tree for the file using GetFileContents
type instance RuleResult GetParsedModule = ParsedModule
type instance RuleInput GetParsedModule = ProjectHaskellFiles

-- | The parse tree for the file using GetFileContents,
-- all comments included using Opt_KeepRawTokenStream
type instance RuleResult GetParsedModuleWithComments = ParsedModule
type instance RuleInput GetParsedModuleWithComments = ProjectHaskellFiles

type instance RuleResult GetModuleGraph = DependencyInformation
type instance RuleInput GetModuleGraph = NoFile

data GetKnownTargets = GetKnownTargets
deriving (Show, Generic, Eq, Ord)
instance Hashable GetKnownTargets
instance NFData GetKnownTargets
type instance RuleResult GetKnownTargets = KnownTargets
type instance RuleInput GetKnownTargets = NoFile

-- | Convert to Core, requires TypeCheck*
type instance RuleResult GenerateCore = ModGuts
type instance RuleInput GenerateCore = ProjectHaskellFiles

data GenerateCore = GenerateCore
deriving (Eq, Show, Typeable, Generic)
instance Hashable GenerateCore
instance NFData GenerateCore

type instance RuleResult GetLinkable = LinkableResult
type instance RuleInput GetLinkable = ProjectHaskellFiles

data LinkableResult
= LinkableResult
@@ -112,6 +119,7 @@ instance Hashable GetImportMap
instance NFData GetImportMap

type instance RuleResult GetImportMap = ImportMap
type instance RuleInput GetImportMap = ProjectHaskellFiles
newtype ImportMap = ImportMap
{ importMap :: M.Map ModuleName NormalizedFilePath -- ^ Where are the modules imported by this file located?
} deriving stock Show
@@ -232,12 +240,15 @@ instance Show HieAstResult where

-- | The type checked version of this file, requires TypeCheck+
type instance RuleResult TypeCheck = TcModuleResult
type instance RuleInput TypeCheck = ProjectHaskellFiles

-- | The uncompressed HieAST
type instance RuleResult GetHieAst = HieAstResult
type instance RuleInput GetHieAst = AllHaskellFiles

-- | A IntervalMap telling us what is in scope at each point
type instance RuleResult GetBindings = Bindings
type instance RuleInput GetBindings = ProjectHaskellFiles

data DocAndTyThingMap = DKMap {getDocMap :: !DocMap, getTyThingMap :: !TyThingMap}
instance NFData DocAndTyThingMap where
@@ -247,39 +258,50 @@ instance Show DocAndTyThingMap where
show = const "docmap"

type instance RuleResult GetDocMap = DocAndTyThingMap
type instance RuleInput GetDocMap = ProjectHaskellFiles

-- | A GHC session that we reuse.
type instance RuleResult GhcSession = HscEnvEq
type instance RuleInput GhcSession = ProjectHaskellFiles

-- | A GHC session preloaded with all the dependencies
-- This rule is also responsible for calling ReportImportCycles for the direct dependencies
type instance RuleResult GhcSessionDeps = HscEnvEq
type instance RuleInput GhcSessionDeps = ProjectHaskellFiles

-- | Resolve the imports in a module to the file path of a module in the same package
type instance RuleResult GetLocatedImports = [(Located ModuleName, Maybe ArtifactsLocation)]
type instance RuleInput GetLocatedImports = ProjectHaskellFiles

-- | This rule is used to report import cycles. It depends on GetModuleGraph.
-- We cannot report the cycles directly from GetModuleGraph since
-- we can only report diagnostics for the current file.
type instance RuleResult ReportImportCycles = ()
type instance RuleInput ReportImportCycles = ProjectHaskellFiles

-- | Read the module interface file from disk. Throws an error for VFS files.
-- This is an internal rule, use 'GetModIface' instead.
type instance RuleResult GetModIfaceFromDisk = HiFileResult
type instance RuleInput GetModIfaceFromDisk = ProjectHaskellFiles

-- | GetModIfaceFromDisk and index the `.hie` file into the database.
-- This is an internal rule, use 'GetModIface' instead.
type instance RuleResult GetModIfaceFromDiskAndIndex = HiFileResult
type instance RuleInput GetModIfaceFromDiskAndIndex = ProjectHaskellFiles

-- | Get a module interface details, either from an interface file or a typechecked module
type instance RuleResult GetModIface = HiFileResult
type instance RuleInput GetModIface = ProjectHaskellFiles

-- | Get the contents of a file, either dirty (if the buffer is modified) or Nothing to mean use from disk.
type instance RuleResult GetFileContents = (FileVersion, Maybe Text)
type instance RuleInput GetFileContents = AllHaskellFiles

type instance RuleResult GetFileExists = Bool
type instance RuleInput GetFileExists = AllHaskellFiles

type instance RuleResult AddWatchedFile = Bool
type instance RuleInput AddWatchedFile = AllHaskellFiles


-- The Shake key type for getModificationTime queries
@@ -309,6 +331,7 @@ pattern GetModificationTime = GetModificationTime_ {missingFileDiagnostics=True}

-- | Get the modification time of a file.
type instance RuleResult GetModificationTime = FileVersion
type instance RuleInput GetModificationTime = AllHaskellFiles

-- | Either the mtime from disk or an LSP version
-- LSP versions always compare as greater than on disk versions
@@ -351,6 +374,7 @@ instance Hashable IsFileOfInterestResult
instance NFData IsFileOfInterestResult

type instance RuleResult IsFileOfInterest = IsFileOfInterestResult
type instance RuleInput IsFileOfInterest = AllHaskellFiles

data ModSummaryResult = ModSummaryResult
{ msrModSummary :: !ModSummary
@@ -373,9 +397,11 @@ instance NFData ModSummaryResult where
-- | Generate a ModSummary that has enough information to be used to get .hi and .hie files.
-- without needing to parse the entire source
type instance RuleResult GetModSummary = ModSummaryResult
type instance RuleInput GetModSummary = ProjectHaskellFiles

-- | Generate a ModSummary with the timestamps and preprocessed content elided, for more successful early cutoff
type instance RuleResult GetModSummaryWithoutTimestamps = ModSummaryResult
type instance RuleInput GetModSummaryWithoutTimestamps = ProjectHaskellFiles

data GetParsedModule = GetParsedModule
deriving (Eq, Show, Typeable, Generic)
@@ -394,6 +420,7 @@ instance NFData GetLocatedImports

-- | Does this module need to be compiled?
type instance RuleResult NeedsCompilation = Maybe LinkableType
type instance RuleInput NeedsCompilation = ProjectHaskellFiles

data NeedsCompilation = NeedsCompilation
deriving (Eq, Show, Typeable, Generic)
@@ -487,6 +514,7 @@ instance Hashable GetClientSettings
instance NFData GetClientSettings

type instance RuleResult GetClientSettings = Hashed (Maybe Value)
type instance RuleInput GetClientSettings = NoFile

data AddWatchedFile = AddWatchedFile deriving (Eq, Show, Typeable, Generic)
instance Hashable AddWatchedFile
@@ -497,6 +525,7 @@ instance NFData AddWatchedFile
-- thread killed exception issues, so we lift it to a full rule.
-- https://github.com/digital-asset/daml/pull/2808#issuecomment-529639547
type instance RuleResult GhcSessionIO = IdeGhcSession
type instance RuleInput GhcSessionIO = NoFile

data IdeGhcSession = IdeGhcSession
{ loadSessionFun :: FilePath -> IO (IdeResult HscEnvEq, [FilePath])
216 changes: 123 additions & 93 deletions ghcide/src/Development/IDE/Core/Rules.hs

Large diffs are not rendered by default.

237 changes: 125 additions & 112 deletions ghcide/src/Development/IDE/Core/Shake.hs

Large diffs are not rendered by default.

10 changes: 6 additions & 4 deletions ghcide/src/Development/IDE/Import/DependencyInformation.hs
Original file line number Diff line number Diff line change
@@ -49,11 +49,13 @@ import Data.Maybe
import Data.Tuple.Extra hiding (first, second)
import Development.IDE.GHC.Compat
import Development.IDE.GHC.Orphans ()
import Development.IDE.Graph.Internal.Rules
import Development.IDE.Import.FindImports (ArtifactsLocation (..))
import Development.IDE.Types.Diagnostics
import Development.IDE.Types.Location
import GHC.Generics (Generic)
import Prelude hiding (mod)
import Development.IDE.Core.InputPath (InputPath(..))


-- | The imports for a given module.
@@ -335,10 +337,10 @@ transitiveReverseDependencies file DependencyInformation{..} = do
in IntSet.foldr go res new

-- | Immediate reverse dependencies of a file
immediateReverseDependencies :: NormalizedFilePath -> DependencyInformation -> Maybe [NormalizedFilePath]
immediateReverseDependencies file DependencyInformation{..} = do
FilePathId cur_id <- lookupPathToId depPathIdMap file
return $ map (idToPath depPathIdMap . FilePathId) (maybe mempty IntSet.toList (IntMap.lookup cur_id depReverseModuleDeps))
immediateReverseDependencies :: InputPath ProjectHaskellFiles -> DependencyInformation -> Maybe [InputPath ProjectHaskellFiles]
immediateReverseDependencies input DependencyInformation{..} = do
FilePathId cur_id <- lookupPathToId depPathIdMap $ unInputPath input
return $ map (InputPath . idToPath depPathIdMap . FilePathId) (maybe mempty IntSet.toList (IntMap.lookup cur_id depReverseModuleDeps))

-- | returns all transitive dependencies in topological order.
transitiveDeps :: DependencyInformation -> NormalizedFilePath -> Maybe TransitiveDependencies
3 changes: 2 additions & 1 deletion ghcide/src/Development/IDE/LSP/Outline.hs
Original file line number Diff line number Diff line change
@@ -29,14 +29,15 @@ import Language.LSP.Protocol.Types (DocumentSymbol (..),
TextDocumentIdentifier (TextDocumentIdentifier),
type (|?) (InL, InR),
uriToFilePath)
import Development.IDE.Core.InputPath (InputPath(InputPath))


moduleOutline
:: PluginMethodHandler IdeState Method_TextDocumentDocumentSymbol
moduleOutline ideState _ DocumentSymbolParams{ _textDocument = TextDocumentIdentifier uri }
= liftIO $ case uriToFilePath uri of
Just (toNormalizedFilePath' -> fp) -> do
mb_decls <- fmap fst <$> runAction "Outline" ideState (useWithStale GetParsedModule fp)
mb_decls <- fmap fst <$> runAction "Outline" ideState (useWithStale GetParsedModule $ InputPath fp)
pure $ case mb_decls of
Nothing -> InL []
Just ParsedModule { pm_parsed_source = L _ltop HsModule { hsmodName, hsmodDecls, hsmodImports } }
3 changes: 3 additions & 0 deletions ghcide/src/Development/IDE/Plugin/Completions/Types.hs
Original file line number Diff line number Diff line change
@@ -19,6 +19,7 @@ import Data.Text (Text)
import Data.Typeable (Typeable)
import Development.IDE.GHC.Compat
import Development.IDE.Graph (RuleResult)
import Development.IDE.Graph.Internal.Rules (RuleInput, InputClass(..))
import Development.IDE.Spans.Common ()
import GHC.Generics (Generic)
import qualified GHC.Types.Name.Occurrence as Occ
@@ -28,7 +29,9 @@ import qualified Language.LSP.Protocol.Types as J

-- | Produce completions info for a file
type instance RuleResult LocalCompletions = CachedCompletions
type instance RuleInput LocalCompletions = ProjectHaskellFiles
type instance RuleResult NonLocalCompletions = CachedCompletions
type instance RuleInput NonLocalCompletions = ProjectHaskellFiles

data LocalCompletions = LocalCompletions
deriving (Eq, Show, Typeable, Generic)
12 changes: 7 additions & 5 deletions ghcide/src/Development/IDE/Plugin/Test.hs
Original file line number Diff line number Diff line change
@@ -52,6 +52,8 @@ import Language.LSP.Protocol.Types
import qualified "list-t" ListT
import qualified StmContainers.Map as STM
import System.Time.Extra
import Development.IDE.Core.InputPath (InputPath(InputPath), generalizeProjectInput)
import Development.IDE.Graph.Internal.Rules (InputClass(ProjectHaskellFiles))

type Age = Int
data TestRequest
@@ -98,7 +100,7 @@ testRequestHandler _ (BlockSeconds secs) = do
return (Right A.Null)
testRequestHandler s (GetInterfaceFilesDir file) = liftIO $ do
let nfp = fromUri $ toNormalizedUri file
sess <- runAction "Test - GhcSession" s $ use_ GhcSession nfp
sess <- runAction "Test - GhcSession" s $ use_ GhcSession $ InputPath nfp
let hiPath = hiDir $ hsc_dflags $ hscEnv sess
return $ Right (toJSON hiPath)
testRequestHandler s GetShakeSessionQueueCount = liftIO $ do
@@ -111,7 +113,7 @@ testRequestHandler s WaitForShakeQueue = liftIO $ do
return $ Right A.Null
testRequestHandler s (WaitForIdeRule k file) = liftIO $ do
let nfp = fromUri $ toNormalizedUri file
success <- runAction ("WaitForIdeRule " <> k <> " " <> show file) s $ parseAction (fromString k) nfp
success <- runAction ("WaitForIdeRule " <> k <> " " <> show file) s $ parseAction (fromString k) $ InputPath $ nfp
let res = WaitForIdeRuleResult <$> success
return $ bimap PluginInvalidParams toJSON res
testRequestHandler s GetBuildKeysBuilt = liftIO $ do
@@ -147,16 +149,16 @@ getDatabaseKeys field db = do
step <- shakeGetBuildStep db
return [ k | (k, res) <- keys, field res == Step step]

parseAction :: CI String -> NormalizedFilePath -> Action (Either Text Bool)
parseAction :: CI String -> InputPath ProjectHaskellFiles -> Action (Either Text Bool)
parseAction "typecheck" fp = Right . isJust <$> use TypeCheck fp
parseAction "getLocatedImports" fp = Right . isJust <$> use GetLocatedImports fp
parseAction "getmodsummary" fp = Right . isJust <$> use GetModSummary fp
parseAction "getmodsummarywithouttimestamps" fp = Right . isJust <$> use GetModSummaryWithoutTimestamps fp
parseAction "getparsedmodule" fp = Right . isJust <$> use GetParsedModule fp
parseAction "ghcsession" fp = Right . isJust <$> use GhcSession fp
parseAction "ghcsessiondeps" fp = Right . isJust <$> use GhcSessionDeps fp
parseAction "gethieast" fp = Right . isJust <$> use GetHieAst fp
parseAction "getFileContents" fp = Right . isJust <$> use GetFileContents fp
parseAction "gethieast" fp = Right . isJust <$> use GetHieAst (generalizeProjectInput fp)
parseAction "getFileContents" fp = Right . isJust <$> use GetFileContents (generalizeProjectInput fp)
parseAction other _ = return $ Left $ "Cannot parse ide rule: " <> pack (original other)

-- | a command that blocks forever. Used for testing
28 changes: 19 additions & 9 deletions ghcide/src/Development/IDE/Plugin/TypeLenses.hs
Original file line number Diff line number Diff line change
@@ -24,7 +24,7 @@ import Data.Aeson.Types (toJSON)
import qualified Data.Aeson.Types as A
import Data.List (find)
import qualified Data.Map as Map
import Data.Maybe (catMaybes, maybeToList)
import Data.Maybe (catMaybes, maybeToList, listToMaybe)
import qualified Data.Text as T
import Development.IDE (GhcSession (..),
HscEnvEq (hscEnv),
@@ -81,6 +81,9 @@ import Language.LSP.Protocol.Types (ApplyWorkspaceEditParams
WorkspaceEdit (WorkspaceEdit),
type (|?) (..))
import Text.Regex.TDFA ((=~))
import Development.IDE.Graph (RuleInput)
import Development.IDE.Graph.Internal.Rules (InputClass(ProjectHaskellFiles))
import Development.IDE.Core.InputPath (classifyProjectHaskellInputs)

data Log = LogShake Shake.Log deriving Show

@@ -167,18 +170,24 @@ codeLensProvider ideState pId CodeLensParams{_textDocument = TextDocumentIdentif
codeLensResolveProvider :: ResolveFunction IdeState TypeLensesResolve Method_CodeLensResolve
codeLensResolveProvider ideState pId lens@CodeLens{_range} uri TypeLensesResolve = do
nfp <- getNormalizedFilePathE uri
(gblSigs@(GlobalBindingTypeSigsResult _), pm) <-
runActionE "codeLens.GetGlobalBindingTypeSigs" ideState
$ useWithStaleE GetGlobalBindingTypeSigs nfp
let mInput = listToMaybe $ classifyProjectHaskellInputs [nfp]
(mGblSigs, mPm) <-
case mInput of
Nothing -> pure (Nothing, Nothing)
Just input -> do
(gblSigs@(GlobalBindingTypeSigsResult _), pm) <-
runActionE "codeLens.GetGlobalBindingTypeSigs" ideState
$ useWithStaleE GetGlobalBindingTypeSigs input
pure (Just gblSigs, Just pm)
-- regardless of how the original lens was generated, we want to get the range
-- that the global bindings rule would expect here, hence the need to reverse
-- position map the range, regardless of whether it was position mapped in the
-- beginning or freshly taken from diagnostics.
newRange <- handleMaybe PluginStaleResolve (fromCurrentRange pm _range)
newRange <- handleMaybe PluginStaleResolve (mPm >>= flip fromCurrentRange _range)
-- We also pass on the PositionMapping so that the generated text edit can
-- have the range adjusted.
(title, edit) <-
handleMaybe PluginStaleResolve $ suggestGlobalSignature' False (Just gblSigs) (Just pm) newRange
handleMaybe PluginStaleResolve $ suggestGlobalSignature' False mGblSigs mPm newRange
pure $ lens & L.command ?~ generateLensCommand pId uri title edit

generateLensCommand :: PluginId -> Uri -> T.Text -> TextEdit -> Command
@@ -295,13 +304,14 @@ instance NFData GlobalBindingTypeSigsResult where
rnf = rwhnf

type instance RuleResult GetGlobalBindingTypeSigs = GlobalBindingTypeSigsResult
type instance RuleInput GetGlobalBindingTypeSigs = ProjectHaskellFiles

rules :: Recorder (WithPriority Log) -> Rules ()
rules recorder = do
define (cmapWithPrio LogShake recorder) $ \GetGlobalBindingTypeSigs nfp -> do
tmr <- use TypeCheck nfp
define (cmapWithPrio LogShake recorder) $ \GetGlobalBindingTypeSigs input -> do
tmr <- use TypeCheck input
-- we need session here for tidying types
hsc <- use GhcSession nfp
hsc <- use GhcSession input
result <- liftIO $ gblBindingType (hscEnv <$> hsc) (tmrTypechecked <$> tmr)
pure ([], result)

43 changes: 29 additions & 14 deletions ghcide/src/Development/IDE/Spans/AtPoint.hs
Original file line number Diff line number Diff line change
@@ -208,11 +208,11 @@ gotoDefinition withHieDb getHieFile ideOpts imports srcSpans pos
atPoint
:: IdeOptions
-> HieAstResult
-> DocAndTyThingMap
-> HscEnv
-> Maybe DocAndTyThingMap
-> Maybe HscEnv
-> Position
-> IO (Maybe (Maybe Range, [T.Text]))
atPoint IdeOptions{} (HAR _ hf _ _ (kind :: HieKind hietype)) (DKMap dm km) env pos =
atPoint IdeOptions{} (HAR _ hf _ _ (kind :: HieKind hietype)) mDkMap mEnv pos =
listToMaybe <$> sequence (pointCommand hf pos hoverInfo)
where
-- Hover info for values/data
@@ -251,9 +251,15 @@ atPoint IdeOptions{} (HAR _ hf _ _ (kind :: HieKind hietype)) (DKMap dm km) env
prettyName (Right n, dets) = pure $ T.unlines $
wrapHaskell (printOutputable n <> maybe "" (" :: " <>) ((prettyType <$> identType dets) <|> maybeKind))
: maybeToList (pretty (definedAt n) (prettyPackageName n))
++ catMaybes [ T.unlines . spanDocToMarkdown <$> lookupNameEnv dm n
++ catMaybes [ T.unlines . spanDocToMarkdown <$> maybeDoc
]
where maybeKind = fmap printOutputable $ safeTyThingType =<< lookupNameEnv km n
where maybeKind = do
(DKMap _ km) <- mDkMap
nameEnv <- lookupNameEnv km n
printOutputable <$> safeTyThingType nameEnv
maybeDoc = do
(DKMap dm _) <- mDkMap
lookupNameEnv dm n
pretty Nothing Nothing = Nothing
pretty (Just define) Nothing = Just $ define <> "\n"
pretty Nothing (Just pkgName) = Just $ pkgName <> "\n"
@@ -270,22 +276,31 @@ atPoint IdeOptions{} (HAR _ hf _ _ (kind :: HieKind hietype)) (DKMap dm km) env
-- the package(with version) this `ModuleName` belongs to.
packageNameForImportStatement :: ModuleName -> IO T.Text
packageNameForImportStatement mod = do
mpkg <- findImportedModule env mod :: IO (Maybe Module)
mpkg <- fmap join $ sequence $
flip findImportedModule mod <$> mEnv :: IO (Maybe Module)
let moduleName = printOutputable mod
case mpkg >>= packageNameWithVersion of
Nothing -> pure moduleName
Just pkgWithVersion -> pure $ moduleName <> "\n\n" <> pkgWithVersion

-- Return the package name and version of a module.
-- For example, given module `Data.List`, it should return something like `base-4.x`.
packageNameWithVersion :: Module -> Maybe T.Text
packageNameWithVersion m = do
let pid = moduleUnit m
conf <- lookupUnit env pid
let pkgName = T.pack $ unitPackageNameString conf
version = T.pack $ showVersion (unitPackageVersion conf)
pure $ pkgName <> "-" <> version

packageNameWithVersion m = let pid = moduleUnit m in
case mEnv of
-- If we have an HscEnv (because this is a project file),
-- we can get the package name from that.
Just env -> do
conf <- lookupUnit env pid
let pkgName = T.pack $ unitPackageNameString conf
version = T.pack $ showVersion (unitPackageVersion conf)
pure $ pkgName <> "-" <> version
-- If we don't have an HscEnv (because this is a dependency file),
-- then we can get a similar format for the package name
-- from the UnitId.
Nothing ->
let uid = toUnitId pid
pkgStr = takeWhile (/= ':') $ show uid
in Just $ T.pack pkgStr
-- Type info for the current node, it may contains several symbols
-- for one range, like wildcard
types :: [hietype]
12 changes: 7 additions & 5 deletions ghcide/src/Development/IDE/Spans/Pragmas.hs
Original file line number Diff line number Diff line change
@@ -15,7 +15,7 @@ import qualified Data.List as List
import qualified Data.Maybe as Maybe
import Data.Text (Text, pack)
import qualified Data.Text as Text
import Development.IDE (srcSpanToRange, IdeState, NormalizedFilePath, GhcSession (..), getFileContents, hscEnv, runAction)
import Development.IDE (srcSpanToRange, IdeState, GhcSession (..), getFileContents, hscEnv, runAction)
import Development.IDE.GHC.Compat
import Development.IDE.GHC.Compat.Util
import qualified Language.LSP.Protocol.Types as LSP
@@ -26,6 +26,8 @@ import Ide.Types (PluginId(..))
import qualified Data.Text as T
import Development.IDE.Core.PluginUtils
import qualified Language.LSP.Protocol.Lens as L
import Development.IDE.Core.InputPath (InputPath, generalizeProjectInput)
import Development.IDE.Graph.Internal.Rules (InputClass(ProjectHaskellFiles))

getNextPragmaInfo :: DynFlags -> Maybe Text -> NextPragmaInfo
getNextPragmaInfo dynFlags mbSourceText =
@@ -53,10 +55,10 @@ insertNewPragma (NextPragmaInfo nextPragmaLine _) newPragma = LSP.TextEdit prag
pragmaInsertPosition = LSP.Position (fromIntegral nextPragmaLine) 0
pragmaInsertRange = LSP.Range pragmaInsertPosition pragmaInsertPosition

getFirstPragma :: MonadIO m => PluginId -> IdeState -> NormalizedFilePath -> ExceptT PluginError m NextPragmaInfo
getFirstPragma (PluginId pId) state nfp = do
(hscEnv -> hsc_dflags -> sessionDynFlags, _) <- runActionE (T.unpack pId <> ".GhcSession") state $ useWithStaleE GhcSession nfp
(_, fileContents) <- liftIO $ runAction (T.unpack pId <> ".GetFileContents") state $ getFileContents nfp
getFirstPragma :: MonadIO m => PluginId -> IdeState -> InputPath ProjectHaskellFiles -> ExceptT PluginError m NextPragmaInfo
getFirstPragma (PluginId pId) state input = do
(hscEnv -> hsc_dflags -> sessionDynFlags, _) <- runActionE (T.unpack pId <> ".GhcSession") state $ useWithStaleE GhcSession input
(_, fileContents) <- liftIO $ runAction (T.unpack pId <> ".GetFileContents") state $ getFileContents $ generalizeProjectInput input
pure $ getNextPragmaInfo sessionDynFlags fileContents

-- Pre-declaration comments parser -----------------------------------------------------
2 changes: 1 addition & 1 deletion hls-graph/src/Development/IDE/Graph.hs
Original file line number Diff line number Diff line change
@@ -12,7 +12,7 @@ module Development.IDE.Graph(
-- * Explicit parallelism
parallel,
-- * Oracle rules
ShakeValue, RuleResult,
ShakeValue, RuleResult, RuleInput,
-- * Special rules
alwaysRerun,
-- * Actions for inspecting the keys in the database
10 changes: 10 additions & 0 deletions hls-graph/src/Development/IDE/Graph/Internal/Rules.hs
Original file line number Diff line number Diff line change
@@ -23,6 +23,16 @@ import Development.IDE.Graph.Internal.Types
-- | The type mapping between the @key@ or a rule and the resulting @value@.
type family RuleResult key -- = value

-- | The broadest class of files a Rule is applicable to
data InputClass
= ProjectHaskellFiles
| AllHaskellFiles
| NoFile

-- | The type mapping between the @key@ or a rule and the
-- class of files it is applicable to.
type family RuleInput key :: InputClass

action :: Action a -> Rules ()
action x = do
ref <- Rules $ asks rulesActions