github.com/aretext/aretext@v1.3.0/state/cursor_test.go (about) 1 package state 2 3 import ( 4 "testing" 5 6 "github.com/stretchr/testify/assert" 7 "github.com/stretchr/testify/require" 8 9 "github.com/aretext/aretext/locate" 10 "github.com/aretext/aretext/selection" 11 "github.com/aretext/aretext/text" 12 ) 13 14 func TestMoveCursor(t *testing.T) { 15 textTree, err := text.NewTreeFromString("abcd") 16 require.NoError(t, err) 17 state := NewEditorState(100, 100, nil, nil) 18 state.documentBuffer.textTree = textTree 19 state.documentBuffer.cursor.position = 2 20 MoveCursor(state, func(params LocatorParams) uint64 { 21 return locate.NextCharInLine(params.TextTree, 1, false, params.CursorPos) 22 }) 23 assert.Equal(t, uint64(3), state.documentBuffer.cursor.position) 24 } 25 26 func TestMoveCursorToLineAbove(t *testing.T) { 27 testCases := []struct { 28 name string 29 inputString string 30 count uint64 31 initialCursor cursorState 32 expectedCursor cursorState 33 }{ 34 { 35 name: "empty string, move up one line", 36 inputString: "", 37 count: 1, 38 initialCursor: cursorState{position: 0}, 39 expectedCursor: cursorState{position: 0}, 40 }, 41 { 42 name: "single line, move up one line", 43 inputString: "abcdefgh", 44 count: 1, 45 initialCursor: cursorState{position: 3}, 46 expectedCursor: cursorState{position: 3}, 47 }, 48 { 49 name: "single line, move up one line with logical offset", 50 inputString: "abcdefgh", 51 count: 1, 52 initialCursor: cursorState{position: 7, logicalOffset: 4}, 53 expectedCursor: cursorState{position: 7, logicalOffset: 4}, 54 }, 55 { 56 name: "two lines, move up one line at start of line", 57 inputString: "abcdefgh\nijklm\nopqrs", 58 count: 1, 59 initialCursor: cursorState{position: 15}, 60 expectedCursor: cursorState{position: 9}, 61 }, 62 { 63 name: "two lines, move up at same offset", 64 inputString: "abcdefgh\nijklmnop", 65 count: 1, 66 initialCursor: cursorState{position: 11}, 67 expectedCursor: cursorState{position: 2}, 68 }, 69 { 70 name: "two lines, move up from shorter line to longer line", 71 inputString: "abcdefgh\nijk", 72 count: 1, 73 initialCursor: cursorState{position: 11}, 74 expectedCursor: cursorState{position: 2}, 75 }, 76 { 77 name: "two lines, move up from shorter line with logical offset to longer line", 78 inputString: "abcdefgh\nijk", 79 count: 1, 80 initialCursor: cursorState{position: 11, logicalOffset: 2}, 81 expectedCursor: cursorState{position: 4}, 82 }, 83 { 84 name: "two lines, move up from longer line to shorter line", 85 inputString: "abc\nefghijkl", 86 count: 1, 87 initialCursor: cursorState{position: 9}, 88 expectedCursor: cursorState{position: 2, logicalOffset: 3}, 89 }, 90 { 91 name: "two lines, move up from longer line with logical offset to shorter line", 92 inputString: "abc\nefghijkl", 93 count: 1, 94 initialCursor: cursorState{position: 11, logicalOffset: 5}, 95 expectedCursor: cursorState{position: 2, logicalOffset: 10}, 96 }, 97 { 98 name: "two lines, move up with multi-char grapheme cluster", 99 inputString: "abcde\u0301fgh\nijklmnopqrstuv", 100 count: 1, 101 initialCursor: cursorState{position: 15}, 102 expectedCursor: cursorState{position: 6}, 103 }, 104 { 105 name: "three lines, move up from longer line to empty line", 106 inputString: "abcd\n\nefghijkl", 107 count: 1, 108 initialCursor: cursorState{position: 8}, 109 expectedCursor: cursorState{position: 5, logicalOffset: 2}, 110 }, 111 { 112 name: "move up multiple lines", 113 inputString: "abcd\nefgh\nijkl", 114 count: 2, 115 initialCursor: cursorState{position: 12}, 116 expectedCursor: cursorState{position: 2}, 117 }, 118 { 119 name: "move up to tab", 120 inputString: "abcd\ne\tefg\nhijkl", 121 count: 1, 122 initialCursor: cursorState{position: 13}, 123 expectedCursor: cursorState{position: 6, logicalOffset: 1}, 124 }, 125 { 126 name: "move up from tab", 127 inputString: "abcd\ne\tefg\nhijkl", 128 count: 1, 129 initialCursor: cursorState{position: 6, logicalOffset: 2}, 130 expectedCursor: cursorState{position: 3}, 131 }, 132 } 133 134 for _, tc := range testCases { 135 t.Run(tc.name, func(t *testing.T) { 136 textTree, err := text.NewTreeFromString(tc.inputString) 137 require.NoError(t, err) 138 state := NewEditorState(100, 100, nil, nil) 139 state.documentBuffer.textTree = textTree 140 state.documentBuffer.cursor = tc.initialCursor 141 state.documentBuffer.tabSize = 4 142 MoveCursorToLineAbove(state, tc.count) 143 assert.Equal(t, tc.expectedCursor, state.documentBuffer.cursor) 144 }) 145 } 146 } 147 148 func TestMoveCursorToLineBelow(t *testing.T) { 149 testCases := []struct { 150 name string 151 inputString string 152 count uint64 153 initialCursor cursorState 154 expectedCursor cursorState 155 }{ 156 { 157 name: "empty string, move down one line", 158 inputString: "", 159 count: 1, 160 initialCursor: cursorState{position: 0}, 161 expectedCursor: cursorState{position: 0}, 162 }, 163 { 164 name: "single line, move down one line", 165 inputString: "abcdefgh", 166 count: 1, 167 initialCursor: cursorState{position: 3}, 168 expectedCursor: cursorState{position: 3}, 169 }, 170 { 171 name: "single line, move down one line with logical offset", 172 inputString: "abcdefgh", 173 count: 1, 174 initialCursor: cursorState{position: 7, logicalOffset: 4}, 175 expectedCursor: cursorState{position: 7, logicalOffset: 4}, 176 }, 177 { 178 name: "two lines, move down one line at start of line", 179 inputString: "abcdefgh\nijklm\nopqrs", 180 count: 1, 181 initialCursor: cursorState{position: 9}, 182 expectedCursor: cursorState{position: 15}, 183 }, 184 { 185 name: "two lines, move down at same offset", 186 inputString: "abcdefgh\nijklmnop", 187 count: 1, 188 initialCursor: cursorState{position: 2}, 189 expectedCursor: cursorState{position: 11}, 190 }, 191 { 192 name: "two lines, move down from shorter line to longer line", 193 inputString: "abc\nefghijkl", 194 count: 1, 195 initialCursor: cursorState{position: 2}, 196 expectedCursor: cursorState{position: 6}, 197 }, 198 { 199 name: "two lines, move down from shorter line with logical offset to longer line", 200 inputString: "abc\nefghijkl", 201 count: 1, 202 initialCursor: cursorState{position: 2, logicalOffset: 3}, 203 expectedCursor: cursorState{position: 9}, 204 }, 205 { 206 name: "two lines, move down from longer line to shorter line", 207 inputString: "abcdefgh\nijkl", 208 count: 1, 209 initialCursor: cursorState{position: 7}, 210 expectedCursor: cursorState{position: 12, logicalOffset: 4}, 211 }, 212 { 213 name: "two lines, move down from longer line with logical offset to shorter line", 214 inputString: "abcdefgh\nijkl", 215 count: 1, 216 initialCursor: cursorState{position: 7, logicalOffset: 5}, 217 expectedCursor: cursorState{position: 12, logicalOffset: 9}, 218 }, 219 { 220 name: "two lines, move down with multi-char grapheme cluster", 221 inputString: "abcdefgh\nijklmno\u0301pqrstuv", 222 count: 1, 223 initialCursor: cursorState{position: 7}, 224 expectedCursor: cursorState{position: 17}, 225 }, 226 { 227 name: "three lines, move down from longer line to empty line", 228 inputString: "abcdefgh\n\nijkl", 229 count: 1, 230 initialCursor: cursorState{position: 2}, 231 expectedCursor: cursorState{position: 9, logicalOffset: 2}, 232 }, 233 { 234 name: "move down multiple lines", 235 inputString: "abcd\nefgh\nijkl", 236 count: 2, 237 initialCursor: cursorState{position: 2}, 238 expectedCursor: cursorState{position: 12}, 239 }, 240 { 241 name: "move down past newline at end of text", 242 inputString: "abcd\nefgh\nijkl\n", 243 count: 1, 244 initialCursor: cursorState{position: 12}, 245 expectedCursor: cursorState{position: 15, logicalOffset: 2}, 246 }, 247 { 248 name: "move down past single newline", 249 inputString: "\n", 250 count: 1, 251 initialCursor: cursorState{position: 0}, 252 expectedCursor: cursorState{position: 1}, 253 }, 254 { 255 name: "move down to tab", 256 inputString: "abcd\ne\tefg\nhijkl", 257 count: 1, 258 initialCursor: cursorState{position: 3}, 259 expectedCursor: cursorState{position: 6, logicalOffset: 2}, 260 }, 261 { 262 name: "move down from tab", 263 inputString: "abcd\ne\tefg\nhijkl", 264 count: 1, 265 initialCursor: cursorState{position: 6, logicalOffset: 1}, 266 expectedCursor: cursorState{position: 13}, 267 }, 268 } 269 270 for _, tc := range testCases { 271 t.Run(tc.name, func(t *testing.T) { 272 textTree, err := text.NewTreeFromString(tc.inputString) 273 require.NoError(t, err) 274 state := NewEditorState(100, 100, nil, nil) 275 state.documentBuffer.textTree = textTree 276 state.documentBuffer.cursor = tc.initialCursor 277 state.documentBuffer.tabSize = 4 278 MoveCursorToLineBelow(state, tc.count) 279 assert.Equal(t, tc.expectedCursor, state.documentBuffer.cursor) 280 }) 281 } 282 } 283 284 func TestMoveCursorToStartOfSelection(t *testing.T) { 285 testCases := []struct { 286 name string 287 inputString string 288 selectionMode selection.Mode 289 selectionStartPos uint64 290 selectionEndPos uint64 291 expectedCursor cursorState 292 }{ 293 { 294 name: "empty", 295 inputString: "", 296 selectionMode: selection.ModeChar, 297 selectionStartPos: 0, 298 selectionEndPos: 0, 299 expectedCursor: cursorState{position: 0}, 300 }, 301 { 302 name: "no selection", 303 inputString: "abcdefh", 304 selectionMode: selection.ModeNone, 305 selectionStartPos: 1, 306 selectionEndPos: 3, 307 expectedCursor: cursorState{position: 3}, 308 }, 309 { 310 name: "charwise, select forward", 311 inputString: "abcdefgh", 312 selectionMode: selection.ModeChar, 313 selectionStartPos: 1, 314 selectionEndPos: 3, 315 expectedCursor: cursorState{position: 1}, 316 }, 317 { 318 name: "charwise, select backward", 319 inputString: "abcdefgh", 320 selectionMode: selection.ModeChar, 321 selectionStartPos: 3, 322 selectionEndPos: 1, 323 expectedCursor: cursorState{position: 1}, 324 }, 325 { 326 name: "linewise select forward", 327 inputString: "abcd\nef\ngh\nijkl", 328 selectionMode: selection.ModeLine, 329 selectionStartPos: 6, 330 selectionEndPos: 9, 331 expectedCursor: cursorState{position: 5}, 332 }, 333 { 334 name: "linewise select backward", 335 inputString: "abcd\nef\ngh\nijkl", 336 selectionMode: selection.ModeLine, 337 selectionStartPos: 9, 338 selectionEndPos: 6, 339 expectedCursor: cursorState{position: 5}, 340 }, 341 } 342 343 for _, tc := range testCases { 344 t.Run(tc.name, func(t *testing.T) { 345 textTree, err := text.NewTreeFromString(tc.inputString) 346 require.NoError(t, err) 347 state := NewEditorState(100, 100, nil, nil) 348 state.documentBuffer.textTree = textTree 349 state.documentBuffer.selector.Start(tc.selectionMode, tc.selectionStartPos) 350 state.documentBuffer.cursor = cursorState{position: tc.selectionEndPos} 351 MoveCursorToStartOfSelection(state) 352 assert.Equal(t, tc.expectedCursor, state.documentBuffer.cursor) 353 }) 354 } 355 } 356 357 func TestSelectRange(t *testing.T) { 358 textTree, err := text.NewTreeFromString("abc def ghi") 359 require.NoError(t, err) 360 state := NewEditorState(100, 100, nil, nil) 361 state.documentBuffer.textTree = textTree 362 state.documentBuffer.selector.Start(selection.ModeLine, 0) 363 state.documentBuffer.cursor = cursorState{position: 3} 364 365 SelectRange(state, func(LocatorParams) (uint64, uint64) { 366 return 5, 7 367 }) 368 assert.Equal(t, selection.ModeChar, state.documentBuffer.SelectionMode()) 369 assert.Equal(t, selection.Region{StartPos: 5, EndPos: 7}, state.documentBuffer.SelectedRegion()) 370 assert.Equal(t, cursorState{position: 6}, state.documentBuffer.cursor) 371 }