-------------------------------------------------------------------------------- {-# LANGUAGE BangPatterns #-} module Patat.PrettyPrint.Matrix ( Matrix , Cell (..) , emptyCell , docToMatrix , hPutMatrix ) where -------------------------------------------------------------------------------- import Control.Monad (unless, when) import Data.Char.WCWidth.Extended (wcwidth) import qualified Data.Vector as V import qualified Data.Vector.Mutable as VM import Patat.PrettyPrint.Internal hiding (null) import Patat.Size (Size (..)) import qualified System.Console.ANSI as Ansi import qualified System.IO as IO -------------------------------------------------------------------------------- data Cell = Cell [Ansi.SGR] Char deriving (Eq, Show) -------------------------------------------------------------------------------- type Matrix = V.Vector Cell -------------------------------------------------------------------------------- emptyCell :: Cell emptyCell = Cell [] ' ' -------------------------------------------------------------------------------- docToMatrix :: Size -> Doc -> Matrix docToMatrix (Size rows cols) doc = V.create $ do matrix <- VM.replicate (rows * cols) emptyCell go matrix 0 0 $ docToChunks doc pure matrix where go r y x (StringChunk _ [] : cs) = go r y x cs go _ _ _ [] = pure () go _ y _ _ | y >= rows = pure () go r y _ (NewlineChunk : cs) = go r (y + 1) 0 cs go r y x (ControlChunk ClearScreenControl : cs) = go r y x cs -- ? go r _ x (ControlChunk (GoToLineControl y) : cs) = go r y x cs go r y x chunks@(StringChunk codes (z : zs) : cs) | x + w > cols = go r (y + 1) 0 chunks | otherwise = do VM.write r (y * cols + x) (Cell codes z) go r y (x + wcwidth z) (StringChunk codes zs : cs) where w = wcwidth z -------------------------------------------------------------------------------- hPutMatrix :: IO.Handle -> Size -> Matrix -> IO () hPutMatrix h size matrix = go 0 0 0 [] where go !y !x !empties prevCodes | x >= sCols size = IO.hPutStrLn h "" >> go (y + 1) 0 0 prevCodes | y >= sRows size = Ansi.hSetSGR h [Ansi.Reset] -- Try to not print empty things (e.g. fill the screen with spaces) as -- an optimization. Instead, store the number of empties and print them -- when something actually follows. | cell == emptyCell = do unless (null prevCodes) $ Ansi.hSetSGR h [Ansi.Reset] go y (x + 1) (empties + 1) [] | otherwise = do unless (empties == 0) $ IO.hPutStr h (replicate empties ' ') when (prevCodes /= codes) $ Ansi.hSetSGR h (Ansi.Reset : reverse codes) IO.hPutStr h [c] go y (x + wcwidth c) 0 codes where cell@(Cell codes c) = matrix V.! (y * sCols size + x)