----------------------------------------------------------------------------- -- | -- Module : System.Terminal.Concurrent -- Copyright : © 2009 Joachim Breitner -- License : BSD-style (see the file LICENSE) -- -- Maintainer : Joachim Breitner -- Stability : experimental -- Portability : non-portable (concurrency) -- -- This library provides a simple interface to output status -- messages from more than one thread. -- -- It will continue adding information (such as dots, or "done") -- to the correct line corresponding to the issuing thread and continue -- scrolling when a line is done. -- ----------------------------------------------------------------------------- module System.Terminal.Concurrent ( getConcurrentOutputter ) where import Control.Concurrent import Control.Monad import Data.IORef import Data.List import System.IO -- What threadid has written to what line, counting from below type OutputState = [(ThreadId,String)] -- | Returns an IO action to be called to output strings in a thread-safe manner. getConcurrentOutputter :: IO (String -> IO ()) getConcurrentOutputter = do chan <- newChan isTerm <- hIsTerminalDevice stdout osRef <- newIORef [] lock <- newQSem 1 return $ \s -> unless (null s) $ do waitQSem lock t <- myThreadId os <- readIORef osRef let all_lines = lines $ maybe s (++s) (lookup t os) let done = last s == '\n' let (done_lines, current_line) = if done || null all_lines then (all_lines, Nothing) else (init all_lines, Just (last all_lines)) when isTerm $ do unless (null os) $ do -- Go up some lines putStr $ "\ESC[" ++ show (length os) ++ "A" -- Clear everything unless (null os) $ putStr "\ESC[0J" -- Go up once more, to make it fit putStr $ "\ESC[1A" -- Write a new done line, if suitable mapM_ putLnStr done_lines unless isTerm $ do mapM_ putStrLn done_lines -- Update line let os'= replaceAppendOrDelete t current_line os when isTerm $ do -- Write the new not-yet-done lines mapM_ (putLnStr) $ map snd os' -- Go to the beginning of the next new line putStrLn "" writeIORef osRef os' signalQSem lock replaceAppendOrDelete :: (Eq a) => a -> Maybe b -> [(a,b)] -> [(a,b)] replaceAppendOrDelete a Nothing [] = [] replaceAppendOrDelete a (Just b) [] = [(a,b)] replaceAppendOrDelete a Nothing ((a',_):xs) | a == a' = xs replaceAppendOrDelete a (Just b) ((a',b'):xs) | a == a' = (a,b):xs replaceAppendOrDelete a mbB (x:xs) = x : replaceAppendOrDelete a mbB xs putLnStr s = putStr ('\n':s)