Fix stdoutGateway line buffering
This commit is contained in:
parent
2c0d5af5e9
commit
7e565df923
@ -62,8 +62,6 @@ module Language.Haskell.GhcMod (
|
|||||||
, gmErrStr
|
, gmErrStr
|
||||||
, gmPutStrLn
|
, gmPutStrLn
|
||||||
, gmErrStrLn
|
, gmErrStrLn
|
||||||
, gmUnsafePutStr
|
|
||||||
, gmUnsafeErrStr
|
|
||||||
-- * FileMapping
|
-- * FileMapping
|
||||||
, loadMappedFile
|
, loadMappedFile
|
||||||
, loadMappedFileSource
|
, loadMappedFileSource
|
||||||
|
@ -28,85 +28,56 @@ module Language.Haskell.GhcMod.Output (
|
|||||||
|
|
||||||
, gmReadProcess
|
, gmReadProcess
|
||||||
|
|
||||||
, gmUnsafePutStr
|
|
||||||
, gmUnsafeErrStr
|
|
||||||
|
|
||||||
, stdoutGateway
|
, stdoutGateway
|
||||||
, flushStdoutGateway
|
, flushStdoutGateway
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Data.List
|
import Data.List
|
||||||
|
import qualified Data.Label as L
|
||||||
|
import qualified Data.Label.Base as LB
|
||||||
import System.IO
|
import System.IO
|
||||||
import System.Exit
|
import System.Exit
|
||||||
import System.Process
|
import System.Process
|
||||||
import Control.Monad
|
import Control.Monad
|
||||||
|
import Control.Monad.State.Strict
|
||||||
import Control.DeepSeq
|
import Control.DeepSeq
|
||||||
import Control.Exception
|
import Control.Exception
|
||||||
import Control.Concurrent
|
import Control.Concurrent (forkIO, killThread, myThreadId)
|
||||||
|
import Control.Concurrent.MVar
|
||||||
|
import Control.Concurrent.Chan
|
||||||
|
import Pipes
|
||||||
|
import Pipes.Lift
|
||||||
import Prelude
|
import Prelude
|
||||||
|
|
||||||
import Language.Haskell.GhcMod.Types hiding (LineSeparator)
|
import Language.Haskell.GhcMod.Types hiding (LineSeparator, MonadIO(..))
|
||||||
import Language.Haskell.GhcMod.Monad.Types
|
import Language.Haskell.GhcMod.Monad.Types hiding (MonadIO(..))
|
||||||
|
|
||||||
withLines :: (String -> String) -> String -> String
|
|
||||||
withLines f s = let
|
|
||||||
res = unlines $ map f $ lines s
|
|
||||||
in
|
|
||||||
case s of
|
|
||||||
[] -> res
|
|
||||||
_ | not $ isTerminated s ->
|
|
||||||
reverse $ drop 1 $ reverse res
|
|
||||||
_ -> res
|
|
||||||
|
|
||||||
isTerminated :: String -> Bool
|
|
||||||
isTerminated "" = False
|
|
||||||
isTerminated s = isNewline (last s)
|
|
||||||
|
|
||||||
isNewline :: Char -> Bool
|
|
||||||
isNewline c = c == '\n'
|
|
||||||
|
|
||||||
toGmLines :: String -> (GmLines String)
|
|
||||||
toGmLines "" = GmLines GmPartial ""
|
|
||||||
toGmLines s | isNewline (last s) = GmLines GmTerminated s
|
|
||||||
toGmLines s = GmLines GmPartial s
|
|
||||||
|
|
||||||
outputFns :: (GmOut m, MonadIO m')
|
outputFns :: (GmOut m, MonadIO m')
|
||||||
=> m (GmLines String -> m' (), GmLines String -> m' ())
|
=> m (String -> m' (), String -> m' ())
|
||||||
outputFns =
|
outputFns =
|
||||||
outputFns' `liftM` gmoAsk
|
outputFns' `liftM` gmoAsk
|
||||||
|
|
||||||
pfxFns :: Maybe (String, String) -> (GmLines String -> GmLines String, GmLines String -> GmLines String)
|
|
||||||
pfxFns lpfx = case lpfx of
|
|
||||||
Nothing -> ( id, id )
|
|
||||||
Just (op, ep) -> ( fmap $ pfx (op++), fmap $ pfx (ep++) )
|
|
||||||
where
|
|
||||||
pfx f = withLines f
|
|
||||||
|
|
||||||
stdioOutputFns :: MonadIO m => Maybe (String, String) -> (GmLines String -> m (), GmLines String -> m ())
|
|
||||||
stdioOutputFns lpfx = let
|
|
||||||
(outPfx, errPfx) = pfxFns lpfx
|
|
||||||
in
|
|
||||||
( liftIO . putStr . unGmLine . outPfx
|
|
||||||
, liftIO . hPutStr stderr . unGmLine . errPfx)
|
|
||||||
|
|
||||||
chanOutputFns :: MonadIO m
|
|
||||||
=> Chan (Either (MVar ()) (GmStream, GmLines String))
|
|
||||||
-> Maybe (String, String)
|
|
||||||
-> (GmLines String -> m (), GmLines String -> m ())
|
|
||||||
chanOutputFns c lpfx = let
|
|
||||||
(outPfx, errPfx) = pfxFns lpfx
|
|
||||||
in
|
|
||||||
( liftIO . writeChan c . Right . (,) GmOutStream . outPfx
|
|
||||||
, liftIO . writeChan c . Right . (,) GmErrStream . errPfx)
|
|
||||||
|
|
||||||
outputFns' ::
|
outputFns' ::
|
||||||
MonadIO m => GhcModOut -> (GmLines String -> m (), GmLines String -> m ())
|
MonadIO m => GhcModOut -> (String -> m (), String -> m ())
|
||||||
outputFns' (GhcModOut oopts c) = let
|
outputFns' (GhcModOut oopts c) = let
|
||||||
OutputOpts {..} = oopts
|
OutputOpts {..} = oopts
|
||||||
in
|
in
|
||||||
case ooptLinePrefix of
|
case ooptLinePrefix of
|
||||||
Nothing -> stdioOutputFns ooptLinePrefix
|
Nothing -> stdioOutputFns
|
||||||
Just _ -> chanOutputFns c ooptLinePrefix
|
Just _ -> chanOutputFns c
|
||||||
|
|
||||||
|
stdioOutputFns :: MonadIO m => (String -> m (), String -> m ())
|
||||||
|
stdioOutputFns =
|
||||||
|
( liftIO . putStr
|
||||||
|
, liftIO . hPutStr stderr
|
||||||
|
)
|
||||||
|
|
||||||
|
chanOutputFns :: MonadIO m
|
||||||
|
=> Chan (Either (MVar ()) (GmStream, String))
|
||||||
|
-> (String -> m (), String -> m ())
|
||||||
|
chanOutputFns c = (write GmOutStream, write GmErrStream)
|
||||||
|
where
|
||||||
|
write stream s = liftIO $ writeChan c $ Right $ (stream,s)
|
||||||
|
|
||||||
gmPutStr, gmPutStrLn, gmErrStr, gmErrStrLn
|
gmPutStr, gmPutStrLn, gmErrStr, gmErrStrLn
|
||||||
:: (MonadIO m, GmOut m) => String -> m ()
|
:: (MonadIO m, GmOut m) => String -> m ()
|
||||||
@ -124,16 +95,10 @@ gmErrStrLn = gmErrStr . (++"\n")
|
|||||||
|
|
||||||
gmPutStrIO, gmErrStrIO :: (GmOut m, MonadIO mi) => m (String -> mi ())
|
gmPutStrIO, gmErrStrIO :: (GmOut m, MonadIO mi) => m (String -> mi ())
|
||||||
|
|
||||||
gmPutStrIO = ((. toGmLines) . fst) `liftM` outputFns
|
gmPutStrIO = fst `liftM` outputFns
|
||||||
gmErrStrIO = ((. toGmLines) . snd) `liftM` outputFns
|
gmErrStrIO = snd `liftM` outputFns
|
||||||
|
|
||||||
|
|
||||||
-- | Only use these when you're sure there are no other writers on stdout
|
|
||||||
gmUnsafePutStr, gmUnsafeErrStr
|
|
||||||
:: MonadIO m => OutputOpts -> String -> m ()
|
|
||||||
gmUnsafePutStr oopts = (fst $ stdioOutputFns (ooptLinePrefix oopts)) . toGmLines
|
|
||||||
gmUnsafeErrStr oopts = (snd $ stdioOutputFns (ooptLinePrefix oopts)) . toGmLines
|
|
||||||
|
|
||||||
gmReadProcess :: GmOut m => m (FilePath -> [String] -> String -> IO String)
|
gmReadProcess :: GmOut m => m (FilePath -> [String] -> String -> IO String)
|
||||||
gmReadProcess = do
|
gmReadProcess = do
|
||||||
GhcModOut {..} <- gmoAsk
|
GhcModOut {..} <- gmoAsk
|
||||||
@ -143,64 +108,83 @@ gmReadProcess = do
|
|||||||
Nothing ->
|
Nothing ->
|
||||||
return $ readProcess
|
return $ readProcess
|
||||||
|
|
||||||
flushStdoutGateway :: Chan (Either (MVar ()) (GmStream, GmLines String)) -> IO ()
|
flushStdoutGateway :: Chan (Either (MVar ()) (GmStream, String)) -> IO ()
|
||||||
flushStdoutGateway c = do
|
flushStdoutGateway c = do
|
||||||
mv <- newEmptyMVar
|
mv <- newEmptyMVar
|
||||||
writeChan c $ Left mv
|
writeChan c $ Left mv
|
||||||
takeMVar mv
|
takeMVar mv
|
||||||
|
|
||||||
stdoutGateway :: Chan (Either (MVar ()) (GmStream, GmLines String)) -> IO ()
|
type Line = String
|
||||||
stdoutGateway chan = go ("", "")
|
|
||||||
|
stdoutGateway :: (String, String) -> Chan (Either (MVar ()) (GmStream, String)) -> IO ()
|
||||||
|
stdoutGateway (outPf, errPf) chan = do
|
||||||
|
runEffect $ commandProc >-> evalStateP ("","") seperateStreams
|
||||||
where
|
where
|
||||||
go :: (String, String) -> IO ()
|
commandProc :: Producer (Either (MVar ()) (GmStream, String)) IO ()
|
||||||
go buf = do
|
commandProc = do
|
||||||
cmd <- readChan chan
|
cmd <- liftIO $ readChan chan
|
||||||
case cmd of
|
case cmd of
|
||||||
Left mv -> do
|
Left mv -> do
|
||||||
let flush (obuf, ebuf) = do
|
yield $ Left mv
|
||||||
-- Add newline to unterminated stderr but not to stdout
|
Right input -> do
|
||||||
-- otherwise emacs will get confused etc
|
yield $ Right input
|
||||||
putStr $ ebuf ++ if null ebuf || last ebuf /= '\n'
|
commandProc
|
||||||
then "" else "\n"
|
|
||||||
putStr obuf
|
|
||||||
work (GmOutStream, GmLines GmPartial "") buf flush
|
|
||||||
putMVar mv ()
|
|
||||||
Right l ->
|
|
||||||
work l buf go
|
|
||||||
|
|
||||||
work (stream, GmLines ty l) buf@(obuf, ebuf) cont = case ty of
|
seperateStreams :: Consumer (Either (MVar ()) (GmStream, String)) (StateT (String, String) IO) ()
|
||||||
GmTerminated ->
|
seperateStreams = do
|
||||||
case stream of
|
ecmd <- await
|
||||||
GmOutStream ->
|
case ecmd of
|
||||||
putStr (obuf++l) >> hFlush stdout >> cont ("", ebuf)
|
Left mv -> do
|
||||||
GmErrStream ->
|
-- flush buffers
|
||||||
putStr (ebuf++l) >> hFlush stdout >> cont (obuf, "")
|
(\s -> lift $ zoom (streamLens s) $ sGetLine Nothing)
|
||||||
|
`mapM_` [GmOutStream, GmErrStream]
|
||||||
|
|
||||||
GmPartial ->
|
liftIO $ putMVar mv ()
|
||||||
case reverse $ lines l of
|
Right (stream, str) -> do
|
||||||
[] -> cont buf
|
ls <- lift $ zoom (streamLens stream) $ sGetLine (Just str)
|
||||||
[x] -> cont (appendBuf stream buf x)
|
case ls of
|
||||||
x:xs -> do
|
[] -> return ()
|
||||||
putStr $ unlines $ reverse xs
|
_ -> liftIO $ putStr $ unlines $ map (streamPf stream++) ls
|
||||||
hFlush stdout
|
|
||||||
cont (appendBuf stream buf x)
|
|
||||||
|
|
||||||
appendBuf GmOutStream (obuf, ebuf) s = (obuf++s, ebuf)
|
liftIO $ hFlush stdout
|
||||||
appendBuf GmErrStream (obuf, ebuf) s = (obuf, ebuf++s)
|
seperateStreams
|
||||||
|
|
||||||
|
sGetLine :: (Maybe String) -> StateT String IO [Line]
|
||||||
|
sGetLine mstr' = do
|
||||||
|
buf <- get
|
||||||
|
let mstr = (buf++) <$> mstr'
|
||||||
|
case mstr of
|
||||||
|
Nothing -> put "" >> return [buf]
|
||||||
|
Just "" -> return []
|
||||||
|
Just s | last s == '\n' -> put "" >> return (lines s)
|
||||||
|
| otherwise -> do
|
||||||
|
let (p:ls') = reverse $ lines s
|
||||||
|
put p
|
||||||
|
return $ reverse $ ls'
|
||||||
|
|
||||||
|
streamLens GmOutStream = LB.fst
|
||||||
|
streamLens GmErrStream = LB.snd
|
||||||
|
|
||||||
|
streamPf GmOutStream = outPf
|
||||||
|
streamPf GmErrStream = errPf
|
||||||
|
|
||||||
|
zoom :: Monad m => (f L.:-> o) -> StateT o m a -> StateT f m a
|
||||||
|
zoom l (StateT a) =
|
||||||
|
StateT $ \f -> do
|
||||||
|
(a, s') <- a $ L.get l f
|
||||||
|
return (a, L.set l s' f)
|
||||||
|
|
||||||
readProcessStderrChan ::
|
readProcessStderrChan ::
|
||||||
GmOut m => m (FilePath -> [String] -> String -> IO String)
|
GmOut m => m (FilePath -> [String] -> String -> IO String)
|
||||||
readProcessStderrChan = do
|
readProcessStderrChan = do
|
||||||
(_, e :: GmLines String -> IO ()) <- outputFns
|
(_, e :: String -> IO ()) <- outputFns
|
||||||
return $ readProcessStderrChan' e
|
return $ readProcessStderrChan' e
|
||||||
|
|
||||||
readProcessStderrChan' ::
|
readProcessStderrChan' ::
|
||||||
(GmLines String -> IO ())
|
(String -> IO ()) -> FilePath -> [String] -> String -> IO String
|
||||||
-> FilePath -> [String] -> String -> IO String
|
|
||||||
readProcessStderrChan' pute = go pute
|
readProcessStderrChan' pute = go pute
|
||||||
where
|
where
|
||||||
go :: (GmLines String -> IO ()) -> FilePath -> [String] -> String -> IO String
|
go :: (String -> IO ()) -> FilePath -> [String] -> String -> IO String
|
||||||
go putErr exe args input = do
|
go putErr exe args input = do
|
||||||
let cp = (proc exe args) {
|
let cp = (proc exe args) {
|
||||||
std_out = CreatePipe
|
std_out = CreatePipe
|
||||||
@ -233,7 +217,7 @@ readProcessStderrChan' pute = go pute
|
|||||||
where
|
where
|
||||||
ignoreSEx = handle (\(SomeException _) -> return ())
|
ignoreSEx = handle (\(SomeException _) -> return ())
|
||||||
reader h = ignoreSEx $ do
|
reader h = ignoreSEx $ do
|
||||||
putErr . toGmLines . (++"\n") =<< hGetLine h
|
putErr . (++"\n") =<< hGetLine h
|
||||||
reader h
|
reader h
|
||||||
|
|
||||||
withForkWait :: IO () -> (IO () -> IO a) -> IO a
|
withForkWait :: IO () -> (IO () -> IO a) -> IO a
|
||||||
|
@ -172,19 +172,9 @@ data Cradle = Cradle {
|
|||||||
, cradleDistDir :: FilePath
|
, cradleDistDir :: FilePath
|
||||||
} deriving (Eq, Show)
|
} deriving (Eq, Show)
|
||||||
|
|
||||||
|
|
||||||
data GmStream = GmOutStream | GmErrStream
|
data GmStream = GmOutStream | GmErrStream
|
||||||
deriving (Show)
|
deriving (Show)
|
||||||
|
|
||||||
data GmLineType = GmTerminated | GmPartial
|
|
||||||
deriving (Show)
|
|
||||||
|
|
||||||
data GmLines a = GmLines GmLineType a
|
|
||||||
deriving (Show, Functor)
|
|
||||||
|
|
||||||
unGmLine :: GmLines a -> a
|
|
||||||
unGmLine (GmLines _ s) = s
|
|
||||||
|
|
||||||
data GhcModEnv = GhcModEnv {
|
data GhcModEnv = GhcModEnv {
|
||||||
gmOptions :: Options
|
gmOptions :: Options
|
||||||
, gmCradle :: Cradle
|
, gmCradle :: Cradle
|
||||||
@ -192,7 +182,7 @@ data GhcModEnv = GhcModEnv {
|
|||||||
|
|
||||||
data GhcModOut = GhcModOut {
|
data GhcModOut = GhcModOut {
|
||||||
gmoOptions :: OutputOpts
|
gmoOptions :: OutputOpts
|
||||||
, gmoChan :: Chan (Either (MVar ()) (GmStream, GmLines String))
|
, gmoChan :: Chan (Either (MVar ()) (GmStream, String))
|
||||||
}
|
}
|
||||||
|
|
||||||
data GhcModLog = GhcModLog {
|
data GhcModLog = GhcModLog {
|
||||||
|
@ -174,6 +174,7 @@ Library
|
|||||||
, djinn-ghc >= 0.0.2.2
|
, djinn-ghc >= 0.0.2.2
|
||||||
, fclabels == 2.0.*
|
, fclabels == 2.0.*
|
||||||
, extra == 1.4.*
|
, extra == 1.4.*
|
||||||
|
, pipes == 4.1.*
|
||||||
if impl(ghc < 7.8)
|
if impl(ghc < 7.8)
|
||||||
Build-Depends: convertible
|
Build-Depends: convertible
|
||||||
if impl(ghc < 7.5)
|
if impl(ghc < 7.5)
|
||||||
|
@ -399,7 +399,7 @@ main = do
|
|||||||
Left e -> throw e
|
Left e -> throw e
|
||||||
Right res@(globalOptions,_) -> catches (progMain res) [
|
Right res@(globalOptions,_) -> catches (progMain res) [
|
||||||
Handler $ \(e :: GhcModError) ->
|
Handler $ \(e :: GhcModError) ->
|
||||||
exitError' globalOptions $ renderStyle ghcModStyle (gmeDoc e)
|
runGmOutT globalOptions $ exitError $ renderStyle ghcModStyle (gmeDoc e)
|
||||||
]
|
]
|
||||||
|
|
||||||
progMain :: (Options,[String]) -> IO ()
|
progMain :: (Options,[String]) -> IO ()
|
||||||
@ -575,11 +575,6 @@ instance Exception InvalidCommandLine
|
|||||||
exitError :: (MonadIO m, GmOut m) => String -> m a
|
exitError :: (MonadIO m, GmOut m) => String -> m a
|
||||||
exitError msg = gmErrStrLn (dropWhileEnd (=='\n') msg) >> liftIO exitFailure
|
exitError msg = gmErrStrLn (dropWhileEnd (=='\n') msg) >> liftIO exitFailure
|
||||||
|
|
||||||
exitError' :: Options -> String -> IO a
|
|
||||||
exitError' opts msg = do
|
|
||||||
gmUnsafeErrStr (optOutput opts) $ dropWhileEnd (=='\n') msg ++ "\n"
|
|
||||||
liftIO exitFailure
|
|
||||||
|
|
||||||
fatalError :: String -> a
|
fatalError :: String -> a
|
||||||
fatalError s = throw $ FatalError $ "ghc-mod: " ++ s
|
fatalError s = throw $ FatalError $ "ghc-mod: " ++ s
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user