packages feed

zwirn-0.2.3.1: app/zwirnmill/Editor/Selection.hs

module Editor.Selection where

import Data.Maybe (fromMaybe)
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Zipper as Z
import Editor.Core
import Editor.Util
import Lens.Micro

getSelectionRange :: EditorState -> Maybe ((Int, Int), (Int, Int))
getSelectionRange es = getSelectionOrdered (currentCursor es) (es ^. esSelection)

getSelectionOrdered :: (Int, Int) -> Maybe (Int, Int) -> Maybe ((Int, Int), (Int, Int))
getSelectionOrdered cursor msel = normaliseSelection cursor <$> msel
  where
    normaliseSelection (r1, c1) (r2, c2)
      | r1 < r2 = ((r1, c1), (r2, c2))
      | r1 == r2 && c1 <= c2 = ((r1, c1), (r2, c2))
      | otherwise = ((r2, c2), (r1, c1 - 1))

inSelection :: (Int, Int) -> (Int, Int) -> Maybe (Int, Int) -> Bool
inSelection (a, b) cursor msel = case getSelectionOrdered cursor msel of
  Nothing -> False
  Just (x, y) -> inSpan (a, b) x y

deleteSelection :: EditorState -> EditorState
deleteSelection es = case getSelectionRange es of
  Nothing -> es
  Just (p, end) -> clearSelection $ es & esZipper .~ final
    where
      z = es ^. esZipper
      movedZ = Z.moveCursor p z
      charCount = totalCharsBetween movedZ end + 1
      final = iterate Z.deleteChar movedZ !! charCount

totalCharsBetween :: Z.TextZipper Text -> (Int, Int) -> Int
totalCharsBetween tz target = go 0 tz
  where
    go count currentTz
      | Z.cursorPosition currentTz == target = count
      | currentTz == Z.moveRight currentTz = count
      | otherwise = go (count + 1) (Z.moveRight currentTz)

selectedText :: EditorState -> Maybe Text
selectedText es = case getSelectionRange es of
  Nothing -> Nothing
  Just (begin, end) -> Just $ fst $ go ("", Z.moveCursor begin $ es ^. esZipper)
    where
      go (accum, z)
        | Z.cursorPosition z == end = (T.reverse (T.cons c accum), z)
        | otherwise = go (T.cons c accum, Z.moveRight z)
        where
          c = fromMaybe '\n' (Z.currentChar z)

extendSelection :: (Z.TextZipper Text -> Z.TextZipper Text) -> EditorState -> EditorState
extendSelection f es =
  let es' = withZipper f es
      cursor = currentCursor es
      next = currentCursor es'
      actual = if fst cursor > fst next || snd next < snd cursor then (fst cursor, snd cursor - 1) else cursor
      anchor = fromMaybe actual (es' ^. esSelection)
   in es' & esSelection ?~ anchor

extendSelectionUp :: EditorState -> EditorState
extendSelectionUp = extendSelection Z.moveUp

extendSelectionDown :: EditorState -> EditorState
extendSelectionDown = extendSelection Z.moveDown

extendSelectionLeft :: EditorState -> EditorState
extendSelectionLeft = extendSelection Z.moveLeft

extendSelectionRight :: EditorState -> EditorState
extendSelectionRight = extendSelection Z.moveRight

extendSelectionLineStart :: EditorState -> EditorState
extendSelectionLineStart = extendSelection Z.gotoBOL

extendSelectionLineEnd :: EditorState -> EditorState
extendSelectionLineEnd = extendSelection Z.gotoEOL

extendSelectionTo :: (Int, Int) -> EditorState -> EditorState
extendSelectionTo p = extendSelection (Z.moveCursor p)

selectAll :: EditorState -> EditorState
selectAll es =
  es
    & esZipper %~ Z.gotoEOF
    & esSelection ?~ (0, 0)