module System.Terminal.Virtual where import Control.Monad.STM import Control.Concurrent.STM.TVar import Control.Monad.IO.Class import qualified Data.ByteString as BS import qualified Data.Text as T import System.Terminal.MonadInput import System.Terminal.MonadScreen (Size (..), Position (..), EraseMode (..)) import System.Terminal.Terminal data VirtualTerminal = VirtualTerminal { virtualSettings :: VirtualTerminalSettings , virtualCursor :: TVar Position , virtualWindow :: TVar [String] , virtualAutoWrap :: TVar Bool , virtualAlternateScreenBuffer :: TVar Bool } data VirtualTerminalSettings = VirtualTerminalSettings { virtualType :: BS.ByteString , virtualWindowSize :: STM Size , virtualEvent :: STM Event , virtualInterrupt :: STM Interrupt } instance Terminal VirtualTerminal where termType = virtualType . virtualSettings termEvent = virtualEvent . virtualSettings termInterrupt = virtualInterrupt . virtualSettings termCommand t c = atomically (command t c) termFlush _ = pure () termGetWindowSize = atomically . virtualWindowSize . virtualSettings termGetCursorPosition = readTVarIO . virtualCursor withVirtualTerminal :: (MonadIO m) => VirtualTerminalSettings -> (VirtualTerminal -> m a) -> m a withVirtualTerminal settings handler = do size <- liftIO $ atomically $ virtualWindowSize settings term <- liftIO $ atomically $ VirtualTerminal settings <$> newTVar (Position 0 0) <*> newTVar (replicate (height size) (replicate (width size) ' ')) <*> newTVar True <*> newTVar False handler term command :: VirtualTerminal -> Command -> STM () command t = \case PutLn -> putLn t PutText s -> putString t (T.unpack s) SetAttribute _ -> pure () ResetAttribute _ -> pure () ResetAttributes -> pure () MoveCursorUp i -> moveCursorVertical t (negate i) MoveCursorDown i -> moveCursorVertical t i MoveCursorForward i -> moveCursorHorizontal t i MoveCursorBackward i -> moveCursorHorizontal t (negate i) ShowCursor -> pure () HideCursor -> pure () SaveCursor -> pure () RestoreCursor -> pure () GetCursorPosition -> getCursorPosition t SetCursorPosition pos -> setCursorPosition t pos SetCursorRow r -> setCursorRow t r SetCursorColumn c -> setCursorColumn t c InsertChars i -> insertChars t i DeleteChars i -> deleteChars t i EraseChars i -> eraseChars t i InsertLines i -> insertLines t i DeleteLines i -> deleteLines t i EraseInLine m -> eraseInLine t m EraseInDisplay m -> eraseInDisplay t m SetAutoWrap b -> setAutoWrap t b SetAlternateScreenBuffer b -> setAlternateScreenBuffer t b scrollDown :: Int -> [String] -> [String] scrollDown w window = drop 1 window ++ [replicate w ' '] putLn :: VirtualTerminal -> STM () putLn t = do Size h w <- virtualWindowSize (virtualSettings t) Position r _ <- readTVar (virtualCursor t) window <- readTVar (virtualWindow t) if r + 1 == h then do writeTVar (virtualCursor t) $ Position r 0 writeTVar (virtualWindow t) (scrollDown w window) else do writeTVar (virtualCursor t) $ Position (r + 1) 0 putString :: VirtualTerminal -> String -> STM () putString t s = do Size h w <- virtualWindowSize (virtualSettings t) autoWrap <- readTVar (virtualAutoWrap t) Position r c <- readTVar (virtualCursor t) wndw <- readTVar (virtualWindow t) let cl = w - c -- space in cursor line f "" ls = ls f x [] = let k = (take w x) in (k <> replicate (w - length k) ' ') : f (drop w x) [] f x (l:ls) = let k = (take w x) in (k <> drop (length k) l) : f (drop w x) ls w1 = take r wndw w2 = [take c l <> k <> drop (c + length k) l] where k = take cl s l = wndw !! r w3 | autoWrap = f (drop cl s) $ drop (r + 1) wndw | otherwise = drop (r + 1) wndw w4 = w1 <> w2 <> w3 writeTVar (virtualWindow t) (reverse $ take h $ reverse w4) if autoWrap then do let (r',c') = quotRem (r * w + c + length s) w writeTVar (virtualCursor t) $ Position (min r' (h - 1)) c' else do let (r', c') = (r, min (w - 1) (c + length s)) writeTVar (virtualCursor t) $ Position r' c' moveCursorHorizontal :: VirtualTerminal -> Int -> STM () moveCursorHorizontal t i = do Size _ w <- virtualWindowSize (virtualSettings t) Position r c <- readTVar (virtualCursor t) writeTVar (virtualCursor t) $ Position r (max 0 $ min (w - 1) $ c + i) moveCursorVertical :: VirtualTerminal -> Int -> STM () moveCursorVertical t i = do Size h _ <- virtualWindowSize (virtualSettings t) Position r c <- readTVar (virtualCursor t) writeTVar (virtualCursor t) $ Position (max 0 $ min (h - 1) $ r + i) c getCursorPosition :: VirtualTerminal -> STM () getCursorPosition _ = pure () setCursorPosition :: VirtualTerminal -> Position -> STM () setCursorPosition t (Position r c) = do Size h w <- virtualWindowSize (virtualSettings t) writeTVar (virtualCursor t) $ Position (max 0 (min (h - 1) r)) (max 0 (min (w - 1) c)) setCursorRow :: VirtualTerminal -> Int -> STM () setCursorRow t r = do Size h _ <- virtualWindowSize (virtualSettings t) Position _ c <- readTVar (virtualCursor t) writeTVar (virtualCursor t) $ Position (max 0 (min (h - 1) r)) c setCursorColumn :: VirtualTerminal -> Int -> STM () setCursorColumn t c = do Size _ w <- virtualWindowSize (virtualSettings t) Position r _ <- readTVar (virtualCursor t) writeTVar (virtualCursor t) $ Position r (max 0 (min (w - 1) c)) insertChars :: VirtualTerminal -> Int -> STM () insertChars t i = do Size _ w <- virtualWindowSize (virtualSettings t) Position r c <- readTVar (virtualCursor t) wndw <- readTVar (virtualWindow t) let l = wndw !! r w1 = take r wndw w2 = [take w $ take c l <> replicate i ' ' <> drop c l] w3 = drop (r + 1) wndw writeTVar (virtualWindow t) (w1 <> w2 <> w3) deleteChars :: VirtualTerminal -> Int -> STM () deleteChars t i = do Position r c <- readTVar (virtualCursor t) wndw <- readTVar (virtualWindow t) let l = wndw !! r w1 = take r wndw w2 = [take c l <> drop (c + i) l <> replicate i ' '] w3 = drop (r + 1) wndw writeTVar (virtualWindow t) (w1 <> w2 <> w3) eraseChars :: VirtualTerminal -> Int -> STM () eraseChars t i = do Position r c <- readTVar (virtualCursor t) wndw <- readTVar (virtualWindow t) let l = wndw !! r w1 = take r wndw w2 = [take c l <> replicate i ' ' <> drop (c + i) l] w3 = drop (r + 1) wndw writeTVar (virtualWindow t) (w1 <> w2 <> w3) insertLines :: VirtualTerminal -> Int -> STM () insertLines t i = do Size h w <- virtualWindowSize (virtualSettings t) Position r _ <- readTVar (virtualCursor t) wndw <- readTVar (virtualWindow t) let w1 = take r wndw w2 = replicate i (replicate w ' ') w3 = take (h - r - i) $ drop r wndw writeTVar (virtualWindow t) (w1 <> w2 <> w3) deleteLines :: VirtualTerminal -> Int -> STM () deleteLines t i = do Size h w <- virtualWindowSize (virtualSettings t) Position r _ <- readTVar (virtualCursor t) wndw <- readTVar (virtualWindow t) let w1 = take r wndw w2 = take (h - r - i) $ drop r wndw w3 = replicate i (replicate w ' ') writeTVar (virtualWindow t) (w1 <> w2 <> w3) eraseInLine :: VirtualTerminal -> EraseMode -> STM () eraseInLine t m = do Size _ w <- virtualWindowSize (virtualSettings t) Position r c <- readTVar (virtualCursor t) wndw <- readTVar (virtualWindow t) let l = wndw !! r w1 = take r wndw w2 = case m of EraseBackward -> [replicate (c + 1) ' ' <> drop (c + 1) l] EraseForward -> [take c l <> replicate (w - c) ' '] EraseAll -> [replicate w ' '] w3 = drop (r + 1) wndw writeTVar (virtualWindow t) (w1 <> w2 <> w3) eraseInDisplay :: VirtualTerminal -> EraseMode -> STM () eraseInDisplay t m = do Size h w <- virtualWindowSize (virtualSettings t) Position r _ <- readTVar (virtualCursor t) wndw <- readTVar (virtualWindow t) let w1 = take r wndw w1E = replicate r (replicate w ' ') w2 = [wndw !! r] w2E = [replicate w ' '] w3 = drop (r + 1) wndw w3E = replicate (h - r - 1) (replicate w ' ') writeTVar (virtualWindow t) $ case m of EraseBackward -> w1E <> w2 <> w3 EraseForward -> w1 <> w2 <> w3E EraseAll -> w1E <> w2E <> w3E setAutoWrap :: VirtualTerminal -> Bool -> STM () setAutoWrap t b = do writeTVar (virtualAutoWrap t) b setAlternateScreenBuffer :: VirtualTerminal -> Bool -> STM () setAlternateScreenBuffer t b = do writeTVar (virtualAlternateScreenBuffer t) b