11module DOM.Util.TextCursor
22 ( TextCursor (..)
3+ , content , length , null , empty , single
34 , _before , _selected , _after
5+ , atStart , atEnd , allSelected
6+ , isCursor , cursorAtStart , cursorAtEnd
7+ , isSelection , selectionAtStart , selectionAtEnd
48 , selectAll , moveCursorToStart , moveCursorToEnd
5- , content , empty , single
69 , insert , mapAll
710 ) where
811
912import Prelude
13+ import Data.String (length , null ) as S
1014import Data.Symbol (SProxy (..))
1115import Data.Newtype (class Newtype )
1216import Data.Lens (Lens' , (.~))
@@ -25,15 +29,56 @@ newtype TextCursor = TextCursor
2529
2630derive instance textCursorNewtype :: Newtype TextCursor _
2731
32+ data ContentTest = Null | Any | Full
33+ type ContentPredicate =
34+ { before :: ContentTest
35+ , selected :: ContentTest
36+ , after :: ContentTest
37+ }
38+ testContent :: ContentPredicate -> TextCursor -> Boolean
39+ testContent
40+ { before, selected, after }
41+ (TextCursor { before: b, selected: s, after: a }) =
42+ test before b && test selected s && test after a
43+ where
44+ test Any _ = true
45+ test Null t = S .null t
46+ test Full t = not (S .null t)
47+
2848-- | Get the current text in the field. (Everything before, inside, and after
2949-- | the selection.)
3050content :: TextCursor -> String
3151content (TextCursor { before, selected, after }) = before <> selected <> after
3252
53+ -- | Get the length of the content of a `TextCursor`.
54+ -- check: length textcursor == length (content textcursor)
55+ length :: TextCursor -> Int
56+ length (TextCursor { before, selected, after }) =
57+ S .length before + S .length selected + S .length after
58+
3359-- | An empty input field. No selection.
3460empty :: TextCursor
3561empty = TextCursor { before: " " , selected: " " , after: " " }
3662
63+ -- | Test whether the content of a `TextCursor` is empty.
64+ -- check: null textcursor == null (content textcursor)
65+ null :: TextCursor -> Boolean
66+ null = testContent { before: Null , selected: Null , after: Null }
67+
68+ -- | Test whether the selection is collapsed, i.e. acts like a cursor.
69+ isCursor :: TextCursor -> Boolean
70+ isCursor = testContent { before: Any , selected: Null , after: Any }
71+
72+ -- | Test whether some text is selected.
73+ isSelection :: TextCursor -> Boolean
74+ isSelection = testContent { before: Any , selected: Full , after: Any }
75+
76+ -- | Apply a `Lens` setting a value to an empty `TextCursor`. When used with
77+ -- | `_before`, `_selected`, or `_after` this will provide a `TextCursor` with
78+ -- | only one non-empty field.
79+ single :: Lens' TextCursor String -> String -> TextCursor
80+ single l v = l .~ v $ empty
81+
3782-- | Lens for the text before the selection. Empty if the cursor is at the
3883-- | beginning or the selection starts from the beginning.
3984_before :: Lens' TextCursor String
@@ -48,12 +93,6 @@ _selected = _Newtype <<< prop (SProxy :: SProxy "selected")
4893_after :: Lens' TextCursor String
4994_after = _Newtype <<< prop (SProxy :: SProxy " after" )
5095
51- -- | Apply a `Lens` setting a value to an empty `TextCursor`. When used with
52- -- | `beforeL`, `selectedL`, or `afterL` this will provide a `TextCursor` with
53- -- | only one non-empty field.
54- single :: Lens' TextCursor String -> String -> TextCursor
55- single l v = l .~ v $ empty
56-
5796-- | Map all three fields of the `TextCursor` with an endomorphism, performing
5897-- | a replacement or other transformation such as normalization.
5998mapAll :: (String -> String ) -> TextCursor -> TextCursor
@@ -65,6 +104,7 @@ mapAll f (TextCursor { before, selected, after }) = TextCursor
65104
66105-- | Move the cursor to the start of a field, preserving the overall text
67106-- | content.
107+ -- Content-preserving idempotent endomorphism
68108moveCursorToStart :: TextCursor -> TextCursor
69109moveCursorToStart tc = TextCursor
70110 { before: " "
@@ -75,6 +115,7 @@ moveCursorToStart tc = TextCursor
75115-- | Select all of the text in a field.
76116-- |
77117-- | Note: selection direction is not specified.
118+ -- Content-preserving idempotent endomorphism
78119selectAll :: TextCursor -> TextCursor
79120selectAll tc = TextCursor
80121 { before: " "
@@ -83,15 +124,47 @@ selectAll tc = TextCursor
83124 }
84125
85126-- | Move the cursor to the end of a field, preserving the overall text content.
127+ -- Content-preserving idempotent endomorphism
86128moveCursorToEnd :: TextCursor -> TextCursor
87129moveCursorToEnd tc = TextCursor
88130 { before: content tc
89131 , selected: " "
90132 , after: " "
91133 }
92134
135+ -- | Test whether there is a selection that ranges to the start.
136+ selectionAtStart :: TextCursor -> Boolean
137+ selectionAtStart = isSelection && atStart
138+
139+ -- | Test whether the cursor is at the start with no selection.
140+ cursorAtStart :: TextCursor -> Boolean
141+ cursorAtStart = isCursor && atStart
142+
143+ -- | Test whether the cursor or selection touches the start.
144+ atStart :: TextCursor -> Boolean
145+ atStart = testContent { before: Null , selected: Null , after: Any }
146+
147+ -- | Test whether the cursor has selected the whole field.
148+ allSelected :: TextCursor -> Boolean
149+ allSelected = testContent { before: Null , selected: Any , after: Null }
150+
151+ -- | Test whether the cursor or selection reaches the end.
152+ atEnd :: TextCursor -> Boolean
153+ atEnd = testContent { before: Any , selected: Any , after: Null }
154+
155+ -- | Test whether the cursor is at the end with no selection.
156+ cursorAtEnd :: TextCursor -> Boolean
157+ cursorAtEnd = isCursor && atEnd
158+
159+ -- | Test whether there is a selection that reaches the end.
160+ selectionAtEnd :: TextCursor -> Boolean
161+ selectionAtEnd = isSelection && atEnd
162+
93163-- | Insert a string at the cursor position. If text is selected, the insertion
94164-- | will be part of the selection. Otherwise it is inserted before the cursor.
165+ -- check:
166+ -- length (insert insertion textcursor)
167+ -- == length insertion + length textcursor
95168insert :: String -> TextCursor -> TextCursor
96169insert insertion = case _ of
97170 TextCursor { before, selected: " " , after } -> TextCursor
0 commit comments