hledger-iadd-1.1.3: src/Data/Text/Zipper/Generic/Words.hs
-- | Implements word movements for "Data.Text.Zipper"
--
-- Since these rely on character classes, the 'TZ.GenericTextZipper' constraint
-- is required.
module Data.Text.Zipper.Generic.Words
( moveWordLeft
, moveWordRight
, deletePrevWord
, deleteWord
) where
import Data.Char
import Data.Text.Zipper
import qualified Data.Text.Zipper.Generic as TZ
-- | Move one word to the left
--
-- A word is defined as a consecutive string not satisfying isSpace. This
-- function always leaves the cursor at the beginning of a word (except at the
-- very start of the text).
moveWordLeft :: TZ.GenericTextZipper a => TextZipper a -> TextZipper a
moveWordLeft = doWordLeft False moveLeft
-- | Delete the previous word
--
-- Does the same as 'moveWordLeft' but deletes characters instead of simply
-- moving past them.
deletePrevWord :: (Eq a, TZ.GenericTextZipper a) => TextZipper a -> TextZipper a
deletePrevWord = doWordLeft False deletePrevChar
doWordLeft :: TZ.GenericTextZipper a => Bool -> (TextZipper a -> TextZipper a) -> TextZipper a -> TextZipper a
doWordLeft inWord transform zipper = case charToTheLeft zipper of
Nothing -> zipper -- start of text
Just c
| isSpace c && not inWord
-> doWordLeft False transform (transform zipper)
| not (isSpace c) && not inWord
-> doWordLeft True transform zipper -- switch to skipping letters
| not (isSpace c) && inWord
-> doWordLeft True transform (transform zipper)
| otherwise
-> zipper -- Done
-- | Move one word to the right
--
-- A word is defined as a consecutive string not satisfying isSpace. This
-- function always leaves the cursor at the end of a word (except at the very
-- end of the text).
moveWordRight :: TZ.GenericTextZipper a => TextZipper a -> TextZipper a
moveWordRight = doWordRight False moveRight
-- | Delete the next word
--
-- Does the same as 'moveWordRight' but deletes characters instead of simply
-- moving past them.
deleteWord :: TZ.GenericTextZipper a => TextZipper a -> TextZipper a
deleteWord = doWordRight False deleteChar
doWordRight :: TZ.GenericTextZipper a => Bool -> (TextZipper a -> TextZipper a) -> TextZipper a -> TextZipper a
doWordRight inWord transform zipper = case charToTheRight zipper of
Nothing -> zipper -- end of text
Just c
| isSpace c && not inWord
-> doWordRight False transform (transform zipper)
| not (isSpace c) && not inWord
-> doWordRight True transform zipper -- switch to skipping letters
| not (isSpace c) && inWord
-> doWordRight True transform (transform zipper)
| otherwise
-> zipper -- Done
-- Helpers
charToTheLeft :: TZ.GenericTextZipper a => TextZipper a -> Maybe Char
charToTheLeft zipper = case cursorPosition zipper of
(0, 0) -> Nothing -- Very start of text, no char left
(_, 0) -> Just '\n' -- Start of line, simulate newline
(_, x) -> Just (TZ.toList (currentLine zipper) !! (x-1))
charToTheRight :: TZ.GenericTextZipper a => TextZipper a -> Maybe Char
charToTheRight zipper
| null (getText zipper) = Nothing
| otherwise =
let
(row, col) = cursorPosition zipper
content = getText zipper
curLine = content !! row
numLines = length content
in
if row == numLines - 1 && col == (TZ.length curLine) then
Nothing -- very end
else if col == (TZ.length curLine) then
Just '\n' -- simulate newline
else
Just (TZ.toList curLine !! col)