-- | Internal state of the session -- -- This uses the internal types only. module IdeSession.State ( -- * Types Computed(..) , IdeSession(..) , IdeStaticInfo(..) , IdeSessionState(..) , LogicalTimestamp , IdeIdleState(..) , ManagedFilesInternal(..) , ManagedFile , GhcServer(..) , RunActions(..) , IdeCallbacks(..) -- * Accessors , ideLogicalTimestamp , ideComputed , ideGhcOpts , ideRelativeIncludes , ideGenerateCode , ideManagedFiles , ideObjectFiles , ideBuildExeStatus , ideBuildDocStatus , ideBuildLicensesStatus , ideEnv , ideArgs , ideGhcServer , ideGhcVersion , ideStdoutBufferMode , ideStderrBufferMode , ideBreakInfo , ideTargets , ideRtsOpts , managedSource , managedData -- * To allow for non-server environments , ideSourceDir , ideDataDir -- * Callbacks , defaultIdeCallbacks , ideLogFunc ) where import Control.Concurrent (ThreadId) import Data.Accessor (Accessor, accessor) import Data.Digest.Pure.MD5 (MD5Digest) import System.Exit (ExitCode) import System.Posix.Types (EpochTime) import qualified Data.ByteString as BSS import IdeSession.Config import IdeSession.GHC.API (GhcVersion) import IdeSession.RPC.Client (RpcServer, RpcConversation, ExternalException) import IdeSession.Strict.Container import IdeSession.Strict.MVar (StrictMVar) import IdeSession.Types.Private hiding (RunResult) import qualified IdeSession.Types.Public as Public import IdeSession.Util.Logger import System.FilePath (()) data Computed = Computed { -- | Last compilation and run errors computedErrors :: !(Strict [] SourceError) -- | Modules that got loaded okay , computedLoadedModules :: !(Strict [] ModuleName) -- | Mapping from filepaths to the modules they define , computedFileMap :: !(Strict (Map FilePath) ModuleId) -- | Import information. This is (usually) available even for modules -- with parsing or type errors , computedImports :: !(Strict (Map ModuleName) (Strict [] Import)) -- | Autocompletion map -- -- Mapping, per module, from prefixes to fully qualified names -- I.e., @fo@ might map to @Control.Monad.forM@, @Control.Monad.forM_@ -- etc. (or possibly to @M.forM@, @M.forM_@, etc when Control.Monad -- was imported qualified as @M@). , computedAutoMap :: !(Strict (Map ModuleName) (Strict Trie (Strict [] IdInfo))) -- | Information about identifiers/quasi-quotes , computedSpanInfo :: !(Strict (Map ModuleName) IdMap) -- | Type information about subexpressions , computedExpTypes :: !(Strict (Map ModuleName) ExpMap) -- | Use sites , computedUseSites :: !(Strict (Map ModuleName) UseSites) -- | (Transitive) package dependencies , computedPkgDeps :: !(Strict (Map ModuleName) (Strict [] PackageId)) -- | We access IdProps indirectly through this cache , computedCache :: !ExplicitSharingCache } deriving Show -- | This type is a handle to a session state. Values of this type -- point to the non-persistent parts of the session state in memory -- and to directories containing source and data file that form -- the persistent part of the session state. Whenever we perform updates -- or run queries, it's always in the context of a particular handle, -- representing the session we want to work within. Many sessions -- can be active at once, but in normal applications this shouldn't be needed. -- data IdeSession = IdeSession { ideStaticInfo :: IdeStaticInfo , ideState :: StrictMVar IdeSessionState , ideCallbacks :: IdeCallbacks } data IdeStaticInfo = IdeStaticInfo { -- | Configuration ideConfig :: !SessionConfig -- | (Temporary) directory for session files -- -- See also: -- * 'ideSessionSourceDir' -- * 'ideSessionDataDir', -- * 'ideSessionDistDir' , ideSessionDir :: !FilePath , ideDistDir :: !FilePath } data IdeSessionState = IdeSessionIdle IdeIdleState | IdeSessionShutdown | IdeSessionServerDied ExternalException IdeIdleState type LogicalTimestamp = EpochTime -- TODO: We should split this into local state and (cached) "remote" state. -- Then we can change IdeSessionUpdate to have access to the local state only -- (IdeSessionUpdates _schedules_ remote updates, but doesn't run them; this -- happens only in updateSession). data IdeIdleState = IdeIdleState { -- | A workaround for http://hackage.haskell.org/trac/ghc/ticket/7473. -- Logical timestamps (used to force ghc to recompile files) _ideLogicalTimestamp :: !LogicalTimestamp -- | The result computed by the GHC API typing/compilation invocation -- in the last call to 'updateSession' invocation. , _ideComputed :: !(Strict Maybe Computed) -- | Current GHC options , _ideGhcOpts :: ![String] -- | Include paths (equivalent of GHC's @-i@ parameter) relative to the -- temporary directory where we store the session's source files. -- The initial value, used also for server startup, is taken from -- 'configRelativeIncludes'. -- -- By default this is the singleton list @[""]@ -- i.e., we include the -- sources dir (located there in simple setups, e.g., ide-backend tests) -- but nothing else. , _ideRelativeIncludes :: ![FilePath] -- | Whether to generate code in addition to type-checking. , _ideGenerateCode :: !Bool -- | Files submitted by the user and not deleted yet. , _ideManagedFiles :: !ManagedFilesInternal -- | Object files created from .c files , _ideObjectFiles :: !ObjectFiles -- | Exit status of the last invocation of 'buildExe', if any. , _ideBuildExeStatus :: !(Maybe ExitCode) -- | Exit status of the last invocation of 'buildDoc', if any. , _ideBuildDocStatus :: !(Maybe ExitCode) -- | Exit status of the last invocation of 'buildDoc', if any. , _ideBuildLicensesStatus :: !(Maybe ExitCode) -- | Environment overrides , _ideEnv :: ![(String, Maybe String)] -- | Command line arguments for snippets (expected value of `getArgs`) , _ideArgs :: ![String] -- | The GHC server (this is replaced in 'restartSession') , _ideGhcServer :: GhcServer -- Intentionally lazy -- | GHC version , _ideGhcVersion :: GhcVersion -- Intentionally lazy -- | Buffer mode for standard output for 'runStmt' , _ideStdoutBufferMode :: !Public.RunBufferMode -- | Buffer mode for standard error for 'runStmt' , _ideStderrBufferMode :: !Public.RunBufferMode -- | Are we currently in a breakpoint? , _ideBreakInfo :: !(Strict Maybe Public.BreakInfo) -- | Targets for compilation , _ideTargets :: !Public.Targets -- | RTS options (for the ghc session, not for executables) , _ideRtsOpts :: [String] } -- | The collection of source and data files submitted by the user. data ManagedFilesInternal = ManagedFilesInternal { _managedSource :: [ManagedFile] , _managedData :: [ManagedFile] } type ManagedFile = (FilePath, (MD5Digest, LogicalTimestamp)) -- | Mapping from C files to the corresponding .o files and their timestamps type ObjectFiles = [(FilePath, (FilePath, LogicalTimestamp))] data GhcServer = OutProcess RpcServer | InProcess RpcConversation ThreadId -- | Handles to the running code snippet, through which one can interact -- with the snippet. -- -- Requirement: concurrent uses of @supplyStdin@ should be possible, -- e.g., two threads that share a @RunActions@ should be able to provide -- input concurrently without problems. (Currently this is ensured -- by @supplyStdin@ writing to a channel.) data RunActions a = RunActions { -- | Wait for the code to output something or terminate runWait :: IO (Either BSS.ByteString a) -- | Send a UserInterrupt exception to the code -- -- A call to 'interrupt' after the snippet has terminated has no effect. , interrupt :: IO () -- | Make data available on the code's stdin -- -- A call to 'supplyStdin' after the snippet has terminated has no effect. , supplyStdin :: BSS.ByteString -> IO () -- | Force terminate the runaction -- (The server will be useless after this -- for internal use only). -- -- Guranteed not to block. , forceCancel :: IO () } -- | Session callbacks. Currently this just configures how logging is -- handled. data IdeCallbacks = IdeCallbacks { ideCallbacksLogFunc :: LogFunc } {------------------------------------------------------------------------------ Accessors ------------------------------------------------------------------------------} ideLogicalTimestamp :: Accessor IdeIdleState LogicalTimestamp ideComputed :: Accessor IdeIdleState (Strict Maybe Computed) ideGhcOpts :: Accessor IdeIdleState [String] ideRelativeIncludes :: Accessor IdeIdleState [FilePath] ideGenerateCode :: Accessor IdeIdleState Bool ideManagedFiles :: Accessor IdeIdleState ManagedFilesInternal ideObjectFiles :: Accessor IdeIdleState ObjectFiles ideBuildExeStatus :: Accessor IdeIdleState (Maybe ExitCode) ideBuildDocStatus :: Accessor IdeIdleState (Maybe ExitCode) ideBuildLicensesStatus :: Accessor IdeIdleState (Maybe ExitCode) ideEnv :: Accessor IdeIdleState [(String, Maybe String)] ideArgs :: Accessor IdeIdleState [String] ideGhcServer :: Accessor IdeIdleState GhcServer ideGhcVersion :: Accessor IdeIdleState GhcVersion ideStdoutBufferMode :: Accessor IdeIdleState Public.RunBufferMode ideStderrBufferMode :: Accessor IdeIdleState Public.RunBufferMode ideBreakInfo :: Accessor IdeIdleState (Strict Maybe Public.BreakInfo) ideTargets :: Accessor IdeIdleState Public.Targets ideRtsOpts :: Accessor IdeIdleState [String] ideLogicalTimestamp = accessor _ideLogicalTimestamp $ \x s -> s { _ideLogicalTimestamp = x } ideComputed = accessor _ideComputed $ \x s -> s { _ideComputed = x } ideGhcOpts = accessor _ideGhcOpts $ \x s -> s { _ideGhcOpts = x } ideRelativeIncludes = accessor _ideRelativeIncludes $ \x s -> s { _ideRelativeIncludes = x } ideGenerateCode = accessor _ideGenerateCode $ \x s -> s { _ideGenerateCode = x } ideManagedFiles = accessor _ideManagedFiles $ \x s -> s { _ideManagedFiles = x } ideObjectFiles = accessor _ideObjectFiles $ \x s -> s { _ideObjectFiles = x } ideBuildExeStatus = accessor _ideBuildExeStatus $ \x s -> s { _ideBuildExeStatus = x } ideBuildDocStatus = accessor _ideBuildDocStatus $ \x s -> s { _ideBuildDocStatus = x } ideBuildLicensesStatus = accessor _ideBuildLicensesStatus $ \x s -> s { _ideBuildLicensesStatus = x } ideEnv = accessor _ideEnv $ \x s -> s { _ideEnv = x } ideArgs = accessor _ideArgs $ \x s -> s { _ideArgs = x } ideGhcServer = accessor _ideGhcServer $ \x s -> s { _ideGhcServer = x } ideGhcVersion = accessor _ideGhcVersion $ \x s -> s { _ideGhcVersion = x } ideStdoutBufferMode = accessor _ideStdoutBufferMode $ \x s -> s { _ideStdoutBufferMode = x } ideStderrBufferMode = accessor _ideStderrBufferMode $ \x s -> s { _ideStderrBufferMode = x } ideBreakInfo = accessor _ideBreakInfo $ \x s -> s { _ideBreakInfo = x } ideTargets = accessor _ideTargets $ \x s -> s { _ideTargets = x } ideRtsOpts = accessor _ideRtsOpts $ \x s -> s { _ideRtsOpts = x } managedSource :: Accessor ManagedFilesInternal [ManagedFile] managedData :: Accessor ManagedFilesInternal [ManagedFile] managedSource = accessor _managedSource $ \x s -> s { _managedSource = x } managedData = accessor _managedData $ \x s -> s { _managedData = x } {------------------------------------------------------------------------------ To allow for non-server(local) environments ------------------------------------------------------------------------------} -- | Get the directory that holds source files. ideSourceDir :: IdeStaticInfo -> FilePath ideSourceDir IdeStaticInfo{..} = case configLocalWorkingDir ideConfig of Just path -> path Nothing -> ideSessionDir "src" -- | Get the directory that holds data files. ideDataDir :: IdeStaticInfo -> FilePath ideDataDir IdeStaticInfo{..} = case configLocalWorkingDir ideConfig of Just path -> path Nothing -> ideSessionDir "data" {------------------------------------------------------------------------------ Callbacks ------------------------------------------------------------------------------} -- | Default session configuration. -- -- Use this instead of creating your own IdeCallbacks to be robust -- against extensions of IdeCallbacks. -- -- >> defaultIdeCallbacks = IdeCallbacks -- >> { ideCallbacksLogFunc = \_ _ _ _ -> return () -- >> } defaultIdeCallbacks :: IdeCallbacks defaultIdeCallbacks = IdeCallbacks { ideCallbacksLogFunc = \_ _ _ _ -> return () } -- | Get the 'LogFunc' for use with the functions in "IdeSession.Util.Logger" ideLogFunc :: IdeSession -> LogFunc ideLogFunc = ideCallbacksLogFunc . ideCallbacks