ghc-modi's "find" now catches up if packageDb is updated.

This commit is contained in:
Kazu Yamamoto 2014-09-20 12:25:46 +09:00
parent e66aefebee
commit e8988c2f02
3 changed files with 72 additions and 33 deletions

View File

@ -46,6 +46,7 @@ module Language.Haskell.GhcMod (
, dumpSymbol , dumpSymbol
-- * SymbolDb -- * SymbolDb
, loadSymbolDb , loadSymbolDb
, isOutdated
) where ) where
import Language.Haskell.GhcMod.Boot import Language.Haskell.GhcMod.Boot

View File

@ -1,4 +1,4 @@
{-# LANGUAGE CPP #-} {-# LANGUAGE CPP, BangPatterns #-}
module Language.Haskell.GhcMod.Find module Language.Haskell.GhcMod.Find
#ifndef SPEC #ifndef SPEC
@ -10,6 +10,7 @@ module Language.Haskell.GhcMod.Find
, dumpSymbol , dumpSymbol
, findSymbol , findSymbol
, lookupSym , lookupSym
, isOutdated
) )
#endif #endif
where where
@ -52,8 +53,14 @@ import qualified Data.Map as M
-- | Type of function and operation names. -- | Type of function and operation names.
type Symbol = String type Symbol = String
-- | Database from 'Symbol' to \['ModuleString'\]. -- | Database from 'Symbol' to \['ModuleString'\].
newtype SymbolDb = SymbolDb (Map Symbol [ModuleString]) data SymbolDb = SymbolDb {
deriving (Show) table :: Map Symbol [ModuleString]
, packageCachePath :: FilePath
, symbolDbCachePath :: FilePath
} deriving (Show)
isOutdated :: SymbolDb -> IO Bool
isOutdated db = symbolDbCachePath db `isOlderThan` packageCachePath db
---------------------------------------------------------------- ----------------------------------------------------------------
@ -85,13 +92,27 @@ lookupSymbol :: IOish m => Symbol -> SymbolDb -> GhcModT m String
lookupSymbol sym db = convert' $ lookupSym sym db lookupSymbol sym db = convert' $ lookupSym sym db
lookupSym :: Symbol -> SymbolDb -> [ModuleString] lookupSym :: Symbol -> SymbolDb -> [ModuleString]
lookupSym sym (SymbolDb db) = fromMaybe [] $ M.lookup sym db lookupSym sym db = fromMaybe [] $ M.lookup sym $ table db
--------------------------------------------------------------- ---------------------------------------------------------------
-- | Loading a file and creates 'SymbolDb'. -- | Loading a file and creates 'SymbolDb'.
loadSymbolDb :: (IOish m, MonadError GhcModError m) => m SymbolDb loadSymbolDb :: (IOish m, MonadError GhcModError m) => m SymbolDb
loadSymbolDb = SymbolDb <$> readSymbolDb loadSymbolDb = do
ghcMod <- liftIO ghcModExecutable
file <- chop <$> readProcess' ghcMod ["dumpsym"]
!db <- M.fromAscList . map conv . lines <$> liftIO (readFile file)
return $ SymbolDb {
table = db
, packageCachePath = takeDirectory file </> packageCache
, symbolDbCachePath = file
}
where
conv :: String -> (Symbol,[ModuleString])
conv = read
chop "" = ""
chop xs = init xs
-- | Returns the path to the currently running ghc-mod executable. With ghc<7.6 -- | Returns the path to the currently running ghc-mod executable. With ghc<7.6
-- this is a guess but >=7.6 uses 'getExecutablePath'. -- this is a guess but >=7.6 uses 'getExecutablePath'.
@ -113,17 +134,6 @@ ghcModExecutable = do _ <- getExecutablePath' -- get rid of unused warning when
getExecutablePath' = return "" getExecutablePath' = return ""
# endif # endif
readSymbolDb :: (IOish m, MonadError GhcModError m) => m (Map Symbol [ModuleString])
readSymbolDb = do
ghcMod <- liftIO ghcModExecutable
file <- chop <$> readProcess' ghcMod ["dumpsym"]
M.fromAscList . map conv . lines <$> liftIO (readFile file)
where
conv :: String -> (Symbol,[ModuleString])
conv = read
chop "" = ""
chop xs = init xs
---------------------------------------------------------------- ----------------------------------------------------------------
-- used 'ghc-mod dumpsym' -- used 'ghc-mod dumpsym'
@ -144,7 +154,7 @@ dumpSymbol = do
let cache = dir </> symbolCache let cache = dir </> symbolCache
pkgdb = dir </> packageCache pkgdb = dir </> packageCache
create <- liftIO $ cache `isNewerThan` pkgdb create <- liftIO $ cache `isOlderThan` pkgdb
when create $ (liftIO . writeSymbolCache cache) =<< getSymbolTable when create $ (liftIO . writeSymbolCache cache) =<< getSymbolTable
return $ unlines [cache] return $ unlines [cache]
@ -155,15 +165,15 @@ writeSymbolCache cache sm =
void . withFile cache WriteMode $ \hdl -> void . withFile cache WriteMode $ \hdl ->
mapM (hPrint hdl) sm mapM (hPrint hdl) sm
isNewerThan :: FilePath -> FilePath -> IO Bool isOlderThan :: FilePath -> FilePath -> IO Bool
isNewerThan ref file = do isOlderThan cache file = do
exist <- doesFileExist ref exist <- doesFileExist cache
if not exist then if not exist then
return True return True
else do else do
tRef <- getModificationTime ref tCache <- getModificationTime cache
tFile <- getModificationTime file tFile <- getModificationTime file
return $ tRef <= tFile -- including equal just in case return $ tCache <= tFile -- including equal just in case
-- | Browsing all functions in all system/user modules. -- | Browsing all functions in all system/user modules.
getSymbolTable :: IOish m => GhcModT m [(Symbol,[ModuleString])] getSymbolTable :: IOish m => GhcModT m [(Symbol,[ModuleString])]

View File

@ -25,6 +25,7 @@ import Control.Exception (SomeException(..), Exception)
import qualified Control.Exception as E import qualified Control.Exception as E
import Control.Monad (when) import Control.Monad (when)
import CoreMonad (liftIO) import CoreMonad (liftIO)
import Data.IORef (IORef, newIORef, readIORef, writeIORef)
import Data.List (intercalate) import Data.List (intercalate)
import Data.List.Split (splitOn) import Data.List.Split (splitOn)
import Data.Typeable (Typeable) import Data.Typeable (Typeable)
@ -35,8 +36,8 @@ import Paths_ghc_mod
import System.Console.GetOpt import System.Console.GetOpt
import System.Directory (setCurrentDirectory) import System.Directory (setCurrentDirectory)
import System.Environment (getArgs) import System.Environment (getArgs)
import System.IO (hFlush,stdout)
import System.Exit (ExitCode, exitFailure) import System.Exit (ExitCode, exitFailure)
import System.IO (hFlush,stdout)
import Utils import Utils
@ -97,8 +98,9 @@ main = E.handle cmdHandler $
let rootdir = cradleRootDir cradle0 let rootdir = cradleRootDir cradle0
-- c = cradle0 { cradleCurrentDir = rootdir } TODO: ????? -- c = cradle0 { cradleCurrentDir = rootdir } TODO: ?????
setCurrentDirectory rootdir setCurrentDirectory rootdir
symDb <- async $ runGhcModT opt loadSymbolDb -- Asynchronous db loading starts here.
(res, _) <- runGhcModT opt $ loop symDb symdbreq <- newSymDbReq opt
(res, _) <- runGhcModT opt $ loop symdbreq
case res of case res of
Right () -> return () Right () -> return ()
@ -129,13 +131,13 @@ replace needle replacement = intercalate replacement . splitOn needle
---------------------------------------------------------------- ----------------------------------------------------------------
loop :: IOish m => SymDbReq -> GhcModT m () loop :: IOish m => SymDbReq -> GhcModT m ()
loop symDbReq = do loop symdbreq = do
cmdArg <- liftIO getLine cmdArg <- liftIO getLine
let (cmd,arg') = break (== ' ') cmdArg let (cmd,arg') = break (== ' ') cmdArg
arg = dropWhile (== ' ') arg' arg = dropWhile (== ' ') arg'
(ret,ok) <- case cmd of (ret,ok) <- case cmd of
"check" -> checkStx arg "check" -> checkStx arg
"find" -> findSym arg symDbReq "find" -> findSym arg symdbreq
"lint" -> lintStx arg "lint" -> lintStx arg
"info" -> showInfo arg "info" -> showInfo arg
"type" -> showType arg "type" -> showType arg
@ -154,7 +156,7 @@ loop symDbReq = do
else do else do
liftIO $ putStrLn $ notGood ret liftIO $ putStrLn $ notGood ret
liftIO $ hFlush stdout liftIO $ hFlush stdout
when ok $ loop symDbReq when ok $ loop symdbreq
---------------------------------------------------------------- ----------------------------------------------------------------
@ -167,12 +169,38 @@ checkStx file = do
---------------------------------------------------------------- ----------------------------------------------------------------
type SymDbReq = Async (Either GhcModError SymbolDb, GhcModLog) type SymDbReqAction = (Either GhcModError SymbolDb, GhcModLog)
data SymDbReq = SymDbReq (IORef (Async SymDbReqAction)) (IO SymDbReqAction)
findSym :: IOish m => String -> SymDbReq newSymDbReq :: Options -> IO SymDbReq
-> GhcModT m (String, Bool) newSymDbReq opt = do
findSym sym dbReq = do let act = runGhcModT opt loadSymbolDb
db <- hoistGhcModT =<< liftIO (wait dbReq) req <- async act
ref <- newIORef req
return $ SymDbReq ref act
getDb :: IOish m => SymDbReq -> GhcModT m SymbolDb
getDb (SymDbReq ref _) = do
req <- liftIO $ readIORef ref
-- 'wait' really waits for the asynchronous action at the fist time.
-- Then it reads a cached value from the second time.
hoistGhcModT =<< liftIO (wait req)
checkDb :: IOish m => SymDbReq -> SymbolDb -> GhcModT m SymbolDb
checkDb (SymDbReq ref act) db = do
outdated <- liftIO $ isOutdated db
if outdated then do
-- async and wait here is unnecessary because this is essentially
-- synchronous. But Async can be used a cache.
req <- liftIO $ async act
liftIO $ writeIORef ref req
hoistGhcModT =<< liftIO (wait req)
else
return db
findSym :: IOish m => Symbol -> SymDbReq -> GhcModT m (String, Bool)
findSym sym symdbreq = do
db <- getDb symdbreq >>= checkDb symdbreq
ret <- lookupSymbol sym db ret <- lookupSymbol sym db
return (ret, True) return (ret, True)