packages feed

ghc-mod-5.1.1.0: src/Misc.hs

{-# 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