hs-term-emulator-0.1.0.0: src/System/Terminal/Emulator/Term/Process.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE OverloadedStrings #-}
module System.Terminal.Emulator.Term.Process
( Term,
TermLine,
processTermAtoms,
)
where
import Control.Category ((>>>))
import Control.Lens
import Data.ByteString (ByteString)
import qualified Data.ByteString as B
import qualified Data.ByteString.Char8 as BC8
import Data.Foldable (foldl')
import Data.List (iterate')
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Vector as V
import qualified Data.Vector.Unboxed as VU
import qualified System.Console.ANSI.Types as SGR
import System.Terminal.Emulator.Attrs (Attrs, attrsBg, attrsFg, attrsIntensity, attrsUnderline, blankAttrs)
import System.Terminal.Emulator.DECPrivateMode (DECPrivateMode)
import qualified System.Terminal.Emulator.DECPrivateMode as DECPrivateMode
import System.Terminal.Emulator.KeyboardInput (KeyboardState (keyboardState_CRLF, keyboardState_DECCKM, keyboardState_Locked))
import System.Terminal.Emulator.Parsing.Types (ControlSequenceIntroducer (..), DeviceStatusReport (..), EraseInDisplayParam (..), EraseInLineParam (..), EscapeSequence (..), Mode (..), OperatingSystemCommand (..), SendDeviceAttributesSecondary (RequestTerminalIdentificationCode), SingleCharacterFunction (..), TermAtom (..), WindowManipulation (..))
import System.Terminal.Emulator.Term (Term, activeScreen, addScrollBackLines, altScreenActive, cursorLine, cursorPos, cursorState, insertMode, keyboardState, mkTerm, modeWrap, numCols, numRows, origin, scrollBackLines, scrollBottom, scrollTop, termAttrs, termScreen, vuIndex, windowTitle, wrapNext)
import System.Terminal.Emulator.TermLines (TermLine)
import qualified System.Terminal.Emulator.TermLines as TL
import Prelude hiding (lines)
processTermAtoms :: [TermAtom] -> Term -> (ByteString, Term)
processTermAtoms termAtoms term =
foldl'
( \(!w1, !t) termAtom ->
let (!w2, !t') = processTermAtom termAtom t
in (w1 <> w2, t')
)
(B.empty, term)
termAtoms
processTermAtom :: TermAtom -> Term -> (ByteString, Term)
processTermAtom (TermAtom_VisibleChar char) = nw $ processVisibleChar char
processTermAtom (TermAtom_SingleCharacterFunction Control_Bell) = nw id -- TODO
processTermAtom (TermAtom_SingleCharacterFunction Control_Backspace) = nw $ \term -> cursorMoveTo (term ^. cursorPos . _1, (term ^. cursorPos . _2) - 1) term
processTermAtom (TermAtom_SingleCharacterFunction Control_Tab) = nw $ putTabs 1
processTermAtom (TermAtom_SingleCharacterFunction Control_LineFeed) = nw $ processLF
processTermAtom (TermAtom_SingleCharacterFunction Control_VerticalTab) = nw $ processLF
processTermAtom (TermAtom_SingleCharacterFunction Control_FormFeed) = nw $ processLF
processTermAtom (TermAtom_SingleCharacterFunction Control_CarriageReturn) = nw $ \term -> cursorMoveTo (term ^. cursorPos . _1, 0) term
processTermAtom (TermAtom_SingleCharacterFunction Control_ReturnTerminalStatus) = nw id
processTermAtom (TermAtom_SingleCharacterFunction Control_SwitchToStandardCharacterSet) = nw id
processTermAtom (TermAtom_SingleCharacterFunction Control_SwitchToAlternateCharacterSet) = nw id
processTermAtom (TermAtom_EscapeSequence escapeSequence) = processEscapeSequence escapeSequence
processTermAtom (TermAtom_SingleCharacterFunctionUnknown x) = error $ "Unknown Character Function: " <> show x
processTermAtom (TermAtom_EscapeSequenceUnknown x)
| isExpectedInvalidEscSequence x = nw id
| otherwise = error $ "Unknown ESC seq: " <> show x
-- | No-write operation
nw :: (Term -> Term) -> Term -> (ByteString, Term)
nw f term = (B.empty, f term)
-- | I have observed some invalid ESC sequences in the wild, that I am
-- deciding to ignore for now
isExpectedInvalidEscSequence :: Text -> Bool
isExpectedInvalidEscSequence str
| ("\ESC[" `T.isPrefixOf` str) && T.any (== '\r') str = True
| str == "\ESC\r" = True
| otherwise = False
processEscapeSequence :: EscapeSequence -> Term -> (ByteString, Term)
processEscapeSequence Esc_ReverseIndex = nw reverseIndex
processEscapeSequence Esc_RIS = nw id -- TODO
processEscapeSequence Esc_DECPAM = nw id -- TODO
processEscapeSequence Esc_DECPNM = nw id -- TODO
processEscapeSequence (ESC_SetG0CharacterSet _) = nw id -- Ignore
processEscapeSequence (Esc_CSI (CSI_CursorUp n)) = nw $ \term -> cursorMoveTo ((term ^. cursorPos . _1) - n, term ^. cursorPos . _2) term
processEscapeSequence (Esc_CSI (CSI_CursorDown n)) = nw $ \term -> cursorMoveTo ((term ^. cursorPos . _1) + n, term ^. cursorPos . _2) term
processEscapeSequence (Esc_CSI (CSI_LinePositionRelative n)) = nw $ \term -> cursorMoveTo ((term ^. cursorPos . _1) + n, term ^. cursorPos . _2) term
processEscapeSequence (Esc_CSI (CSI_CharacterPositionRelative n)) = nw $ \term -> cursorMoveTo (term ^. cursorPos . _1, (term ^. cursorPos . _2) + n) term
processEscapeSequence (Esc_CSI (CSI_CursorForward n)) = nw $ \term -> cursorMoveTo (term ^. cursorPos . _1, (term ^. cursorPos . _2) + n) term
processEscapeSequence (Esc_CSI (CSI_CursorBack n)) = nw $ \term -> cursorMoveTo (term ^. cursorPos . _1, (term ^. cursorPos . _2) - n) term
processEscapeSequence (Esc_CSI (CSI_EraseInLine param)) = nw $ eraseInLine param
processEscapeSequence (Esc_CSI (CSI_EraseCharacters n)) = nw $ eraseCharacters n
processEscapeSequence (Esc_CSI (CSI_InsertBlankCharacters n)) = nw $ insertBlankChars n
processEscapeSequence (Esc_CSI (CSI_InsertBlankLines n)) = nw $ insertBlankLines n
processEscapeSequence (Esc_CSI (CSI_DeleteChars n)) = nw $ deleteChars n
processEscapeSequence (Esc_CSI (CSI_DeleteLines n)) = nw $ deleteLines n
processEscapeSequence (Esc_CSI (CSI_CursorCharacterAbsolute col)) = nw $ \term -> cursorMoveTo (term ^. cursorPos . _1, col - 1) term
processEscapeSequence (Esc_CSI (CSI_CharacterPositionAbsolute col)) = nw $ \term -> cursorMoveTo (term ^. cursorPos . _1, col - 1) term
processEscapeSequence (Esc_CSI (CSI_CursorPosition row col)) = nw $ cursorMoveAbsoluteTo (row - 1, col -1)
processEscapeSequence (Esc_CSI (CSI_HorizontalVerticalPosition row col)) = nw $ cursorMoveAbsoluteTo (row - 1, col - 1)
processEscapeSequence (Esc_CSI (CSI_LinePositionAbsolute row)) = nw $ \term -> cursorMoveAbsoluteTo (row - 1, term ^. cursorPos . _2) term
processEscapeSequence (Esc_CSI (CSI_ScrollUp n)) = nw $ \term -> scrollUp (term ^. scrollTop) n term
processEscapeSequence (Esc_CSI (CSI_ScrollDown n)) = nw $ \term -> scrollDown (term ^. scrollTop) n term
processEscapeSequence (Esc_CSI (CSI_EraseInDisplay param)) = nw $ eraseInDisplay param
processEscapeSequence (Esc_CSI (CSI_WindowManipulation param)) = nw $ windowManipulation param
processEscapeSequence (Esc_CSI (CSI_DeviceStatusReport param)) = deviceStatusReport param
processEscapeSequence (Esc_CSI (CSI_SoftTerminalReset)) = nw $ softTerminalReset
processEscapeSequence (Esc_CSI (CSI_SetMode param)) = nw $ setMode param
processEscapeSequence (Esc_CSI (CSI_ResetMode param)) = nw $ resetMode param
processEscapeSequence (Esc_CSI (CSI_SendDeviceAttributes)) = sendDeviceAttributes
processEscapeSequence (Esc_CSI (CSI_SendDeviceAttributesSecondary param)) = sendDeviceAttributesSecondary param
processEscapeSequence (Esc_CSI (CSI_RequestDECPrivateMode _i)) = nw id -- TODO (?)
processEscapeSequence (Esc_CSI (CSI_DECSTBM top bottom)) = nw $ (setScrollingRegion top bottom) >>> cursorMoveAbsoluteTo (0, 0)
processEscapeSequence (Esc_CSI (CSI_DECSET decset)) = nw $ termProcessDecset decset
processEscapeSequence (Esc_CSI (CSI_DECSET_Unknown _code)) = nw id -- TODO Log this
processEscapeSequence (Esc_CSI (CSI_DECRST decset)) = nw $ termProcessDecrst decset
processEscapeSequence (Esc_CSI (CSI_DECRST_Unknown _code)) = nw id -- TODO Log this
processEscapeSequence (Esc_CSI (CSI_SGR sgrs)) = nw $ \term -> V.foldl' (flip termProcessSGR) term sgrs
processEscapeSequence (Esc_OSC osc) = processOsc osc
putTabs :: Int -> Term -> Term
putTabs n
| n >= 0 = \term -> (iterate' putTabForward term) !! n
| otherwise = \term -> (iterate' putTabBackward term) !! (negate n)
where
tabspaces = 8
putTabForward :: Term -> Term
putTabForward term = ((cursorPos . _2) .~ (limit 0 (term ^. numCols - 1) col')) term
where
col = term ^. cursorPos . _2
col' = ((col + tabspaces) `div` tabspaces) * tabspaces
putTabBackward :: Term -> Term
putTabBackward term
| col == 0 = term
| otherwise = ((cursorPos . _2) .~ (limit 0 (term ^. numCols - 1) col')) term
where
col = term ^. cursorPos . _2
col' = ((col - 1) `div` tabspaces) * tabspaces
processLF :: Term -> Term
processLF term = addNewline (keyboardState_CRLF (term ^. keyboardState)) term
eraseInLine :: EraseInLineParam -> Term -> Term
eraseInLine ClearFromCursorToEndOfLine term = clearRegion (term ^. cursorPos) (term ^. cursorPos ^. _1, (term ^. numCols) - 1) term
eraseInLine ClearFromCursorToBeginningOfLine term = clearRegion (term ^. cursorPos ^. _1, 0) (term ^. cursorPos) term
eraseInLine ClearEntireLine term = clearRegion (term ^. cursorPos ^. _1, 0) (term ^. cursorPos ^. _1, (term ^. numCols) - 1) term
eraseCharacters :: Int -> Term -> Term
eraseCharacters n term = clearRegion (term ^. cursorPos) ((_2 %~ ((subtract 1) . (+ n))) (term ^. cursorPos)) term
reverseIndex :: Term -> Term
reverseIndex term
| term ^. cursorPos . _1 == term ^. scrollTop = scrollDown (term ^. scrollTop) 1 term
| otherwise = cursorMoveTo ((term ^. cursorPos . _1) - 1, term ^. cursorPos . _2) term
eraseInDisplay :: EraseInDisplayParam -> Term -> Term
eraseInDisplay EraseAbove _ = error "TODO EraseAbove"
eraseInDisplay EraseBelow term = (clearToEndOfLine >>> clearBelow) term
where
clearToEndOfLine = clearRegion (term ^. cursorPos) ((_2 .~ ((term ^. numCols) - 1)) (term ^. cursorPos))
clearBelow
| term ^. cursorPos . _1 < term ^. numRows - 1 =
clearRegion
(((_1 %~ (+ 1)) >>> (_2 .~ 0)) (term ^. cursorPos))
(((_1 .~ ((term ^. numRows) - 1)) >>> (_2 .~ ((term ^. numCols) - 1))) (term ^. cursorPos))
| otherwise = id
eraseInDisplay EraseAll term = clearRegion (0, 0) ((term ^. numRows) - 1, (term ^. numCols) - 1) term
eraseInDisplay EraseSavedLines term = (scrollBackLines .~ TL.empty) term
windowManipulation :: WindowManipulation -> Term -> Term
windowManipulation SaveIconAndWindowTitleOnStack = id -- TODO We could add a stack to our 'Term' data structure and save this
windowManipulation RestoreIconAndWindowTitleOnStack = id -- TODO We could add a stack to our 'Term' data structure and save this
deviceStatusReport :: DeviceStatusReport -> Term -> (ByteString, Term)
deviceStatusReport param term = case param of
StatusReport ->
let ok = "\ESC[0n"
in (ok, term)
ReportCursorPosition ->
let (line, col) = term ^. cursorPos
lineStr = BC8.pack (show (line + 1))
colStr = BC8.pack (show (col + 1))
cpr = "\ESC[" <> lineStr <> ";" <> colStr <> "R"
in (cpr, term)
sendDeviceAttributes :: Term -> (ByteString, Term)
sendDeviceAttributes term =
let identification = "\ESC[?1;2c" -- TODO or maybe "\ESC[?6c" ?
in (identification, term)
sendDeviceAttributesSecondary :: SendDeviceAttributesSecondary -> Term -> (ByteString, Term)
sendDeviceAttributesSecondary RequestTerminalIdentificationCode term =
let identification = "\ESC[>0;0;0c"
in (identification, term)
softTerminalReset :: Term -> Term
softTerminalReset term = mkTerm (term ^. numCols, term ^. numRows)
setMode :: Mode -> Term -> Term
setMode KeyboardActionMode = keyboardState %~ (\state -> state {keyboardState_Locked = True})
setMode InsertReplaceMode = insertMode .~ True
setMode SendReceive = error "TODO Send/receive (SRM) Not Supported"
setMode AutomaticNewlineNormalLinefeed = keyboardState %~ (\state -> state {keyboardState_CRLF = True})
resetMode :: Mode -> Term -> Term
resetMode KeyboardActionMode = keyboardState %~ (\state -> state {keyboardState_Locked = False})
resetMode InsertReplaceMode = insertMode .~ False
resetMode SendReceive = id
resetMode AutomaticNewlineNormalLinefeed = keyboardState %~ (\state -> state {keyboardState_CRLF = False})
processOsc :: OperatingSystemCommand -> Term -> (ByteString, Term)
processOsc (OSC_SetTitle _ True str) = nw $ windowTitle .~ str
processOsc (OSC_SetTitle _ False _) = nw id -- set window icon not supported
processOsc (OSC_ChangeTextForegroundColor _) = nw id -- Ignore
processOsc (OSC_ChangeTextBackgroundColor _) = nw id -- Ignore
processOsc OSC_RequestTextForegroundColor = \term -> ("\ESC]10;0\a", term)
processOsc OSC_RequestTextBackgroundColor = \term -> ("\ESC]11;0\a", term)
processOsc OSC_ResetTextCursorColor = nw id
insertBlankChars :: Int -> Term -> Term
insertBlankChars n term = (cursorLine %~ updateLine) term
where
n' = limit 0 (term ^. numCols - term ^. cursorPos . _2) n
col = term ^. cursorPos . _2
updateLine :: TermLine -> TermLine
updateLine termLine =
start <> blanks <> rest
where
start = VU.take col termLine
blanks = VU.replicate n' (' ', term ^. termAttrs)
rest = VU.slice col (term ^. numCols - col) termLine
insertBlankLines :: Int -> Term -> Term
insertBlankLines n term
| between (term ^. scrollTop, term ^. scrollBottom) (term ^. cursorPos . _1) = scrollDown (term ^. cursorPos . _1) n term
| otherwise = term
deleteChars :: Int -> Term -> Term
deleteChars n term = (cursorLine %~ updateLine) term
where
n' = limit 0 ((term ^. numCols) - (term ^. cursorPos . _2)) n
srcCol = col + n'
size = term ^. numCols - srcCol
col = term ^. cursorPos . _2
updateLine :: TermLine -> TermLine
updateLine termLine =
start <> slice <> VU.replicate n' (' ', term ^. termAttrs)
where
start = VU.take col termLine
slice = VU.slice srcCol size termLine
deleteLines :: Int -> Term -> Term
deleteLines n term
| between (term ^. scrollTop, term ^. scrollBottom) (term ^. cursorPos . _1) = scrollUp (term ^. cursorPos . _1) n term
| otherwise = term
setScrollingRegion :: Maybe Int -> Maybe Int -> Term -> Term
setScrollingRegion mbTop mbBottom term =
((scrollTop .~ top) >>> (scrollBottom .~ bottom)) term
where
top1 = case mbTop of
Nothing -> 0
Just t -> t - 1
bottom1 = case mbBottom of
Nothing -> term ^. numRows - 1
Just b -> b - 1
minY = 0
maxY = term ^. numRows - 1
top2 = limit minY maxY top1
bottom2 = limit minY maxY bottom1
(top, bottom) = if top2 > bottom2 then (bottom2, top2) else (top2, bottom2)
termProcessDecset :: DECPrivateMode -> Term -> Term
termProcessDecset DECPrivateMode.DECCKM = keyboardState %~ (\state -> state {keyboardState_DECCKM = True})
termProcessDecset DECPrivateMode.DECOM = (cursorState . origin .~ True) >>> (cursorMoveAbsoluteTo (0, 0))
termProcessDecset DECPrivateMode.ReportButtonPress = id
termProcessDecset DECPrivateMode.BracketedPasteMode = id -- TODO Set flag on 'Term'
termProcessDecset DECPrivateMode.SaveCursorAsInDECSCAndUseAlternateScreenBuffer = altScreenActive .~ True
termProcessDecset DECPrivateMode.Att610 = id -- TODO Set flag on 'Term'
termProcessDecset DECPrivateMode.DECTCEM = id -- TODO Set flag on 'Term'
termProcessDecset DECPrivateMode.DECAWM = modeWrap .~ True
termProcessDecset other = error $ "TODO: DECSET: " <> show other
termProcessDecrst :: DECPrivateMode -> Term -> Term
termProcessDecrst DECPrivateMode.DECCKM = keyboardState %~ (\state -> state {keyboardState_DECCKM = False})
termProcessDecrst DECPrivateMode.DECOM = (cursorState . origin .~ False) >>> (cursorMoveAbsoluteTo (0, 0))
termProcessDecrst DECPrivateMode.Att610 = id -- TODO Unset flag on 'Term'
termProcessDecrst DECPrivateMode.DECTCEM = id -- TODO Unset flag on 'Term'
termProcessDecrst DECPrivateMode.DECCOLM = id -- Ignored
termProcessDecrst DECPrivateMode.ReportButtonPress = id
termProcessDecrst DECPrivateMode.BracketedPasteMode = id -- TODO Unset flag on 'Term'
termProcessDecrst DECPrivateMode.SaveCursorAsInDECSCAndUseAlternateScreenBuffer = altScreenActive .~ False
termProcessDecrst DECPrivateMode.DECAWM = modeWrap .~ False
termProcessDecrst DECPrivateMode.EnableAllMouseMotions = id
termProcessDecrst DECPrivateMode.ReportMotionOnButtonPress = id
termProcessDecrst other = error $ "TODO: DECRST: " <> show other
termProcessSGR :: SGR.SGR -> Term -> Term
termProcessSGR = over termAttrs . applySGR
-- | For absolute user moves, when DECOM is set
cursorMoveAbsoluteTo :: (Int, Int) -> Term -> Term
cursorMoveAbsoluteTo (row, col) term =
cursorMoveTo (row + rowOffset, col) term
where
rowOffset
| term ^. cursorState . origin = term ^. scrollTop
| otherwise = 0
cursorMoveTo :: (Int, Int) -> Term -> Term
cursorMoveTo (row, col) term =
( cursorPos . _1 .~ (limit minY maxY row)
>>> cursorPos . _2 .~ (limit minX maxX col)
>>> cursorState . wrapNext .~ False
)
term
where
minX = 0
maxX = term ^. numCols - 1
(minY, maxY)
| term ^. cursorState . origin = (term ^. scrollTop, term ^. scrollBottom)
| otherwise = (0, term ^. numRows - 1)
applySGR :: SGR.SGR -> Attrs -> Attrs
applySGR SGR.Reset = const blankAttrs
applySGR (SGR.SetConsoleIntensity intensity) = set attrsIntensity intensity
applySGR (SGR.SetItalicized _) = id -- TODO Not Supported
applySGR (SGR.SetUnderlining underlining) = set attrsUnderline underlining
applySGR (SGR.SetBlinkSpeed _) = id -- TODO Not Supported
applySGR (SGR.SetVisible _) = id -- TODO Not Supported
applySGR (SGR.SetSwapForegroundBackground _) = id -- TODO Not Supported
applySGR (SGR.SetColor SGR.Foreground intensity color) = set attrsFg (Just (intensity, color))
applySGR (SGR.SetColor SGR.Background intensity color) = set attrsBg (Just (intensity, color))
applySGR (SGR.SetRGBColor _ _) = id -- TODO Not Supported
applySGR (SGR.SetPaletteColor _ _) = id -- TODO Not Supported
applySGR (SGR.SetDefaultColor SGR.Foreground) = set attrsFg Nothing
applySGR (SGR.SetDefaultColor SGR.Background) = set attrsBg Nothing
processVisibleChar :: Char -> Term -> Term
processVisibleChar c =
moveCursorBefore
>>> moveChars
>>> moveCursorDown
>>> setChar
>>> moveCursorAfter
where
moveCursorBefore :: Term -> Term
moveCursorBefore term
| (term ^. modeWrap) && (term ^. cursorState ^. wrapNext) = addNewline True term
| otherwise = term
moveChars :: Term -> Term
moveChars term
| (term ^. insertMode) && (col < (term ^. numCols) - 1) =
( cursorLine
%~ ( \line ->
VU.take
(term ^. numCols)
(VU.take col line <> VU.singleton (' ', 0) <> VU.drop col line)
)
)
term
| otherwise = term
where
col = term ^. cursorPos . _2
moveCursorDown :: Term -> Term
moveCursorDown term
| term ^. cursorPos . _2 > (term ^. numCols) - 1 = addNewline True term
| otherwise = term
setChar :: Term -> Term
setChar term = ((cursorLine . (vuIndex (term ^. cursorPos . _2))) .~ (c, term ^. termAttrs)) term
moveCursorAfter :: Term -> Term
moveCursorAfter term
| term ^. cursorPos . _2 < (term ^. numCols) - 1 = cursorMoveTo (term ^. cursorPos . _1, (term ^. cursorPos . _2) + 1) term
| otherwise = ((cursorState . wrapNext) .~ True) term
addNewline ::
-- | first column
Bool ->
Term ->
Term
addNewline firstCol = doScrollUp >>> moveCursor
where
doScrollUp :: Term -> Term
doScrollUp term
| term ^. cursorPos . _1 == term ^. scrollBottom = scrollUp (term ^. scrollTop) 1 term
| otherwise = term
moveCursor :: Term -> Term
moveCursor term = cursorMoveTo (newRow, newCol) term
where
newRow
| term ^. cursorPos . _1 == term ^. scrollBottom = term ^. cursorPos . _1
| otherwise = (term ^. cursorPos . _1) + 1
newCol
| firstCol = 0
| otherwise = term ^. cursorPos . _2
scrollDown :: Int -> Int -> Term -> Term
scrollDown orig n term = scrollLines term
where
n' = limit 0 (term ^. scrollBottom - orig + 1) n
scrollLines =
activeScreen
%~ ( \lines ->
TL.take orig lines
<> TL.replicate n' newBlankLine
<> TL.take ((term ^. scrollBottom) - orig - n' + 1) (TL.drop orig lines)
<> TL.drop ((term ^. scrollBottom) + 1) lines
)
newBlankLine = VU.replicate (term ^. numCols) (' ', term ^. termAttrs)
scrollUp :: Int -> Int -> Term -> Term
scrollUp orig n term =
(copyLinesToScrollBack >>> scrollLines) term
where
n' = limit 0 (term ^. scrollBottom - orig + 1) n
copyLinesToScrollBack
| not (term ^. altScreenActive) && orig == 0 = addScrollBackLines (TL.take n' (term ^. termScreen))
| otherwise = id
scrollLines =
activeScreen
%~ ( \lines ->
TL.take orig lines
<> TL.take ((term ^. scrollBottom) - orig - n' + 1) (TL.drop (orig + n') lines)
<> TL.replicate n' newBlankLine
<> TL.drop ((term ^. scrollBottom) + 1) lines
)
newBlankLine = VU.replicate (term ^. numCols) (' ', term ^. termAttrs)
clearRegion :: (Int, Int) -> (Int, Int) -> Term -> Term
clearRegion (line1, col1) (line2, col2) term =
foldl'
(\t line -> clearRow line (limit minX maxX col1') (limit minX maxX col2') t)
term
[(limit minY maxY line1') .. (limit minY maxY line2')]
where
line1' = min line1 line2
line2' = max line1 line2
col1' = min col1 col2
col2' = max col1 col2
minX = 0
maxX = term ^. numCols - 1
minY = 0
maxY = term ^. numRows - 1
clearRow :: Int -> Int -> Int -> Term -> Term
clearRow line startCol endCol term =
foldl'
(\t col -> (activeScreen . TL.vIndex line . vuIndex col .~ (' ', attrs)) t)
term
[startCol .. endCol]
where
attrs = term ^. termAttrs
limit ::
-- | minimum allowed value
Int ->
-- | maximum allowed value
Int ->
-- | value to limit
Int ->
Int
limit minVal maxVal val
| val < minVal = minVal
| val > maxVal = maxVal
| otherwise = val
between :: Ord a => (a, a) -> a -> Bool
between (low, high) val = val >= low && val <= high