Merge branch 'master' into release

This commit is contained in:
Daniel Gröber 2014-10-03 21:41:44 +02:00
commit e3d4303ea8
16 changed files with 900 additions and 399 deletions

View File

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

View File

@ -31,7 +31,7 @@ import qualified Distribution.PackageDescription as P
import Distribution.PackageDescription.Configuration (finalizePackageDescription)
import Distribution.PackageDescription.Parse (readPackageDescription)
import Distribution.Simple.Compiler (CompilerId(..), CompilerFlavor(..))
import Distribution.Simple.Program (ghcProgram)
import Distribution.Simple.Program as C (ghcProgram)
import Distribution.Simple.Program.Types (programName, programFindVersion)
import Distribution.System (buildPlatform)
import Distribution.Text (display)
@ -155,7 +155,7 @@ getGHCId = CompilerId GHC <$> getGHC
getGHC :: IO Version
getGHC = do
mv <- programFindVersion ghcProgram silent (programName ghcProgram)
mv <- programFindVersion C.ghcProgram silent (programName C.ghcProgram)
case mv of
-- TODO: MonadError it up
Nothing -> E.throwIO $ userError "ghc not found"

View File

@ -1,4 +1,4 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE RecordWildCards, CPP #-}
-- | This module facilitates extracting information from Cabal's on-disk
-- 'LocalBuildInfo' (@dist/setup-config@).
@ -6,13 +6,17 @@ module Language.Haskell.GhcMod.CabalConfig (
CabalConfig
, cabalConfigDependencies
, cabalConfigFlags
, setupConfigFile
, World
, getCurrentWorld
, isWorldChanged
) where
import Language.Haskell.GhcMod.Error
import Language.Haskell.GhcMod.GhcPkg
import Language.Haskell.GhcMod.Utils
import Language.Haskell.GhcMod.Read
import Language.Haskell.GhcMod.Types
import Language.Haskell.GhcMod.Utils
import qualified Language.Haskell.GhcMod.Cabal16 as C16
import qualified Language.Haskell.GhcMod.Cabal18 as C18
@ -23,7 +27,7 @@ import qualified Language.Haskell.GhcMod.Cabal21 as C21
#endif
import Control.Applicative ((<$>))
import Control.Monad (mplus)
import Control.Monad (unless, void, mplus)
#if MIN_VERSION_mtl(2,2,1)
import Control.Monad.Except ()
#else
@ -39,9 +43,17 @@ import Distribution.PackageDescription (FlagAssignment)
import Distribution.Simple.BuildPaths (defaultDistPref)
import Distribution.Simple.Configure (localBuildInfoFile)
import Distribution.Simple.LocalBuildInfo (ComponentName)
import Data.Traversable (traverse)
import MonadUtils (liftIO)
import System.Directory (doesFileExist, getModificationTime)
import System.FilePath ((</>))
#if __GLASGOW_HASKELL__ <= 704
import System.Time (ClockTime)
#else
import Data.Time (UTCTime)
#endif
----------------------------------------------------------------
-- | 'Show'ed cabal 'LocalBuildInfo' string
@ -53,20 +65,26 @@ type CabalConfig = String
getConfig :: (IOish m, MonadError GhcModError m)
=> Cradle
-> m CabalConfig
getConfig cradle = liftIO (readFile path) `tryFix` \_ ->
configure `modifyError'` GMECabalConfigure
getConfig cradle = do
world <- liftIO $ getCurrentWorld cradle
let valid = isSetupConfigValid world
unless valid configure
liftIO (readFile file) `tryFix` \_ ->
configure `modifyError'` GMECabalConfigure
where
file = setupConfigFile cradle
prjDir = cradleRootDir cradle
path = prjDir </> configPath
configure :: (IOish m, MonadError GhcModError m) => m ()
configure =
withDirectory_ prjDir $ readProcess' "cabal" ["configure"] >> return ()
configure = withDirectory_ prjDir $ void $ readProcess' "cabal" ["configure"]
setupConfigFile :: Cradle -> FilePath
setupConfigFile crdl = cradleRootDir crdl </> setupConfigPath
-- | Path to 'LocalBuildInfo' file, usually @dist/setup-config@
configPath :: FilePath
configPath = localBuildInfoFile defaultDistPref
setupConfigPath :: FilePath
setupConfigPath = localBuildInfoFile defaultDistPref
-- | Get list of 'Package's needed by all components of the current package
cabalConfigDependencies :: (IOish m, MonadError GhcModError m)
@ -175,3 +193,57 @@ extractField config field =
case extractParens <$> find (field `isPrefixOf`) (tails config) of
Just f -> Right f
Nothing -> Left $ "extractField: failed extracting "++field++" from input, input contained `"++field++"'? " ++ show (field `isInfixOf` config)
----------------------------------------------------------------
#if __GLASGOW_HASKELL__ <= 704
type ModTime = ClockTime
#else
type ModTime = UTCTime
#endif
data World = World {
worldCabalFile :: Maybe FilePath
, worldCabalFileModificationTime :: Maybe ModTime
, worldPackageCache :: FilePath
, worldPackageCacheModificationTime :: ModTime
, worldSetupConfig :: FilePath
, worldSetupConfigModificationTime :: Maybe ModTime
} deriving (Show, Eq)
getCurrentWorld :: Cradle -> IO World
getCurrentWorld crdl = do
cachePath <- getPackageCachePath crdl
let mCabalFile = cradleCabalFile crdl
pkgCache = cachePath </> packageCache
setupFile = setupConfigFile crdl
mCabalFileMTime <- getModificationTime `traverse` mCabalFile
pkgCacheMTime <- getModificationTime pkgCache
exist <- doesFileExist setupFile
mSeetupMTime <- if exist then
Just <$> getModificationTime setupFile
else
return Nothing
return $ World {
worldCabalFile = mCabalFile
, worldCabalFileModificationTime = mCabalFileMTime
, worldPackageCache = pkgCache
, worldPackageCacheModificationTime = pkgCacheMTime
, worldSetupConfig = setupFile
, worldSetupConfigModificationTime = mSeetupMTime
}
isWorldChanged :: World -> Cradle -> IO Bool
isWorldChanged world crdl = do
world' <- getCurrentWorld crdl
return (world /= world')
isSetupConfigValid :: World -> Bool
isSetupConfigValid World{ worldSetupConfigModificationTime = Nothing, ..} = False
isSetupConfigValid World{ worldSetupConfigModificationTime = Just mt, ..} =
cond1 && cond2
where
cond1 = case worldCabalFileModificationTime of
Nothing -> True
Just mtime -> mtime <= mt
cond2 = worldPackageCacheModificationTime <= mt

View File

@ -8,9 +8,8 @@ module Language.Haskell.GhcMod.Check (
import Control.Applicative ((<$>))
import Language.Haskell.GhcMod.DynFlags
import qualified Language.Haskell.GhcMod.Gap as Gap
import qualified GHC as G
import Language.Haskell.GhcMod.Logger
import Language.Haskell.GhcMod.Monad (IOish, GhcModT, overrideGhcUserOptions)
import Language.Haskell.GhcMod.Monad (IOish, GhcModT)
import Language.Haskell.GhcMod.Target (setTargetFiles)
----------------------------------------------------------------
@ -30,10 +29,15 @@ checkSyntax files = either id id <$> check files
check :: IOish m
=> [FilePath] -- ^ The target files.
-> GhcModT m (Either String String)
{-
check fileNames = overrideGhcUserOptions $ \ghcOpts -> do
withLogger (setAllWarningFlags . setNoMaxRelevantBindings . Gap.setWarnTypedHoles . Gap.setDeferTypeErrors) $ do
_ <- G.setSessionDynFlags =<< addCmdOpts ghcOpts =<< G.getSessionDynFlags
setTargetFiles fileNames
-}
check fileNames =
withLogger (setAllWarningFlags . setNoMaxRelevantBindings) $
setTargetFiles fileNames
----------------------------------------------------------------

View File

@ -1,6 +1,7 @@
{-# LANGUAGE TypeFamilies, ScopedTypeVariables #-}
module Language.Haskell.GhcMod.Error (
GhcModError(..)
, gmeDoc
, modifyError
, modifyError'
, tryFix
@ -10,6 +11,7 @@ module Language.Haskell.GhcMod.Error (
import Control.Monad.Error (MonadError(..), Error(..))
import Exception
import Text.PrettyPrint
data GhcModError = GMENoMsg
-- ^ Unknown error
@ -29,6 +31,20 @@ instance Error GhcModError where
noMsg = GMENoMsg
strMsg = GMEString
gmeDoc :: GhcModError -> Doc
gmeDoc e = case e of
GMENoMsg ->
text "Unknown error"
GMEString msg ->
text msg
GMECabalConfigure msg ->
text "cabal configure failed: " <> gmeDoc msg
GMECabalFlags msg ->
text "retrieval of the cabal configuration flags failed: " <> gmeDoc msg
GMEProcess cmd msg ->
text ("launching operating system process `"++unwords cmd++"` failed: ")
<> gmeDoc msg
modifyError :: MonadError e m => (e -> e) -> m a -> m a
modifyError f action = action `catchError` \e -> throwError $ f e

View File

@ -1,4 +1,4 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE CPP, BangPatterns #-}
module Language.Haskell.GhcMod.Find
#ifndef SPEC
@ -10,30 +10,27 @@ module Language.Haskell.GhcMod.Find
, dumpSymbol
, findSymbol
, lookupSym
, isOutdated
)
#endif
where
import Config (cProjectVersion,cTargetPlatformString)
import Control.Applicative ((<$>))
import Control.Monad (when, void)
import Control.Monad.Error.Class
import Data.Function (on)
import Data.List (groupBy, sort)
import Data.List.Split (splitOn)
import Data.Maybe (fromMaybe)
import DynFlags (DynFlags(..), systemPackageConfig)
import Exception (handleIO)
import qualified GHC as G
import Language.Haskell.GhcMod.Convert
import Language.Haskell.GhcMod.GhcPkg
import Language.Haskell.GhcMod.Monad
import Language.Haskell.GhcMod.Utils
import Language.Haskell.GhcMod.Types
import Language.Haskell.GhcMod.Utils
import Name (getOccString)
import System.Directory (doesDirectoryExist, getAppUserDataDirectory, doesFileExist, getModificationTime)
import System.Directory (doesFileExist, getModificationTime)
import System.FilePath ((</>), takeDirectory)
import System.IO
import System.Environment
#ifndef MIN_VERSION_containers
#define MIN_VERSION_containers(x,y,z) 1
@ -52,8 +49,14 @@ import qualified Data.Map as M
-- | Type of function and operation names.
type Symbol = String
-- | Database from 'Symbol' to \['ModuleString'\].
newtype SymbolDb = SymbolDb (Map Symbol [ModuleString])
deriving (Show)
data SymbolDb = SymbolDb {
table :: Map Symbol [ModuleString]
, packageCachePath :: FilePath
, symbolDbCachePath :: FilePath
} deriving (Show)
isOutdated :: SymbolDb -> IO Bool
isOutdated db = symbolDbCachePath db `isOlderThan` packageCachePath db
----------------------------------------------------------------
@ -66,12 +69,6 @@ symbolCacheVersion = 0
symbolCache :: String
symbolCache = "ghc-mod-"++ show symbolCacheVersion ++".cache"
packageCache :: String
packageCache = "package.cache"
packageConfDir :: String
packageConfDir = "package.conf.d"
----------------------------------------------------------------
-- | Looking up 'SymbolDb' with 'Symbol' to \['ModuleString'\]
@ -85,39 +82,21 @@ lookupSymbol :: IOish m => Symbol -> SymbolDb -> GhcModT m String
lookupSymbol sym db = convert' $ lookupSym sym db
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'.
loadSymbolDb :: (IOish m, MonadError GhcModError m) => m SymbolDb
loadSymbolDb = SymbolDb <$> readSymbolDb
-- | Returns the path to the currently running ghc-mod executable. With ghc<7.6
-- this is a guess but >=7.6 uses 'getExecutablePath'.
ghcModExecutable :: IO FilePath
#ifndef SPEC
ghcModExecutable = do
dir <- getExecutablePath'
return $ dir </> "ghc-mod"
#else
ghcModExecutable = do _ <- getExecutablePath' -- get rid of unused warning when
-- compiling spec
return "dist/build/ghc-mod/ghc-mod"
#endif
where
getExecutablePath' :: IO FilePath
# if __GLASGOW_HASKELL__ >= 706
getExecutablePath' = takeDirectory <$> getExecutablePath
# else
getExecutablePath' = return ""
# endif
readSymbolDb :: (IOish m, MonadError GhcModError m) => m (Map Symbol [ModuleString])
readSymbolDb = do
loadSymbolDb = do
ghcMod <- liftIO ghcModExecutable
file <- chop <$> readProcess' ghcMod ["dumpsym"]
M.fromAscList . map conv . lines <$> liftIO (readFile file)
!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
@ -127,24 +106,18 @@ readSymbolDb = do
----------------------------------------------------------------
-- used 'ghc-mod dumpsym'
getSymbolCachePath :: IOish m => GhcModT m FilePath
getSymbolCachePath = do
u:_ <- filter (/= GlobalDb) . cradlePkgDbStack <$> cradle
Just db <- (liftIO . flip resolvePackageDb u) =<< G.getSessionDynFlags
return db
`catchError` const (fail "Couldn't find non-global package database for symbol cache")
-- | Dumping a set of ('Symbol',\['ModuleString'\]) to a file
-- if the file does not exist or is invalid.
-- The file name is printed.
dumpSymbol :: IOish m => GhcModT m String
dumpSymbol = do
dir <- getSymbolCachePath
crdl <- cradle
dir <- liftIO $ getPackageCachePath crdl
let cache = dir </> symbolCache
pkgdb = dir </> packageCache
create <- liftIO $ cache `isNewerThan` pkgdb
create <- liftIO $ cache `isOlderThan` pkgdb
when create $ (liftIO . writeSymbolCache cache) =<< getSymbolTable
return $ unlines [cache]
@ -155,15 +128,15 @@ writeSymbolCache cache sm =
void . withFile cache WriteMode $ \hdl ->
mapM (hPrint hdl) sm
isNewerThan :: FilePath -> FilePath -> IO Bool
isNewerThan ref file = do
exist <- doesFileExist ref
isOlderThan :: FilePath -> FilePath -> IO Bool
isOlderThan cache file = do
exist <- doesFileExist cache
if not exist then
return True
else do
tRef <- getModificationTime ref
tCache <- getModificationTime cache
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.
getSymbolTable :: IOish m => GhcModT m [(Symbol,[ModuleString])]
@ -192,16 +165,3 @@ collectModules :: [(Symbol,ModuleString)]
collectModules = map tieup . groupBy ((==) `on` fst) . sort
where
tieup x = (head (map fst x), map snd x)
--- Copied from ghc module `Packages' unfortunately it's not exported :/
resolvePackageDb :: DynFlags -> GhcPkgDb -> IO (Maybe FilePath)
resolvePackageDb df GlobalDb = return $ Just (systemPackageConfig df)
resolvePackageDb _ (PackageDb name) = return $ Just name
resolvePackageDb _ UserDb = handleIO (\_ -> return Nothing) $ do
appdir <- getAppUserDataDirectory "ghc"
let dir = appdir </> (target_arch ++ '-':target_os ++ '-':cProjectVersion)
pkgconf = dir </> packageConfDir
exist <- doesDirectoryExist pkgconf
return $ if exist then Just pkgconf else Nothing
where
[target_arch,_,target_os] = splitOn "-" cTargetPlatformString

View File

@ -8,9 +8,12 @@ module Language.Haskell.GhcMod.GhcPkg (
, fromInstalledPackageId'
, getSandboxDb
, getPackageDbStack
, getPackageCachePath
, packageCache
, packageConfDir
) where
import Config (cProjectVersionInt)
import Config (cProjectVersion, cTargetPlatformString, cProjectVersionInt)
import Control.Applicative ((<$>))
import Control.Exception (SomeException(..))
import qualified Control.Exception as E
@ -18,8 +21,10 @@ import Data.Char (isSpace)
import Data.List (isPrefixOf, intercalate)
import Data.List.Split (splitOn)
import Distribution.Package (InstalledPackageId(..))
import Exception (handleIO)
import Language.Haskell.GhcMod.Types
import Language.Haskell.GhcMod.Utils
import System.Directory (doesDirectoryExist, getAppUserDataDirectory)
import System.FilePath ((</>))
ghcVersion :: Int
@ -46,6 +51,8 @@ getSandboxDbDir sconf = do
parse = head . filter (key `isPrefixOf`) . lines
extractValue = dropWhileEnd isSpace . dropWhile isSpace . drop keyLen
----------------------------------------------------------------
getPackageDbStack :: FilePath -- ^ Project Directory (where the
-- cabal.sandbox.config file would be if it
-- exists)
@ -54,6 +61,8 @@ getPackageDbStack cdir =
(getSandboxDb cdir >>= \db -> return [GlobalDb, PackageDb db])
`E.catch` \(_ :: SomeException) -> return [GlobalDb, UserDb]
----------------------------------------------------------------
fromInstalledPackageId' :: InstalledPackageId -> Maybe Package
fromInstalledPackageId' pid = let
InstalledPackageId pkg = pid
@ -68,6 +77,8 @@ fromInstalledPackageId pid =
Nothing -> error $
"fromInstalledPackageId: `"++show pid++"' is not a valid package-id"
----------------------------------------------------------------
-- | Get options needed to add a list of package dbs to ghc-pkg's db stack
ghcPkgDbStackOpts :: [GhcPkgDb] -- ^ Package db stack
-> [String]
@ -78,6 +89,8 @@ ghcDbStackOpts :: [GhcPkgDb] -- ^ Package db stack
-> [String]
ghcDbStackOpts dbs = ghcDbOpt `concatMap` dbs
----------------------------------------------------------------
ghcPkgDbOpt :: GhcPkgDb -> [String]
ghcPkgDbOpt GlobalDb = ["--global"]
ghcPkgDbOpt UserDb = ["--user"]
@ -95,3 +108,31 @@ ghcDbOpt UserDb
ghcDbOpt (PackageDb pkgDb)
| ghcVersion < 706 = ["-no-user-package-conf", "-package-conf", pkgDb]
| otherwise = ["-no-user-package-db", "-package-db", pkgDb]
----------------------------------------------------------------
packageCache :: String
packageCache = "package.cache"
packageConfDir :: String
packageConfDir = "package.conf.d"
-- fixme: error handling
getPackageCachePath :: Cradle -> IO FilePath
getPackageCachePath crdl = do
let u:_ = filter (/= GlobalDb) $ cradlePkgDbStack crdl
Just db <- resolvePath u
return db
--- Copied from ghc module `Packages' unfortunately it's not exported :/
resolvePath :: GhcPkgDb -> IO (Maybe FilePath)
resolvePath (PackageDb name) = return $ Just name
resolvePath UserDb = handleIO (\_ -> return Nothing) $ do
appdir <- getAppUserDataDirectory "ghc"
let dir = appdir </> (target_arch ++ '-':target_os ++ '-':cProjectVersion)
pkgconf = dir </> packageConfDir
exist <- doesDirectoryExist pkgconf
return $ if exist then Just pkgconf else Nothing
where
[target_arch,_,target_os] = splitOn "-" cTargetPlatformString
resolvePath _ = error "GlobalDb cannot be used in resolvePath"

View File

@ -16,8 +16,9 @@ module Language.Haskell.GhcMod.Internal (
, cabalDependPackages
, cabalSourceDirs
, cabalAllTargets
-- * GHC.Paths
-- * Various Paths
, ghcLibDir
, ghcModExecutable
-- * IO
, getDynamicFlags
-- * Targets
@ -42,21 +43,30 @@ module Language.Haskell.GhcMod.Internal (
, getCompilerMode
, setCompilerMode
, withOptions
-- * 'GhcModError'
, gmeDoc
-- * 'GhcMonad' Choice
, (||>)
, goNext
, runAnyOne
-- * World
, World
, getCurrentWorld
, isWorldChanged
) where
import GHC.Paths (libdir)
import Language.Haskell.GhcMod.CabalApi
import Language.Haskell.GhcMod.CabalConfig
import Language.Haskell.GhcMod.DynFlags
import Language.Haskell.GhcMod.Error
import Language.Haskell.GhcMod.GHCChoice
import Language.Haskell.GhcMod.Logger
import Language.Haskell.GhcMod.Monad
import Language.Haskell.GhcMod.Target
import Language.Haskell.GhcMod.Types
import Language.Haskell.GhcMod.Utils
-- | Obtaining the directory for ghc system libraries.
ghcLibDir :: FilePath

View File

@ -200,11 +200,11 @@ initializeFlagsWithCradle opt c
| cabal = withCabal
| otherwise = withSandbox
where
mCradleFile = cradleCabalFile c
cabal = isJust mCradleFile
mCabalFile = cradleCabalFile c
cabal = isJust mCabalFile
ghcopts = ghcUserOptions opt
withCabal = do
pkgDesc <- parseCabalFile c $ fromJust mCradleFile
pkgDesc <- parseCabalFile c $ fromJust mCabalFile
compOpts <- getCompilerOptions ghcopts c pkgDesc
initSession CabalPkg opt compOpts
withSandbox = initSession SingleFile opt compOpts

View File

@ -25,7 +25,12 @@ newtype LineSeparator = LineSeparator String
data Options = Options {
outputStyle :: OutputStyle
, hlintOpts :: [String]
-- | Line separator string.
, lineSeparator :: LineSeparator
-- | @ghc@ program name.
, ghcProgram :: FilePath
-- | @cabal@ program name.
, cabalProgram :: FilePath
-- | GHC command line options set on the @ghc-mod@ command line
, ghcUserOptions:: [GHCOption]
-- | If 'True', 'browse' also returns operators.
@ -34,15 +39,17 @@ data Options = Options {
, detailed :: Bool
-- | If 'True', 'browse' will return fully qualified name
, qualified :: Bool
-- | Line separator string.
, lineSeparator :: LineSeparator
, hlintOpts :: [String]
}
-- | A default 'Options'.
defaultOptions :: Options
defaultOptions = Options {
outputStyle = PlainStyle
, hlintOpts = []
, ghcProgram = "ghc"
, cabalProgram = "cabal"
, ghcUserOptions= []
, operators = False
, detailed = False

View File

@ -1,11 +1,17 @@
{-# LANGUAGE CPP #-}
module Language.Haskell.GhcMod.Utils where
import Control.Applicative ((<$>))
import Language.Haskell.GhcMod.Error
import MonadUtils (MonadIO, liftIO)
import System.Directory (getCurrentDirectory, setCurrentDirectory)
import System.Exit (ExitCode(..))
import System.Process (readProcessWithExitCode)
import System.FilePath (takeDirectory)
import System.Environment
#ifndef SPEC
import System.FilePath ((</>))
#endif
-- dropWhileEnd is not provided prior to base 4.5.0.0.
dropWhileEnd :: (a -> Bool) -> [a] -> [a]
@ -42,3 +48,23 @@ withDirectory_ :: (MonadIO m, ExceptionMonad m) => FilePath -> m a -> m a
withDirectory_ dir action =
gbracket (liftIO getCurrentDirectory) (liftIO . setCurrentDirectory)
(\_ -> liftIO (setCurrentDirectory dir) >> action)
-- | Returns the path to the currently running ghc-mod executable. With ghc<7.6
-- this is a guess but >=7.6 uses 'getExecutablePath'.
ghcModExecutable :: IO FilePath
#ifndef SPEC
ghcModExecutable = do
dir <- getExecutablePath'
return $ dir </> "ghc-mod"
#else
ghcModExecutable = do _ <- getExecutablePath' -- get rid of unused warning when
-- compiling spec
return "dist/build/ghc-mod/ghc-mod"
#endif
where
getExecutablePath' :: IO FilePath
# if __GLASGOW_HASKELL__ >= 706
getExecutablePath' = takeDirectory <$> getExecutablePath
# else
getExecutablePath' = return ""
# endif

View File

@ -125,8 +125,8 @@
(ghc-display nil
(lambda ()
(insert "Possible completions:\n")
(mapc
(lambda (x)
(mapc
(lambda (x)
(let* (; (ins1 (insert "- "))
(pos-begin (point))
(ins (insert x))

View File

@ -86,8 +86,8 @@ Library
Language.Haskell.GhcMod.Lang
Language.Haskell.GhcMod.Lint
Language.Haskell.GhcMod.Logger
Language.Haskell.GhcMod.Monad
Language.Haskell.GhcMod.Modules
Language.Haskell.GhcMod.Monad
Language.Haskell.GhcMod.PkgDoc
Language.Haskell.GhcMod.Read
Language.Haskell.GhcMod.SrcUtils
@ -106,6 +106,7 @@ Library
, io-choice
, monad-journal >= 0.4
, old-time
, pretty
, process
, syb
, time
@ -134,8 +135,11 @@ Executable ghc-mod
Default-Extensions: ConstraintKinds, FlexibleContexts
HS-Source-Dirs: src
Build-Depends: base >= 4.0 && < 5
, data-default
, directory
, filepath
, pretty
, process
, mtl >= 2.0
, ghc
, ghc-mod
@ -144,8 +148,11 @@ Executable ghc-modi
Default-Language: Haskell2010
Main-Is: GHCModi.hs
Other-Modules: Paths_ghc_mod
Misc
Utils
GHC-Options: -Wall -threaded
if os(windows)
Cpp-Options: -DWINDOWS
Default-Extensions: ConstraintKinds, FlexibleContexts
HS-Source-Dirs: src
Build-Depends: base >= 4.0 && < 5
@ -153,7 +160,10 @@ Executable ghc-modi
, containers
, directory
, filepath
, old-time
, process
, split
, time
, ghc
, ghc-mod
@ -199,6 +209,7 @@ Test-Suite spec
, io-choice
, monad-journal >= 0.4
, old-time
, pretty
, process
, syb
, time

View File

@ -3,187 +3,451 @@
module Main where
import Config (cProjectVersion)
import Control.Applicative ((<$>))
import Control.Exception (Exception, Handler(..), ErrorCall(..))
import CoreMonad (liftIO)
import qualified Control.Exception as E
import Control.Arrow
import Control.Applicative
import Control.Exception (Exception, Handler(..), catches, throw)
import Data.Typeable (Typeable)
import Data.Version (showVersion)
import Data.Default
import Data.List
import Data.Char (isSpace)
import Language.Haskell.GhcMod
import Language.Haskell.GhcMod.Internal
import Paths_ghc_mod
import System.Console.GetOpt (OptDescr(..), ArgDescr(..), ArgOrder(..))
import qualified System.Console.GetOpt as O
import System.Directory (doesFileExist)
import System.Environment (getArgs)
import System.Exit (exitFailure)
import System.IO (hPutStr, hPutStrLn, stdout, stderr, hSetEncoding, utf8)
import System.IO (hPutStrLn, stdout, stderr, hSetEncoding, utf8)
--import System.Process (rawSystem)
--import System.Exit (exitWith)
import Text.PrettyPrint
----------------------------------------------------------------
progVersion :: String
progVersion = "ghc-mod version " ++ showVersion version ++ " compiled by GHC " ++ cProjectVersion ++ "\n"
progVersion =
"ghc-mod version " ++ showVersion version ++ " compiled by GHC "
++ cProjectVersion ++ "\n"
ghcOptHelp :: String
ghcOptHelp = " [-g GHC_opt1 -g GHC_opt2 ...] "
optionUsage :: (String -> String) -> [OptDescr a] -> [String]
optionUsage indent opts = concatMap optUsage opts
where
optUsage (Option so lo dsc udsc) =
[ concat $ intersperse ", " $ addLabel `map` allFlags
, indent $ udsc
, ""
]
where
allFlags = shortFlags ++ longFlags
shortFlags = (('-':) . return) `map` so :: [String]
longFlags = ("--"++) `map` lo
addLabel f@('-':'-':_) = f ++ flagLabel "="
addLabel f@('-':_) = f ++ flagLabel " "
addLabel _ = undefined
flagLabel s =
case dsc of
NoArg _ -> ""
ReqArg _ label -> s ++ label
OptArg _ label -> s ++ "["++label++"]"
-- TODO: Generate the stuff below automatically
usage :: String
usage = progVersion
++ "Usage:\n"
++ "\t ghc-mod list " ++ ghcOptHelp ++ "[-l] [-d]\n"
++ "\t ghc-mod lang [-l]\n"
++ "\t ghc-mod flag [-l]\n"
++ "\t ghc-mod browse " ++ ghcOptHelp ++ "[-l] [-o] [-d] [-q] [<package>:]<module> [[<package>:]<module> ...]\n"
++ "\t ghc-mod check " ++ ghcOptHelp ++ "<HaskellFiles...>\n"
++ "\t ghc-mod expand " ++ ghcOptHelp ++ "<HaskellFiles...>\n"
++ "\t ghc-mod debug " ++ ghcOptHelp ++ "\n"
++ "\t ghc-mod info " ++ ghcOptHelp ++ "<HaskellFile> <module> <expression>\n"
++ "\t ghc-mod type " ++ ghcOptHelp ++ "<HaskellFile> <module> <line-no> <column-no>\n"
++ "\t ghc-mod split " ++ ghcOptHelp ++ "<HaskellFile> <module> <line-no> <column-no>\n"
++ "\t ghc-mod sig " ++ ghcOptHelp ++ "<HaskellFile> <module> <line-no> <column-no>\n"
++ "\t ghc-mod refine " ++ ghcOptHelp ++ "<HaskellFile> <module> <line-no> <column-no> <expression>\n"
++ "\t ghc-mod auto " ++ ghcOptHelp ++ "<HaskellFile> <module> <line-no> <column-no>\n"
++ "\t ghc-mod find <symbol>\n"
++ "\t ghc-mod lint [-h opt] <HaskellFile>\n"
++ "\t ghc-mod root\n"
++ "\t ghc-mod doc <module>\n"
++ "\t ghc-mod boot\n"
++ "\t ghc-mod version\n"
++ "\t ghc-mod help\n"
++ "\n"
++ "<module> for \"info\" and \"type\" is not used, anything is OK.\n"
++ "It is necessary to maintain backward compatibility.\n"
usage =
"Usage: ghc-mod [OPTIONS...] COMMAND [OPTIONS...] \n\
\*Global Options (OPTIONS)*\n\
\ Global options can be specified before and after the command and\n\
\ interspersed with command specific options\n\
\\n"
++ (unlines $ indent <$> optionUsage indent globalArgSpec) ++
"*Commands*\n\
\ - version\n\
\ Print the version of the program.\n\
\\n\
\ - help | --help\n\
\ Print this help message.\n\
\\n\
\ - list [FLAGS...]\n\
\ List all visible modules.\n\
\ Flags:\n\
\ -d\n\
\ Also print the modules' package.\n\
\\n\
\ - lang\n\
\ List all known GHC language extensions.\n\
\\n\
\ - flag\n\
\ List GHC -f<bla> flags.\n\
\\n\
\ - browse [FLAGS...] [PACKAGE:]MODULE...\n\
\ List symbols in a module.\n\
\ Flags:\n\
\ -o\n\
\ Also print operators.\n\
\ -d\n\
\ Print symbols with accompanying signatures.\n\
\ -q\n\
\ Qualify symbols.\n\
\\n\
\ - check FILE...\n\
\ Load the given files using GHC and report errors/warnings, but\n\
\ don't produce output files.\n\
\\n\
\ - expand FILE...\n\
\ Like `check' but also pass `-ddump-splices' to GHC.\n\
\\n\
\ - info FILE [MODULE] EXPR\n\
\ Look up an identifier in the context of FILE (like ghci's `:info')\n\
\ MODULE is completely ignored and only allowed for backwards\n\
\ compatibility.\n\
\\n\
\ - type FILE [MODULE] LINE COL\n\
\ Get the type of the expression under (LINE,COL).\n\
\\n\
\ - split FILE [MODULE] LINE COL\n\
\ Split a function case by examining a type's constructors.\n\
\\n\
\ For example given the following code snippet:\n\
\\n\
\ f :: [a] -> a\n\
\ f x = _body\n\
\\n\
\ would be replaced by:\n\
\\n\
\ f :: [a] -> a\n\
\ f [] = _body\n\
\ f (x:xs) = _body\n\
\\n\
\ (See https://github.com/kazu-yamamoto/ghc-mod/pull/274)\n\
\\n\
\ - sig FILE MODULE LINE COL\n\
\ Generate initial code given a signature.\n\
\\n\
\ For example when (LINE,COL) is on the signature in the following\n\
\ code snippet:\n\
\\n\
\ func :: [a] -> Maybe b -> (a -> b) -> (a,b)\n\
\\n\
\ ghc-mod would add the following on the next line:\n\
\\n\
\ func x y z f = _func_body\n\
\\n\
\ (See: https://github.com/kazu-yamamoto/ghc-mod/pull/274)\n\
\\n\
\ - refine FILE MODULE LINE COL EXPR\n\
\ Refine the typed hole at (LINE,COL) given EXPR.\n\
\\n\
\ For example if EXPR is `filter', which has type `(a -> Bool) -> [a]\n\
\ -> [a]' and (LINE,COL) is on the hole `_body' in the following\n\
\ code snippet:\n\
\\n\
\ filterNothing :: [Maybe a] -> [a]\n\
\ filterNothing xs = _body\n\
\\n\
\ ghc-mod changes the code to get a value of type `[a]', which\n\
\ results in:\n\
\\n\
\ filterNothing xs = filter _body_1 _body_2\n\
\\n\
\ (See also: https://github.com/kazu-yamamoto/ghc-mod/issues/311)\n\
\\n\
\ - auto FILE MODULE LINE COL\n\
\ Try to automatically fill the contents of a hole.\n\
\\n\
\ - find SYMBOL\n\
\ List all modules that define SYMBOL.\n\
\\n\
\ - lint FILE\n\
\ Check files using `hlint'.\n\
\ Flags:\n\
\ -l\n\
\ Option to be passed to hlint.\n\
\\n\
\ - root FILE\n\
\ Try to find the project directory given FILE. For Cabal\n\
\ projects this is the directory containing the cabal file, for\n\
\ projects that use a cabal sandbox but have no cabal file this is the\n\
\ directory containing the sandbox and otherwise this is the directory\n\
\ containing FILE.\n\
\\n\
\ - doc MODULE\n\
\ Try finding the html documentation directory for the given MODULE.\n\
\\n\
\ - debug\n\
\ Print debugging information. Please include the output in any bug\n\
\ reports you submit.\n\
\\n\
\ - boot\n\
\ Internal command used by the emacs frontend.\n"
-- "\n\
-- \The following forms are supported so ghc-mod can be invoked by\n\
-- \`cabal repl':\n\
-- \\n\
-- \ ghc-mod --make GHC_OPTIONS\n\
-- \ Pass all options through to the GHC executable.\n\
-- \\n\
-- \ ghc-mod --interactive GHC_OPTIONS [--ghc-mod]\n\
-- \ Start ghci emulation mode. GHC_OPTIONS are passed to the\n\
-- \ GHC API. If `--ghc-mod' is given ghc-mod specific extensions\n\
-- \ are enabled.\n"
where
indent = (" "++)
cmdUsage :: String -> String -> String
cmdUsage cmd s =
let
-- Find command head
a = dropWhile (not . ((" - " ++ cmd) `isInfixOf`)) $ lines s
-- Take til the end of the current command block
b = flip takeWhile a $ \l ->
all isSpace l || (isIndented l && (isCurrCmdHead l || isNotCmdHead l))
-- Drop extra newline from the end
c = dropWhileEnd (all isSpace) b
isIndented = (" " `isPrefixOf`)
isNotCmdHead = ( not . (" - " `isPrefixOf`))
isCurrCmdHead = ((" - " ++ cmd) `isPrefixOf`)
unindent (' ':' ':' ':' ':l) = l
unindent l = l
in unlines $ unindent <$> c
----------------------------------------------------------------
argspec :: [OptDescr (Options -> Options)]
argspec =
let option s l udsc dsc = Option s l dsc udsc
reqArg udsc dsc = ReqArg dsc udsc
in
[ option "l" ["tolisp"] "print as a list of Lisp" $
NoArg $ \o -> o { outputStyle = LispStyle }
option :: [Char] -> [String] -> String -> ArgDescr a -> OptDescr a
option s l udsc dsc = Option s l dsc udsc
, option "h" ["hlintOpt"] "hlint options" $
reqArg "hlintOpt" $ \h o -> o { hlintOpts = h : hlintOpts o }
reqArg :: String -> (String -> a) -> ArgDescr a
reqArg udsc dsc = ReqArg dsc udsc
, option "g" ["ghcOpt"] "GHC options" $
reqArg "ghcOpt" $ \g o ->
o { ghcUserOptions = g : ghcUserOptions o }
, option "v" ["verbose"] "verbose" $
globalArgSpec :: [OptDescr (Options -> Options)]
globalArgSpec =
[ option "v" ["verbose"] "Be more verbose." $
NoArg $ \o -> o { ghcUserOptions = "-v" : ghcUserOptions o }
, option "o" ["operators"] "print operators, too" $
NoArg $ \o -> o { operators = True }
, option "l" ["tolisp"] "Format output as an S-Expression" $
NoArg $ \o -> o { outputStyle = LispStyle }
, option "d" ["detailed"] "print detailed info" $
NoArg $ \o -> o { detailed = True }
, option "b" ["boundary"] "Output line separator"$
reqArg "SEP" $ \s o -> o { lineSeparator = LineSeparator s }
, option "q" ["qualified"] "show qualified names" $
NoArg $ \o -> o { qualified = True }
, option "g" ["ghcOpt"] "Option to be passed to GHC" $
reqArg "OPT" $ \g o ->
o { ghcUserOptions = g : ghcUserOptions o }
, option "b" ["boundary"] "specify line separator (default is Nul string)"$
reqArg "sep" $ \s o -> o { lineSeparator = LineSeparator s }
, option "" ["with-ghc"] "GHC executable to use" $
reqArg "PROG" $ \p o -> o { ghcProgram = p }
, option "" ["with-cabal"] "cabal-install executable to use" $
reqArg "PROG" $ \p o -> o { cabalProgram = p }
]
parseArgs :: [OptDescr (Options -> Options)] -> [String] -> (Options, [String])
parseArgs spec argv
= case O.getOpt Permute spec argv of
(o,n,[] ) -> (foldr id defaultOptions o, n)
(_,_,errs) -> E.throw (CmdArg errs)
parseGlobalArgs ::[String] -> (Options, [String])
parseGlobalArgs argv
= case O.getOpt RequireOrder globalArgSpec argv of
(o,r,[] ) -> (foldr id defaultOptions o, r)
(_,_,errs) ->
fatalError $ "Parsing command line options failed: \n" ++ concat errs
parseCommandArgs :: [OptDescr (Options -> Options)]
-> [String]
-> Options
-> (Options, [String])
parseCommandArgs spec argv opts
= case O.getOpt RequireOrder (globalArgSpec ++ spec) argv of
(o,r,[]) -> (foldr id opts o, r)
(_,_,errs) ->
fatalError $ "Parsing command options failed: \n" ++ concat errs
----------------------------------------------------------------
data GHCModError = SafeList
| ArgumentsMismatch String
| NoSuchCommand String
| CmdArg [String]
| FileNotExist String deriving (Show, Typeable)
data CmdError = UnknownCommand String
| NoSuchFileError String
| LibraryError GhcModError
instance Exception GHCModError
deriving (Show, Typeable)
instance Exception CmdError
----------------------------------------------------------------
data InteractiveOptions = InteractiveOptions {
ghcModExtensions :: Bool
}
instance Default InteractiveOptions where
def = InteractiveOptions False
handler :: IO a -> IO a
handler = flip catches $
[ Handler $ \(FatalError msg) -> exitError msg
, Handler $ \(InvalidCommandLine e) -> do
case e of
Left cmd ->
exitError $ (cmdUsage cmd usage)
++ "\nghc-mod: Invalid command line form."
Right msg -> exitError msg
]
main :: IO ()
main = flip E.catches handlers $ do
main = handler $ do
hSetEncoding stdout utf8
args <- getArgs
let (opt,cmdArg) = parseArgs argspec args
let cmdArg0 = cmdArg !. 0
cmdArg1 = cmdArg !. 1
cmdArg3 = cmdArg !. 3
cmdArg4 = cmdArg !. 4
cmdArg5 = cmdArg !. 5
remainingArgs = tail cmdArg
nArgs :: Int -> a -> a
nArgs n f = if length remainingArgs == n
then f
else E.throw (ArgumentsMismatch cmdArg0)
(res, _) <- runGhcModT opt $ case cmdArg0 of
"list" -> modules
"lang" -> languages
"flag" -> flags
"browse" -> concat <$> mapM browse remainingArgs
"check" -> checkSyntax remainingArgs
"expand" -> expandTemplate remainingArgs
"debug" -> debugInfo
"info" -> nArgs 3 info cmdArg1 cmdArg3
"type" -> nArgs 4 $ types cmdArg1 (read cmdArg3) (read cmdArg4)
"split" -> nArgs 4 $ splits cmdArg1 (read cmdArg3) (read cmdArg4)
"sig" -> nArgs 4 $ sig cmdArg1 (read cmdArg3) (read cmdArg4)
"refine" -> nArgs 5 $ refine cmdArg1 (read cmdArg3) (read cmdArg4) cmdArg5
"auto" -> nArgs 4 $ auto cmdArg1 (read cmdArg3) (read cmdArg4)
"find" -> nArgs 1 $ findSymbol cmdArg1
"lint" -> nArgs 1 $ withFile lint cmdArg1
"root" -> rootInfo
"doc" -> nArgs 1 $ pkgDoc cmdArg1
"dumpsym" -> dumpSymbol
"boot" -> boot
"version" -> return progVersion
"help" -> return $ O.usageInfo usage argspec
cmd -> E.throw (NoSuchCommand cmd)
case res of
Right s -> putStr s
Left (GMENoMsg) ->
hPutStrLn stderr "Unknown error"
Left (GMEString msg) ->
hPutStrLn stderr msg
Left (GMECabalConfigure msg) ->
hPutStrLn stderr $ "cabal configure failed: " ++ show msg
Left (GMECabalFlags msg) ->
hPutStrLn stderr $ "retrieval of the cabal configuration flags failed: " ++ show msg
Left (GMEProcess cmd msg) ->
hPutStrLn stderr $
"launching operating system process `"++c++"` failed: " ++ show msg
where c = unwords cmd
let (ghcArgs, modArgs) = second stripSeperator $ span (/="--") args
_realGhcArgs = filter (/="--ghc-mod") ghcArgs
where
handlers = [Handler (handleThenExit handler1), Handler (handleThenExit handler2)]
handleThenExit handler e = handler e >> exitFailure
handler1 :: ErrorCall -> IO ()
handler1 = print -- for debug
handler2 :: GHCModError -> IO ()
handler2 SafeList = printUsage
handler2 (ArgumentsMismatch cmd) = do
hPutStrLn stderr $ "\"" ++ cmd ++ "\": Arguments did not match"
printUsage
handler2 (NoSuchCommand cmd) = do
hPutStrLn stderr $ "\"" ++ cmd ++ "\" not supported"
printUsage
handler2 (CmdArg errs) = do
mapM_ (hPutStr stderr) errs
printUsage
handler2 (FileNotExist file) = do
hPutStrLn stderr $ "\"" ++ file ++ "\" not found"
printUsage
printUsage = hPutStrLn stderr $ '\n' : O.usageInfo usage argspec
withFile :: IOish m => (FilePath -> GhcModT m a) -> FilePath -> GhcModT m a
withFile cmd file = do
exist <- liftIO $ doesFileExist file
if exist
then cmd file
else E.throw (FileNotExist file)
xs !. idx
| length xs <= idx = E.throw SafeList
| otherwise = xs !! idx
(globalOptions,_cmdArgs) = parseGlobalArgs modArgs
stripSeperator ("--":rest) = rest
stripSeperator l = l
case args of
_
-- | "--numeric-version" `elem` ghcArgs || "--make" `elem` ghcArgs -> do
-- rawSystem (ghcProgram globalOptions) realGhcArgs >>= exitWith
-- | "--interactive" `elem` ghcArgs -> do
-- let interactiveOptions = if "--ghc-mod" `elem` ghcArgs
-- then def { ghcModExtensions = True }
-- else def
-- -- TODO: pass ghcArgs' to ghc API
-- putStrLn "\ninteractive\n"
-- --print realGhcArgs
-- (res, _) <- runGhcModT globalOptions $ undefined
-- case res of
-- Right s -> putStr s
-- Left e -> exitError $ render (gmeDoc e)
| otherwise -> do
(res, _) <- runGhcModT globalOptions $ commands args
case res of
Right s -> putStr s
Left e -> exitError $ render (gmeDoc e)
-- Obtain ghc options by letting ourselfs be executed by
-- @cabal repl@
-- TODO: need to do something about non-cabal projects
-- exe <- ghcModExecutable
-- let cabalArgs = ["repl", "-v0", "--with-ghc="++exe]
-- ++ (("--ghc-option="++) `map` ("--ghc-mod":"--":args))
-- print cabalArgs
-- rawSystem "cabal" cabalArgs >>= exitWith
commands :: IOish m => [String] -> GhcModT m String
commands [] = fatalError "No command given (try --help)\n"
commands (cmd:args) = fn args
where
fn = case cmd of
_ | cmd == "list" || cmd == "modules" -> modulesCmd
_ | cmd == "help" || cmd == "--help" -> const $ return usage
"version" -> const $ return progVersion
"lang" -> languagesCmd
"flag" -> flagsCmd
"browse" -> browseCmd
"check" -> checkSyntaxCmd
"expand" -> expandTemplateCmd
"debug" -> debugInfoCmd
"info" -> infoCmd
"type" -> typesCmd
"split" -> splitsCmd
"sig" -> sigCmd
"refine" -> refineCmd
"auto" -> autoCmd
"find" -> findSymbolCmd
"lint" -> lintCmd
"root" -> rootInfoCmd
"doc" -> pkgDocCmd
"dumpsym" -> dumpSymbolCmd
"boot" -> bootCmd
_ -> fatalError $ "unknown command: `" ++ cmd ++ "'"
newtype FatalError = FatalError String deriving (Show, Typeable)
instance Exception FatalError
newtype InvalidCommandLine = InvalidCommandLine (Either String String)
deriving (Show, Typeable)
instance Exception InvalidCommandLine
exitError :: String -> IO a
exitError msg = hPutStrLn stderr msg >> exitFailure
fatalError :: String -> a
fatalError s = throw $ FatalError $ "ghc-mod: " ++ s
withParseCmd :: IOish m
=> [OptDescr (Options -> Options)]
-> ([String] -> GhcModT m a)
-> [String]
-> GhcModT m a
withParseCmd spec action args = do
(opts', rest) <- parseCommandArgs spec args <$> options
withOptions (const opts') $ action rest
modulesCmd, languagesCmd, flagsCmd, browseCmd, checkSyntaxCmd, expandTemplateCmd,
debugInfoCmd, infoCmd, typesCmd, splitsCmd, sigCmd, refineCmd, autoCmd,
findSymbolCmd, lintCmd, rootInfoCmd, pkgDocCmd, dumpSymbolCmd, bootCmd
:: IOish m => [String] -> GhcModT m String
modulesCmd = withParseCmd [] $ \[] -> modules
languagesCmd = withParseCmd [] $ \[] -> languages
flagsCmd = withParseCmd [] $ \[] -> flags
debugInfoCmd = withParseCmd [] $ \[] -> debugInfo
rootInfoCmd = withParseCmd [] $ \[] -> rootInfo
-- internal
dumpSymbolCmd = withParseCmd [] $ \[] -> dumpSymbol
bootCmd = withParseCmd [] $ \[] -> boot
findSymbolCmd = withParseCmd [] $ \[sym] -> findSymbol sym
pkgDocCmd = withParseCmd [] $ \[mdl] -> pkgDoc mdl
lintCmd = withParseCmd s $ \[file] -> lint file
where s = hlintArgSpec
browseCmd = withParseCmd s $ \mdls -> concat <$> browse `mapM` mdls
where s = browseArgSpec
checkSyntaxCmd = withParseCmd [] $ checkAction checkSyntax
expandTemplateCmd = withParseCmd [] $ checkAction expandTemplate
typesCmd = withParseCmd [] $ locAction "type" types
splitsCmd = withParseCmd [] $ locAction "split" splits
sigCmd = withParseCmd [] $ locAction "sig" sig
autoCmd = withParseCmd [] $ locAction "auto" auto
refineCmd = withParseCmd [] $ locAction' "refine" refine
infoCmd = withParseCmd [] $ action
where action [file,_,expr] = info file expr
action [file,expr] = info file expr
action _ = throw $ InvalidCommandLine (Left "info")
checkAction :: ([t] -> a) -> [t] -> a
checkAction _ [] = throw $ InvalidCommandLine (Right "No files given.")
checkAction action files = action files
locAction :: String -> (String -> Int -> Int -> a) -> [String] -> a
locAction _ action [file,_,line,col] = action file (read line) (read col)
locAction _ action [file, line,col] = action file (read line) (read col)
locAction cmd _ _ = throw $ InvalidCommandLine (Left cmd)
locAction' :: String -> (String -> Int -> Int -> String -> a) -> [String] -> a
locAction' _ action [f,_,line,col,expr] = action f (read line) (read col) expr
locAction' _ action [f, line,col,expr] = action f (read line) (read col) expr
locAction' cmd _ _ = throw $ InvalidCommandLine (Left cmd)
hlintArgSpec :: [OptDescr (Options -> Options)]
hlintArgSpec =
[ option "h" ["hlintOpt"] "Option to be passed to hlint" $
reqArg "hlintOpt" $ \h o -> o { hlintOpts = h : hlintOpts o }
]
browseArgSpec :: [OptDescr (Options -> Options)]
browseArgSpec =
[ option "o" ["operators"] "Also print operators." $
NoArg $ \o -> o { operators = True }
, option "d" ["detailed"] "Print symbols with accompanying signature." $
NoArg $ \o -> o { detailed = True }
, option "q" ["qualified"] "Qualify symbols" $
NoArg $ \o -> o { qualified = True }
]

View File

@ -20,36 +20,27 @@ module Main where
import Config (cProjectVersion)
import Control.Applicative ((<$>))
import Control.Concurrent.Async (Async, async, wait)
import Control.Exception (SomeException(..), Exception)
import Control.Exception (SomeException(..))
import qualified Control.Exception as E
import Control.Monad (when)
import CoreMonad (liftIO)
import Data.List (find, intercalate)
import Data.List (intercalate)
import Data.List.Split (splitOn)
import Data.Maybe (fromMaybe)
import Data.Set (Set)
import qualified Data.Set as S
import Data.Typeable (Typeable)
import Data.Version (showVersion)
import qualified GHC as G
import Language.Haskell.GhcMod
import Language.Haskell.GhcMod.Internal
import Paths_ghc_mod
import System.Console.GetOpt
import System.Directory (setCurrentDirectory)
import System.Environment (getArgs)
import System.IO (hFlush,stdout)
import System.Exit (ExitCode, exitFailure)
import System.IO (hFlush,stdout)
import Misc
import Utils
----------------------------------------------------------------
type Logger = IO String
----------------------------------------------------------------
progVersion :: String
progVersion = "ghc-modi version " ++ showVersion version ++ " compiled by GHC " ++ cProjectVersion ++ "\n"
@ -79,13 +70,6 @@ parseArgs spec argv
----------------------------------------------------------------
data GHCModiError = CmdArg [String]
deriving (Show, Typeable)
instance Exception GHCModiError
----------------------------------------------------------------
-- Running two GHC monad threads disables the handling of
-- C-c since installSignalHandlers is called twice, sigh.
@ -96,25 +80,33 @@ main = E.handle cmdHandler $
cmdHandler (CmdArg _) = putStr $ usageInfo usage argspec
go (_,"help":_) = putStr $ usageInfo usage argspec
go (_,"version":_) = putStr progVersion
go (opt,_) = flip E.catches handlers $ do
cradle0 <- findCradle
let rootdir = cradleRootDir cradle0
-- c = cradle0 { cradleCurrentDir = rootdir } TODO: ?????
setCurrentDirectory rootdir
symDb <- async $ runGhcModT opt loadSymbolDb
(res, _) <- runGhcModT opt $ loop S.empty symDb
go (opt,_) = emptyNewUnGetLine >>= run opt
case res of
Right () -> return ()
Left (GMECabalConfigure msg) -> do
putStrLn $ notGood $ "cabal configure failed: " ++ show msg
exitFailure
Left e -> bug $ show e
where
-- this is just in case.
-- If an error is caught here, it is a bug of GhcMod library.
handlers = [ E.Handler (\(_ :: ExitCode) -> return ())
, E.Handler (\(SomeException e) -> bug $ show e) ]
run :: Options -> UnGetLine -> IO ()
run opt ref = flip E.catches handlers $ do
cradle0 <- findCradle
let rootdir = cradleRootDir cradle0
-- c = cradle0 { cradleCurrentDir = rootdir } TODO: ?????
setCurrentDirectory rootdir
prepareAutogen cradle0
-- Asynchronous db loading starts here.
symdbreq <- newSymDbReq opt
(res, _) <- runGhcModT opt $ do
crdl <- cradle
world <- liftIO $ getCurrentWorld crdl
loop symdbreq ref world
case res of
Right () -> return ()
Left (GMECabalConfigure msg) -> do
putStrLn $ notGood $ "cabal configure failed: " ++ show msg
exitFailure
Left e -> bug $ show e
where
-- this is just in case.
-- If an error is caught here, it is a bug of GhcMod library.
handlers = [ E.Handler (\(_ :: ExitCode) -> return ())
, E.Handler (\(_ :: Restart) -> run opt ref)
, E.Handler (\(SomeException e) -> bug $ show e) ]
bug :: String -> IO ()
bug msg = do
@ -132,91 +124,63 @@ replace needle replacement = intercalate replacement . splitOn needle
----------------------------------------------------------------
loop :: IOish m => Set FilePath -> SymDbReq -> GhcModT m ()
loop set symDbReq = do
cmdArg <- liftIO getLine
loop :: IOish m => SymDbReq -> UnGetLine -> World -> GhcModT m ()
loop symdbreq ref world = do
-- blocking
cmdArg <- liftIO $ getCommand ref
-- after blocking, we need to see if the world has changed.
crdl <- cradle
changed <- liftIO $ isWorldChanged world crdl
when changed $ do
liftIO $ ungetCommand ref cmdArg
E.throw Restart
cradle >>= liftIO . prepareAutogen
let (cmd,arg') = break (== ' ') cmdArg
arg = dropWhile (== ' ') arg'
(ret,ok,set') <- case cmd of
"check" -> checkStx set arg
"find" -> findSym set arg symDbReq
"lint" -> lintStx set arg
"info" -> showInfo set arg
"type" -> showType set arg
"split" -> doSplit set arg
"sig" -> doSig set arg
"refine" -> doRefine set arg
"auto" -> doAuto set arg
"boot" -> bootIt set
"browse" -> browseIt set arg
"quit" -> return ("quit", False, set)
"" -> return ("quit", False, set)
_ -> return ([], True, set)
(ret,ok) <- case cmd of
"check" -> checkStx arg
"find" -> findSym arg symdbreq
"lint" -> lintStx arg
"info" -> showInfo arg
"type" -> showType arg
"split" -> doSplit arg
"sig" -> doSig arg
"refine" -> doRefine arg
"auto" -> doAuto arg
"boot" -> bootIt
"browse" -> browseIt arg
"quit" -> return ("quit", False)
"" -> return ("quit", False)
_ -> return ([], True)
if ok then do
liftIO $ putStr ret
liftIO $ putStrLn "OK"
else do
liftIO $ putStrLn $ notGood ret
liftIO $ hFlush stdout
when ok $ loop set' symDbReq
when ok $ loop symdbreq ref world
----------------------------------------------------------------
checkStx :: IOish m
=> Set FilePath
-> FilePath
-> GhcModT m (String, Bool, Set FilePath)
checkStx set file = do
set' <- newFileSet set file
let files = S.toList set'
eret <- check files
checkStx :: IOish m => FilePath -> GhcModT m (String, Bool)
checkStx file = do
eret <- check [file]
case eret of
Right ret -> return (ret, True, set')
Left ret -> return (ret, True, set) -- fxime: set
newFileSet :: IOish m => Set FilePath -> FilePath -> GhcModT m (Set FilePath)
newFileSet set file = do
let set1
| S.member file set = set
| otherwise = S.insert file set
mx <- isSameMainFile file <$> getModSummaryForMain
return $ case mx of
Nothing -> set1
Just mainfile -> S.delete mainfile set1
getModSummaryForMain :: IOish m => GhcModT m (Maybe G.ModSummary)
getModSummaryForMain = find isMain <$> G.getModuleGraph
where
isMain m = G.moduleNameString (G.moduleName (G.ms_mod m)) == "Main"
isSameMainFile :: FilePath -> (Maybe G.ModSummary) -> Maybe FilePath
isSameMainFile _ Nothing = Nothing
isSameMainFile file (Just x)
| mainfile == file = Nothing
| otherwise = Just mainfile
where
mmainfile = G.ml_hs_file (G.ms_location x)
-- G.ms_hspp_file x is a temporary file with CPP.
-- this is a just fake.
mainfile = fromMaybe (G.ms_hspp_file x) mmainfile
Right ret -> return (ret, True)
Left ret -> return (ret, True)
----------------------------------------------------------------
type SymDbReq = Async (Either GhcModError SymbolDb, GhcModLog)
findSym :: IOish m => Set FilePath -> String -> SymDbReq
-> GhcModT m (String, Bool, Set FilePath)
findSym set sym dbReq = do
db <- hoistGhcModT =<< liftIO (wait dbReq)
findSym :: IOish m => Symbol -> SymDbReq -> GhcModT m (String, Bool)
findSym sym symdbreq = do
db <- getDb symdbreq >>= checkDb symdbreq
ret <- lookupSymbol sym db
return (ret, True, set)
return (ret, True)
lintStx :: IOish m => Set FilePath
-> FilePath
-> GhcModT m (String, Bool, Set FilePath)
lintStx set optFile = do
lintStx :: IOish m => FilePath -> GhcModT m (String, Bool)
lintStx optFile = do
ret <- withOptions changeOpt $ lint file
return (ret, True, set)
return (ret, True)
where
(opts,file) = parseLintOptions optFile
hopts = if opts == "" then [] else read opts
@ -239,85 +203,56 @@ parseLintOptions optFile = case brk (== ']') (dropWhile (/= '[') optFile) of
----------------------------------------------------------------
showInfo :: IOish m
=> Set FilePath
-> FilePath
-> GhcModT m (String, Bool, Set FilePath)
showInfo set fileArg = do
showInfo :: IOish m => FilePath -> GhcModT m (String, Bool)
showInfo fileArg = do
let [file, expr] = splitN 2 fileArg
set' <- newFileSet set file
ret <- info file expr
return (ret, True, set')
return (ret, True)
showType :: IOish m
=> Set FilePath
-> FilePath
-> GhcModT m (String, Bool, Set FilePath)
showType set fileArg = do
showType :: IOish m => FilePath -> GhcModT m (String, Bool)
showType fileArg = do
let [file, line, column] = splitN 3 fileArg
set' <- newFileSet set file
ret <- types file (read line) (read column)
return (ret, True, set')
return (ret, True)
doSplit :: IOish m
=> Set FilePath
-> FilePath
-> GhcModT m (String, Bool, Set FilePath)
doSplit set fileArg = do
doSplit :: IOish m => FilePath -> GhcModT m (String, Bool)
doSplit fileArg = do
let [file, line, column] = splitN 3 fileArg
set' <- newFileSet set file
ret <- splits file (read line) (read column)
return (ret, True, set')
return (ret, True)
doSig :: IOish m
=> Set FilePath
-> FilePath
-> GhcModT m (String, Bool, Set FilePath)
doSig set fileArg = do
doSig :: IOish m => FilePath -> GhcModT m (String, Bool)
doSig fileArg = do
let [file, line, column] = splitN 3 fileArg
set' <- newFileSet set file
ret <- sig file (read line) (read column)
return (ret, True, set')
return (ret, True)
doRefine :: IOish m
=> Set FilePath
-> FilePath
-> GhcModT m (String, Bool, Set FilePath)
doRefine set fileArg = do
doRefine :: IOish m => FilePath -> GhcModT m (String, Bool)
doRefine fileArg = do
let [file, line, column, expr] = splitN 4 fileArg
set' <- newFileSet set file
ret <- refine file (read line) (read column) expr
return (ret, True, set')
return (ret, True)
doAuto :: IOish m
=> Set FilePath
-> FilePath
-> GhcModT m (String, Bool, Set FilePath)
doAuto set fileArg = do
doAuto :: IOish m => FilePath -> GhcModT m (String, Bool)
doAuto fileArg = do
let [file, line, column] = splitN 3 fileArg
set' <- newFileSet set file
ret <- auto file (read line) (read column)
return (ret, True, set')
return (ret, True)
----------------------------------------------------------------
bootIt :: IOish m
=> Set FilePath
-> GhcModT m (String, Bool, Set FilePath)
bootIt set = do
bootIt :: IOish m => GhcModT m (String, Bool)
bootIt = do
ret <- boot
return (ret, True, set)
return (ret, True)
browseIt :: IOish m
=> Set FilePath
-> ModuleString
-> GhcModT m (String, Bool, Set FilePath)
browseIt set mdl = do
browseIt :: IOish m => ModuleString -> GhcModT m (String, Bool)
browseIt mdl = do
let (det,rest') = break (== ' ') mdl
rest = dropWhile (== ' ') rest'
ret <- if det == "-d"
then withOptions setDetailed (browse rest)
else browse mdl
return (ret, True, set)
return (ret, True)
where
setDetailed opt = opt { detailed = True }
setDetailed opt = opt { detailed = True }

154
src/Misc.hs Normal file
View File

@ -0,0 +1,154 @@
{-# LANGUAGE DeriveDataTypeable, CPP #-}
module Misc (
GHCModiError(..)
, Restart(..)
, UnGetLine
, emptyNewUnGetLine
, ungetCommand
, getCommand
, SymDbReq
, newSymDbReq
, getDb
, checkDb
, prepareAutogen
) where
import Control.Applicative ((<$>))
import Control.Concurrent (threadDelay)
import Control.Concurrent.Async (Async, async, wait)
import Control.Exception (Exception)
import Control.Monad (unless, when)
import CoreMonad (liftIO)
import Data.IORef (IORef, newIORef, readIORef, writeIORef)
import Data.List (isPrefixOf)
import Data.Maybe (isJust)
import Data.Typeable (Typeable)
import System.Directory (doesDirectoryExist, getDirectoryContents)
import System.IO (openBinaryFile, IOMode(..))
import System.Process
import Language.Haskell.GhcMod
import Language.Haskell.GhcMod.Internal
----------------------------------------------------------------
data GHCModiError = CmdArg [String] deriving (Show, Typeable)
instance Exception GHCModiError
----------------------------------------------------------------
data Restart = Restart deriving (Show, Typeable)
instance Exception Restart
----------------------------------------------------------------
newtype UnGetLine = UnGetLine (IORef (Maybe String))
emptyNewUnGetLine :: IO UnGetLine
emptyNewUnGetLine = UnGetLine <$> newIORef Nothing
ungetCommand :: UnGetLine -> String -> IO ()
ungetCommand (UnGetLine ref) cmd = writeIORef ref (Just cmd)
getCommand :: UnGetLine -> IO String
getCommand (UnGetLine ref) = do
mcmd <- readIORef ref
case mcmd of
Nothing -> getLine
Just cmd -> do
writeIORef ref Nothing
return cmd
----------------------------------------------------------------
type SymDbReqAction = (Either GhcModError SymbolDb, GhcModLog)
data SymDbReq = SymDbReq (IORef (Async SymDbReqAction)) (IO SymDbReqAction)
newSymDbReq :: Options -> IO SymDbReq
newSymDbReq opt = do
let act = runGhcModT opt loadSymbolDb
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
----------------------------------------------------------------
build :: IO ProcessHandle
build = do
#ifdef WINDOWS
nul <- openBinaryFile "NUL" AppendMode
#else
nul <- openBinaryFile "/dev/null" AppendMode
#endif
(_, _, _, hdl) <- createProcess $ pro nul
return hdl
where
pro nul = CreateProcess {
cmdspec = RawCommand "cabal" ["build"]
, cwd = Nothing
, env = Nothing
, std_in = Inherit
, std_out = UseHandle nul
, std_err = UseHandle nul
, close_fds = False
#if __GLASGOW_HASKELL__ >= 702
, create_group = True
#endif
#if __GLASGOW_HASKELL__ >= 707
, delegate_ctlc = False
#endif
}
autogen :: String
autogen = "dist/build/autogen"
isAutogenPrepared :: IO Bool
isAutogenPrepared = do
exist <- doesDirectoryExist autogen
if exist then do
files <- filter ("." `isPrefixOf`) <$> getDirectoryContents autogen
if length files >= 2 then
return True
else
return False
else
return False
watch :: Int -> ProcessHandle -> IO ()
watch 0 _ = return ()
watch n hdl = do
prepared <- isAutogenPrepared
if prepared then
interruptProcessGroupOf hdl
else do
threadDelay 100000
watch (n - 1) hdl
prepareAutogen :: Cradle -> IO ()
prepareAutogen crdl = when (isJust $ cradleCabalFile crdl) $ do
prepared <- isAutogenPrepared
unless prepared $ do
hdl <- build
watch 30 hdl