module System.Terminal.Concurrent
( ConcurrentOutput
, startConcurrentOutput
, writeConcurrent
, writeConcurrentDone
) where
import Control.Concurrent
import Control.Monad
import Data.List
import System.IO
data ConcurrentOutput = ConcurrentOutput { coChan :: Chan (ThreadId, Bool, String) }
startConcurrentOutput :: IO ConcurrentOutput
startConcurrentOutput = do
chan <- newChan
isTerm <- hIsTerminalDevice stdout
forkIO (receiverThread chan isTerm)
return (ConcurrentOutput chan)
writeConcurrent :: ConcurrentOutput -> String -> IO ()
writeConcurrent (ConcurrentOutput chan) s = do
threadId <- myThreadId
writeChan chan (threadId, False, s)
writeConcurrentDone :: ConcurrentOutput -> String -> IO ()
writeConcurrentDone (ConcurrentOutput chan) s = do
threadId <- myThreadId
writeChan chan (threadId, True, s)
type OutputState = [(ThreadId,String)]
receiverThread :: Chan (ThreadId, Bool, String) -> Bool -> IO ()
receiverThread chan True = runner []
where runner os = do
(t,d,s) <- readChan chan
let s' = maybe s (++s) (lookup t os)
let os'= if d then filter ((/=t).fst) os
else replaceOrAppend t s' os
unless (null os) $ do
putStr $ "\ESC[" ++ show (length os) ++ "A"
unless (null os) $ putStr "\ESC[0J"
putStr $ "\ESC[1A"
when d $ putLnStr s'
mapM_ (putLnStr) $ map snd os'
putStrLn ""
runner os'
receiverThread chan False = runner []
where runner os = do
(t,d,s) <- readChan chan
let s' = maybe s (++s) (lookup t os)
let os'= if d then filter ((/=t).fst) os
else replaceOrAppend t s' os
when d $ putStrLn s'
runner os'
replaceOrAppend :: (Eq a) => a -> b -> [(a,b)] -> [(a,b)]
replaceOrAppend a b [] = [(a,b)]
replaceOrAppend a b ((a',b'):xs) | a == a' = (a,b):xs
| otherwise = (a',b') : replaceOrAppend a b xs
putLnStr s = putStr ('\n':s)