packages feed

spade-0.1.0.0: src/UI/Widgets/Editor/Cursor.hs

module UI.Widgets.Editor.Cursor where

import qualified Data.List as DL

import Common

offsetToScreenPos
  :: Int  -- ^ Content width, where lines longer are wrapped.
  -> [Int] -- ^ Line lengths
  -> Int -- ^ Linear offset into cursor position. This is real offset that starts at 0
  -> Maybe (ScreenPos, Int) -- Screen pos and actual line in content where cursor is at
offsetToScreenPos _ [] _ = Just $ (ScreenPos 0 0, 1)
offsetToScreenPos _ _ 0 = Just $ (ScreenPos 0 0, 1)
offsetToScreenPos width lns offset =
    let (sp, _, _, _) = DL.foldl' foldFn (Nothing, 0, 0, 1) lns in sp
  where
    foldFn :: (Maybe (ScreenPos, Int), Int, Int, Int) -> Int -> (Maybe (ScreenPos, Int), Int, Int, Int)
    foldFn a@(Just _, _, _, _) _ = a
    foldFn (Nothing, leftOffset, lineCount, realLineCount) lineLength =
        case DL.foldl' foldFn' (Nothing, leftOffset, lineCount) (brokenLines width lineLength) of
          (Just a, _, _) -> (Just (a, realLineCount), 0, 0, 0)
          (Nothing, lo, lc) -> (Nothing, lo, lc, realLineCount+1)

    foldFn' :: (Maybe ScreenPos, Int, Int) -> Int -> (Maybe ScreenPos, Int, Int)
    foldFn' a@(Just _, _, _) _ = a
    foldFn' (Nothing, leftOffset, lineCount) lineLength =
      let rightOffset = leftOffset + lineLength - 1
      in if offset >= leftOffset && offset <= rightOffset
          then (Just $ ScreenPos (offset - leftOffset) lineCount, 0, 0)
          else (Nothing, rightOffset+1, lineCount+1)

brokenLines :: Int -> Int -> [Int]
brokenLines _ 0 = [1]
brokenLines width ll = let
  (flc, lll) = divMod ll width
  in if lll > 0
    -- We add one char to the very endline to signify the ending newline.
    -- and to allow positioning of cursor the very last char in a line.
    then replicate flc width  <> [lll+1]
    else replicate (flc - 1) width  <> [width + 1]

screenPosToOffset
  :: [Int] -- ^ Line lengths
  -> Int -- ^ Content width, where lines longer are wrapped.
  -> ScreenPos -- ^ Screen position relative to top left edget of content.
  -> Maybe (Int, Int, ScreenPos) -- Offset starting at zero, cursor line, and adjusted ScreenPos so cursor is at end of line
screenPosToOffset lns width (ScreenPos x y) =
    let (sp, _, _, _) = DL.foldl' foldFn (Nothing, 0, 0, 1) lns in sp
  where
    foldFn :: (Maybe (Int, Int, ScreenPos), Int, Int, Int) -> Int -> (Maybe (Int, Int, ScreenPos), Int, Int, Int)
    foldFn a@(Just _, _, _, _) _ = a
    foldFn (Nothing, lineCount, leftOffset, currentLine) lineLength =
        case DL.foldl' foldFn' (Nothing, lineCount, leftOffset) (brokenLines width lineLength) of
          (Nothing, a, b) -> (Nothing, a, b, currentLine + 1)
          (Just (a, b), _, _) -> (Just (a, currentLine, b), 0, 0, 0)

    foldFn'
      :: ( Maybe (Int, ScreenPos) -- Result
         , Int -- current screen line no
         , Int -- current offset at line start
         )
      -> Int -- current line width
      -> (Maybe (Int, ScreenPos), Int, Int)
    foldFn' a@(Just _, _, _) _ = a
    foldFn' (Nothing, lno, offset) lw =
      if lno == y
        then let
          aOffset = min (lw - 1) x
          in (Just (offset + aOffset, ScreenPos aOffset y), 0, 0)
        else (Nothing, lno+1, offset+lw)