1-- Copyright (c) 2019 The DAML Authors. All rights reserved.
2-- SPDX-License-Identifier: Apache-2.0
4{-# LANGUAGE TypeFamilies #-}
6module Development.IDE.Core.FileStore(
18import Development.IDE.GHC.Orphans()
19import Development.IDE.Core.Shake
20import Control.Concurrent.Extra
21import qualified Data.Map.Strict as Map
23import qualified Data.Text as T
24import Control.Monad.Extra
25import Development.Shake
26import Development.Shake.Classes
27import Control.Exception
29import Data.Either.Extra
31import qualified Data.ByteString.Char8 as BS
32import Development.IDE.Types.Diagnostics
33import Development.IDE.Types.Location
34import qualified Data.Rope.UTF16 as Rope
38import qualified System.Directory as Dir
41import Foreign.C.String
43import Foreign.Marshal (alloca)
44import Foreign.Storable
45import qualified System.Posix.Error as Posix
48import Language.Haskell.LSP.Core
49import Language.Haskell.LSP.VFS
51-- | haskell-lsp manages the VFS internally and automatically so we cannot use
52-- the builtin VFS without spawning up an LSP server. To be able to test things
53-- like `setBufferModified` we abstract over the VFS implementation.
54data VFSHandle = VFSHandle
55 { getVirtualFile :: NormalizedUri -> IO (Maybe VirtualFile)
56 -- ^ get the contents of a virtual file
57 , setVirtualFileContents :: Maybe (NormalizedUri -> Maybe T.Text -> IO ())
58 -- ^ set a specific file to a value. If Nothing then we are ignoring these
59 -- signals anyway so can just say something was modified
62instance IsIdeGlobal VFSHandle
64makeVFSHandle :: IO VFSHandle
66 vfsVar <- newVar (1, Map.empty)
68 { getVirtualFile = \uri -> do
69 (_nextVersion, vfs) <- readVar vfsVar
70 pure $ Map.lookup uri vfs
71 , setVirtualFileContents = Just $ \uri content ->
72 modifyVar_ vfsVar $ \(nextVersion, vfs) -> pure $ (nextVersion + 1, ) $
74 Nothing -> Map.delete uri vfs
75 -- The second version number is only used in persistFileVFS which we do not use so we set it to 0.
76 Just content -> Map.insert uri (VirtualFile nextVersion 0 (Rope.fromText content)) vfs
79makeLSPVFSHandle :: LspFuncs c -> VFSHandle
80makeLSPVFSHandle lspFuncs = VFSHandle
81 { getVirtualFile = getVirtualFileFunc lspFuncs
82 , setVirtualFileContents = Nothing
86-- | Get the contents of a file, either dirty (if the buffer is modified) or Nothing to mean use from disk.
87type instance RuleResult GetFileContents = (FileVersion, Maybe T.Text)
89data GetFileContents = GetFileContents
90 deriving (Eq, Show, Generic)
91instance Hashable GetFileContents
92instance NFData GetFileContents
93instance Binary GetFileContents
95getModificationTimeRule :: VFSHandle -> Rules ()
96getModificationTimeRule vfs =
97 defineEarlyCutoff $ \GetModificationTime file -> do
98 let file' = fromNormalizedFilePath file
99 let wrap time@(l,s) = do
100 hPutStrLn stderr (show $ BS.pack $ show time)
101 pure (Just $ BS.pack $ show time, ([], Just $ ModificationTime l s))
103 mbVirtual <- liftIO $ getVirtualFile vfs $ filePathToUri' file
105 Just (virtualFileVersion -> ver) ->
106 pure (Just $ BS.pack $ show ver, ([], Just $ VFSVersion ver))
107 Nothing -> liftIO $ (wrap =<< getModTime file')
108 `catch` \(e :: IOException) -> do
109 let err | isDoesNotExistError e = "File does not exist: " ++ file'
110 | otherwise = "IO error while reading " ++ file' ++ ", " ++ displayException e
111 return (Nothing, ([ideErrorText file $ T.pack err], Nothing))
113 -- Dir.getModificationTime is surprisingly slow since it performs
114 -- a ton of conversions. Since we do not actually care about
115 -- the format of the time, we can get away with something cheaper.
116 -- For now, we only try to do this on Unix systems where it seems to get the
117 -- time spent checking file modifications (which happens on every change)
118 -- from > 0.5s to ~0.15s.
119 -- We might also want to try speeding this up on Windows at some point.
120 -- TODO leverage DidChangeWatchedFile lsp notifications on clients that
121 -- support them, as done for GetFileExists
122 getModTime :: FilePath -> IO (Int,Int)
124#ifdef mingw32_HOST_OS
125 do time <- Dir.getModificationTime f
126 let !day = fromInteger $ toModifiedJulianDay $ utctDay time
127 !dayTime = fromInteger $ diffTimeToPicoseconds $ utctDayTime time
130 withCString f $ \f' ->
132 alloca $ \nsecPtr -> do
133 Posix.throwErrnoPathIfMinus1Retry_ "getmodtime" f $ c_getModTime f' secPtr nsecPtr
136 hPutStrLn stderr (show (f, sec, nsec))
137 pure (fromEnum sec, fromIntegral nsec)
139-- Sadly even unix’s getFileStatus + modificationTimeHiRes is still about twice as slow
140-- as doing the FFI call ourselves :(.
141foreign import ccall "getmodtime" c_getModTime :: CString -> Ptr CTime -> Ptr CLong -> IO Int
144getFileContentsRule :: VFSHandle -> Rules ()
145getFileContentsRule vfs =
146 define $ \GetFileContents file -> do
147 -- need to depend on modification time to introduce a dependency with Cutoff
148 time <- use_ GetModificationTime file
149 res <- liftIO $ ideTryIOException file $ do
150 mbVirtual <- getVirtualFile vfs $ filePathToUri' file
151 pure $ Rope.toText . _text <$> mbVirtual
153 Left err -> return ([err], Nothing)
154 Right contents -> return ([], Just (time, contents))
156ideTryIOException :: NormalizedFilePath -> IO a -> IO (Either FileDiagnostic a)
157ideTryIOException fp act =
159 (\(e :: IOException) -> ideErrorText fp $ T.pack $ show e)
163getFileContents :: NormalizedFilePath -> Action (FileVersion, Maybe T.Text)
164getFileContents = use_ GetFileContents
166fileStoreRules :: VFSHandle -> Rules ()
167fileStoreRules vfs = do
169 getModificationTimeRule vfs
170 getFileContentsRule vfs
173-- | Notify the compiler service that a particular file has been modified.
174-- Use 'Nothing' to say the file is no longer in the virtual file system
175-- but should be sourced from disk, or 'Just' to give its new value.
176setBufferModified :: IdeState -> NormalizedFilePath -> Maybe T.Text -> IO ()
177setBufferModified state absFile contents = do
178 VFSHandle{..} <- getIdeGlobalState state
179 whenJust setVirtualFileContents $ \set ->
180 set (filePathToUri' absFile) contents
181 void $ shakeRun state []
183-- | Note that some buffer somewhere has been modified, but don't say what.
184-- Only valid if the virtual file system was initialised by LSP, as that
185-- independently tracks which files are modified.
186setSomethingModified :: IdeState -> IO ()
187setSomethingModified state = do
188 VFSHandle{..} <- getIdeGlobalState state
189 when (isJust setVirtualFileContents) $
190 fail "setSomethingModified can't be called on this type of VFSHandle"
191 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