diff options
| author | tv <tv@krebsco.de> | 2026-03-05 17:47:38 +0100 |
|---|---|---|
| committer | tv <tv@krebsco.de> | 2026-03-06 20:42:17 +0100 |
| commit | c6a68227069d33fdcaa07295322974ab324000c0 (patch) | |
| tree | a9abefbdc4c634a1d19b005662ffdc70ef14a509 /TextViewport.hs | |
| parent | e66e5ee8aa62153b83bc2ae201a8ad44d90e08b8 (diff) | |
bump
Diffstat (limited to 'TextViewport.hs')
| -rw-r--r-- | TextViewport.hs | 69 |
1 files changed, 35 insertions, 34 deletions
diff --git a/TextViewport.hs b/TextViewport.hs index 927ba8b..ea075f4 100644 --- a/TextViewport.hs +++ b/TextViewport.hs @@ -3,6 +3,7 @@ module TextViewport ( Item(..) , Buffer(..) + , WrapMode(..) , Viewport(..) , RenderedLine(..) , renderBuffer @@ -19,77 +20,77 @@ import qualified Data.Text as T -- Logical model -------------------------------------------------------------------------------- --- | A single buffer item. Arbitrary text, may contain hard line breaks. newtype Item = Item { unItem :: Text } - --- | Oldest item first, newest last. No rendering assumptions here. newtype Buffer = Buffer { unBuffer :: [Item] } -------------------------------------------------------------------------------- --- Rendered representation +-- Rendering -------------------------------------------------------------------------------- --- | A single line after splitting on hard breaks and cropping to viewport width. --- This is a *physical* line: no wrapping, only truncation. +-- | Rendering mode: either truncate long lines or wrap them. +data WrapMode + = NoWrap -- ^ Hard truncation at viewport width. + | Wrap -- ^ Soft line breaks inserted at viewport width. + deriving (Eq, Show) + newtype RenderedLine = RenderedLine { unRenderedLine :: Text } deriving (Eq, Show) --- | Render the entire buffer into a flat list of cropped lines. --- This is conceptually stable: scrolling operates only on this list. -renderBuffer :: Int -- ^ viewport width (characters) - -> Buffer - -> [RenderedLine] -renderBuffer w (Buffer items) = +-- | Render the buffer into a flat list of physical lines. +-- Wrapping expands logical lines; truncation keeps one line per logical line. +renderBuffer :: WrapMode -> Int -> Buffer -> [RenderedLine] +renderBuffer mode w (Buffer items) = concatMap renderItem items where renderItem (Item t) = - let ls = T.splitOn "\n" t - in map (RenderedLine . crop) ls - - crop = T.take w -- hard truncation, no wrapping + concatMap renderLogicalLine (T.splitOn "\n" t) + + renderLogicalLine :: Text -> [RenderedLine] + renderLogicalLine line = + case mode of + NoWrap -> [RenderedLine (T.take w line)] + Wrap -> map RenderedLine (wrapText w line) + +-- | Split a line into chunks of at most width characters. +-- This is soft wrapping: no hyphenation, no word-boundary logic. +wrapText :: Int -> Text -> [Text] +wrapText w t + | T.null t = [""] + | otherwise = go t + where + go s + | T.null s = [] + | otherwise = + let (chunk, rest) = T.splitAt w s + in chunk : go rest -------------------------------------------------------------------------------- --- Viewport state +-- Viewport -------------------------------------------------------------------------------- --- | Viewport is defined by width, height, and a scroll offset into the --- rendered line stream. Offset is always a line index, never an item index. data Viewport = Viewport { vpWidth :: !Int , vpHeight :: !Int - , vpOffset :: !Int -- ^ index into rendered lines; 0 = top of buffer + , vpOffset :: !Int } deriving (Eq, Show) --- | Construct a viewport positioned at the bottom (newest content). defaultViewport :: Int -> Int -> [RenderedLine] -> Viewport defaultViewport w h rendered = let total = length rendered off = max 0 (total - h) in Viewport w h off --------------------------------------------------------------------------------- --- Scrolling --------------------------------------------------------------------------------- - --- | Scroll upward by k lines. Clamped at 0. scrollUp :: Int -> Viewport -> Viewport scrollUp k vp = vp { vpOffset = max 0 (vpOffset vp - k) } --- | Scroll downward by k lines. Clamped at the last fully visible window. scrollDown :: Int -> [RenderedLine] -> Viewport -> Viewport scrollDown k rendered vp = - let total = length rendered + let total = length rendered maxOff = max 0 (total - vpHeight vp) newOff = min maxOff (vpOffset vp + k) in vp { vpOffset = newOff } --------------------------------------------------------------------------------- --- Visibility --------------------------------------------------------------------------------- - --- | Extract the currently visible slice of rendered lines. --- The viewport height determines the slice length. visibleLines :: [RenderedLine] -> Viewport -> [RenderedLine] visibleLines rendered vp = take (vpHeight vp) . drop (vpOffset vp) $ rendered |
