hs-term-emulator-0.1.0.0: src/System/Terminal/Emulator/Term/Resize.hs
module System.Terminal.Emulator.Term.Resize
( resizeTerm,
)
where
import Control.Category ((>>>))
import Control.Exception (assert)
import Control.Lens
import qualified Data.Vector.Unboxed as VU
import System.Terminal.Emulator.Term (Term, addScrollBackLines, altScreenActive, cursorPos, numCols, numRows, scrollBackLines, scrollBottom, scrollTop, termAlt, termAttrs, termScreen)
import System.Terminal.Emulator.TermLines (TermLine, TermLines)
import qualified System.Terminal.Emulator.TermLines as TL
import Prelude hiding (lines)
-- | This should be called when the user resizes the terminal window.
--
-- You should also call 'System.Posix.Pty.resizePty', but only afterwards
--
-- The tuple is in the shape @(newWidth, newHeight)@, both must be positive
resizeTerm :: Term -> (Int, Int) -> Term
resizeTerm term (newWidth, newHeight) =
assert (newWidth > 0) $
assert (newHeight > 0) $
( resizeTermWidth newWidth
>>> resizeTermHeight newHeight
>>> scrollTop .~ 0
>>> scrollBottom .~ (newHeight - 1)
)
term
-- Internal function. Resize the terminal, only changing the width.
resizeTermWidth :: Int -> Term -> Term
resizeTermWidth newWidth term =
( numCols .~ newWidth
>>> termScreen %~ fmap adjustLine
>>> termAlt %~ fmap adjustLine
>>> scrollBackLines %~ fmap adjustLine
>>> cursorPos . _2 %~ min (newWidth - 1)
)
term
where
oldWidth = term ^. numCols
expandLine :: TermLine -> TermLine
expandLine = (<> (VU.replicate (newWidth - oldWidth) ((' ', 0))))
shrinkLine :: TermLine -> TermLine
shrinkLine = VU.take newWidth
adjustLine :: TermLine -> TermLine
adjustLine
| newWidth > oldWidth = expandLine
| otherwise = shrinkLine
-- Internal function. Resize the terminal, only changing the height.
resizeTermHeight :: Int -> Term -> Term
resizeTermHeight newHeight term
| newHeight >= oldHeight = resizeTermHeight' newHeight term
| otherwise =
let term' = truncateTermScreenBottom term (oldHeight - newHeight)
in resizeTermHeight' newHeight term'
where
oldHeight = term ^. numRows
resizeTermHeight' :: Int -> Term -> Term
resizeTermHeight' newHeight term =
( numRows .~ newHeight
>>> adjustScreen
>>> termAlt %~ adjustAltScreen
>>> cursorPos . _1 %~ min (newHeight - 1)
)
term
where
oldHeight = term ^. numRows
newBlankLine = VU.replicate (term ^. numCols) (' ', term ^. termAttrs)
expandAltScreen :: TermLines -> TermLines
expandAltScreen = (<> (TL.replicate (newHeight - oldHeight) newBlankLine))
shrinkAltScreen :: TermLines -> TermLines
shrinkAltScreen = TL.take newHeight
adjustAltScreen :: TermLines -> TermLines
adjustAltScreen
| newHeight > oldHeight = expandAltScreen
| otherwise = shrinkAltScreen
expandScreen :: Term -> Term
expandScreen =
( termScreen
%~ ( \lines ->
TL.takeLast numHistoryLines (term ^. scrollBackLines)
<> lines
<> TL.replicate numNewBlankLines newBlankLine
)
)
>>> scrollBackLines %~ TL.dropLast numHistoryLines
>>> moveCursorDown
where
numHistoryLines = min (newHeight - oldHeight) (TL.length (term ^. scrollBackLines))
numNewBlankLines = (newHeight - oldHeight) - numHistoryLines
moveCursorDown :: Term -> Term
moveCursorDown
| term ^. altScreenActive = id
| otherwise = cursorPos . _1 %~ (+ numHistoryLines)
shrinkScreen :: Term -> Term
shrinkScreen =
(termScreen %~ TL.takeLast newHeight)
>>> addScrollBackLines (TL.take numShrunkLines (term ^. termScreen))
>>> moveCursorUp
where
numShrunkLines = oldHeight - newHeight
moveCursorUp :: Term -> Term
moveCursorUp
| term ^. altScreenActive = id
| otherwise = cursorPos . _1 %~ (\y -> max 0 (y - numShrunkLines))
adjustScreen
| newHeight > oldHeight = expandScreen
| otherwise = shrinkScreen
-- | Chop off up to @n@ lines from the bottom of the main screen (only if they
-- are blank and not occupied by the cursor).
--
-- Also modifies the vertical size of the screen according to the number of
-- lines removed (numLines)
truncateTermScreenBottom :: Term -> Int -> Term
truncateTermScreenBottom term numLines
| numLines == 0 = term
| term ^. altScreenActive = term
| term ^. cursorPos . _1 == term ^. numRows - 1 = term
| not (lineIsBlank lastLine) = term
| otherwise =
let term' =
( (termScreen %~ (TL.dropLast 1))
>>> (numRows %~ (subtract 1))
)
term
in truncateTermScreenBottom term' (numLines - 1)
where
lastLine = TL.last (term ^. termScreen)
lineIsBlank :: TermLine -> Bool
lineIsBlank = VU.all (== (' ', 0))