1-- Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
2-- SPDX-License-Identifier: Apache-2.0
4{-# LANGUAGE TypeFamilies #-}
6module Development.IDE.State.FileStore(
7 getFileExists, getFileContents,
16import qualified Data.Map.Strict as Map
17import qualified Data.Text as T
19import Control.Monad.Extra
20import qualified System.Directory as Dir
21import Development.Shake
22import Development.Shake.Classes
23import Development.IDE.State.Shake
24import Development.IDE.UtilGHC
25import Control.Concurrent.Extra
26import Control.Exception
29import qualified Data.ByteString.Char8 as BS
30import Development.IDE.Types.Diagnostics
34-- This module stores the changed files in memory, and answers file system questions
35-- from either the memory changes OR the file system itself
37type DirtyFiles = Map.Map FilePath (UTCTime, StringBuffer) -- when it was modified, it's current value
39-- Store the DirtyFiles globally, so we can get at it through setBufferModified
40newtype GlobalDirtyFiles = GlobalDirtyFiles (Var DirtyFiles)
41instance IsIdeGlobal GlobalDirtyFiles
45-- | Get the modification time of a file.
46type instance RuleResult GetModificationTime = UTCTime
48-- | Get the contents of a file, either dirty (if the buffer is modified) or from disk.
49type instance RuleResult GetFileContents = (UTCTime, StringBuffer)
51-- | Does the file exist.
52type instance RuleResult GetFileExists = Bool
55data GetFileExists = GetFileExists
56 deriving (Eq, Show, Generic)
57instance Hashable GetFileExists
58instance NFData GetFileExists
60data GetModificationTime = GetModificationTime
61 deriving (Eq, Show, Generic)
62instance Hashable GetModificationTime
63instance NFData GetModificationTime
65data GetFileContents = GetFileContents
66 deriving (Eq, Show, Generic)
67instance Hashable GetFileContents
68instance NFData GetFileContents
71getFileExistsRule :: Var DirtyFiles -> Rules ()
72getFileExistsRule dirty =
73 defineEarlyCutoff $ \GetFileExists file -> do
75 res <- liftIO $ handle (\(_ :: IOException) -> return False) $
76 (Map.member file <$> readVar dirty) ||^
77 Dir.doesFileExist file
78 return (Just $ if res then BS.singleton '1' else BS.empty, ([], Just res))
81showTimePrecise :: UTCTime -> String
82showTimePrecise UTCTime{..} = show (toModifiedJulianDay utctDay, diffTimeToPicoseconds utctDayTime)
84getModificationTimeRule :: Var DirtyFiles -> Rules ()
85getModificationTimeRule dirty =
86 defineEarlyCutoff $ \GetModificationTime file -> do
87 let wrap time = (Just $ BS.pack $ showTimePrecise time, ([], Just time))
89 mp <- liftIO $ readVar dirty
90 case Map.lookup file mp of
91 Just (time, _) -> return $ wrap time
92 Nothing -> liftIO $ fmap wrap (Dir.getModificationTime file) `catch` \(e :: IOException) -> do
93 let err | isDoesNotExistError e = "File does not exist: " ++ file
94 | otherwise = "IO error while reading " ++ file ++ ", " ++ displayException e
95 return (Nothing, ([ideErrorText file $ T.pack err], Nothing))
98getFileContentsRule :: Var DirtyFiles -> Rules ()
99getFileContentsRule dirty =
100 define $ \GetFileContents file -> do
101 -- need to depend on modification time to introduce a dependency with Cutoff
102 time <- use_ GetModificationTime file
103 res <- liftIO $ ideTryIOException file $ do
105 case Map.lookup file mp of
106 Just (_, contents) -> return contents
107 Nothing -> hGetStringBuffer file
109 Left err -> return ([err], Nothing)
110 Right contents -> return ([], Just (time, contents))
113getFileContents :: FilePath -> Action (UTCTime, StringBuffer)
114getFileContents = use_ GetFileContents
116getFileExists :: FilePath -> Action Bool
118 -- we deliberately and intentionally wrap the file as an FilePath WITHOUT mkAbsolute
119 -- so that if the file doesn't exist, is on a shared drive that is unmounted etc we get a properly
120 -- cached 'No' rather than an exception in the wrong place
124fileStoreRules :: Rules ()
126 dirty <- liftIO $ newVar Map.empty
127 addIdeGlobal $ GlobalDirtyFiles dirty
128 getModificationTimeRule dirty
129 getFileContentsRule dirty
130 getFileExistsRule dirty
133strictPair :: a -> b -> (a, b)
134strictPair !a !b = (a,b)
137-- | Notify the compiler service of a modified buffer
138setBufferModified :: IdeState -> FilePath -> (Maybe T.Text, UTCTime) -> IO ()
139setBufferModified state absFile (mcontents, !time) = do
140 GlobalDirtyFiles envDirtyFiles <- getIdeGlobalState state
141 -- update vars synchronously
142 modifyVar_ envDirtyFiles $ evaluate . case mcontents of
143 Nothing -> Map.delete absFile
144 Just contents -> Map.insert absFile $ strictPair time (textToStringBuffer contents)
146 -- run shake to update results regarding the files of interest
147 void $ shakeRun state []
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list