github.com/aretext/aretext@v1.3.0/state/search_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/clipboard" 10 "github.com/aretext/aretext/text" 11 ) 12 13 func TestSearchAndCommit(t *testing.T) { 14 textTree, err := text.NewTreeFromString("foo bar baz") 15 require.NoError(t, err) 16 state := NewEditorState(100, 100, nil, nil) 17 buffer := state.documentBuffer 18 buffer.textTree = textTree 19 20 // Start a search. 21 StartSearch(state, SearchDirectionForward, SearchCompleteMoveCursorToMatch) 22 assert.Equal(t, state.inputMode, InputModeSearch) 23 assert.Equal(t, buffer.search.query, "") 24 25 // Enter a search query. 26 AppendRuneToSearchQuery(state, 'b') 27 assert.Equal(t, "b", buffer.search.query) 28 require.NotNil(t, buffer.search.match) 29 assert.Equal(t, uint64(4), buffer.search.match.StartPos) 30 assert.Equal(t, uint64(5), buffer.search.match.EndPos) 31 32 AppendRuneToSearchQuery(state, 'a') 33 assert.Equal(t, "ba", buffer.search.query) 34 require.NotNil(t, buffer.search.match) 35 assert.Equal(t, uint64(4), buffer.search.match.StartPos) 36 assert.Equal(t, uint64(6), buffer.search.match.EndPos) 37 38 AppendRuneToSearchQuery(state, 'r') 39 assert.Equal(t, "bar", buffer.search.query) 40 require.NotNil(t, buffer.search.match) 41 assert.Equal(t, uint64(4), buffer.search.match.StartPos) 42 assert.Equal(t, uint64(7), buffer.search.match.EndPos) 43 44 DeleteRuneFromSearchQuery(state) 45 assert.Equal(t, "ba", buffer.search.query) 46 require.NotNil(t, buffer.search.match) 47 assert.Equal(t, uint64(4), buffer.search.match.StartPos) 48 assert.Equal(t, uint64(6), buffer.search.match.EndPos) 49 50 // Commit the search. 51 CompleteSearch(state, true) 52 assert.Equal(t, state.inputMode, InputModeNormal) 53 assert.Equal(t, "ba", buffer.search.query) 54 assert.Nil(t, buffer.search.match) 55 assert.Equal(t, cursorState{position: 4}, buffer.cursor) 56 } 57 58 func TestSearchAndAbort(t *testing.T) { 59 textTree, err := text.NewTreeFromString("foo bar baz") 60 require.NoError(t, err) 61 state := NewEditorState(100, 100, nil, nil) 62 buffer := state.documentBuffer 63 buffer.textTree = textTree 64 buffer.search.query = "xyz" 65 66 // Start a search. 67 StartSearch(state, SearchDirectionForward, SearchCompleteMoveCursorToMatch) 68 assert.Equal(t, state.inputMode, InputModeSearch) 69 assert.Equal(t, buffer.search.query, "") 70 assert.Equal(t, buffer.search.prevQuery, "xyz") 71 72 // Enter a search query. 73 AppendRuneToSearchQuery(state, 'b') 74 assert.Equal(t, "b", buffer.search.query) 75 require.NotNil(t, buffer.search.match) 76 assert.Equal(t, uint64(4), buffer.search.match.StartPos) 77 assert.Equal(t, uint64(5), buffer.search.match.EndPos) 78 79 // Abort the search. 80 CompleteSearch(state, false) 81 assert.Equal(t, state.inputMode, InputModeNormal) 82 assert.Equal(t, "xyz", buffer.search.query) 83 assert.Nil(t, buffer.search.match) 84 assert.Equal(t, cursorState{position: 0}, buffer.cursor) 85 } 86 87 func TestSearchAndBackspaceEmptyQuery(t *testing.T) { 88 textTree, err := text.NewTreeFromString("foo bar baz") 89 require.NoError(t, err) 90 state := NewEditorState(100, 100, nil, nil) 91 buffer := state.documentBuffer 92 buffer.textTree = textTree 93 94 // Start a search. 95 StartSearch(state, SearchDirectionForward, SearchCompleteMoveCursorToMatch) 96 assert.Equal(t, state.inputMode, InputModeSearch) 97 assert.Equal(t, buffer.search.query, "") 98 99 // Delete from the empty query, equivalent to aborting the search. 100 DeleteRuneFromSearchQuery(state) 101 assert.Equal(t, state.inputMode, InputModeNormal) 102 assert.Equal(t, "", buffer.search.query) 103 assert.Nil(t, buffer.search.match) 104 assert.Equal(t, cursorState{position: 0}, buffer.cursor) 105 } 106 107 func TestSearchForwardCursorOnMatch(t *testing.T) { 108 textTree, err := text.NewTreeFromString("foo bar foo") 109 require.NoError(t, err) 110 state := NewEditorState(100, 100, nil, nil) 111 buffer := state.documentBuffer 112 buffer.textTree = textTree 113 114 // Enter a search query matching at the cursor's current position. 115 StartSearch(state, SearchDirectionForward, SearchCompleteMoveCursorToMatch) 116 AppendRuneToSearchQuery(state, 'f') 117 AppendRuneToSearchQuery(state, 'o') 118 AppendRuneToSearchQuery(state, 'o') 119 assert.Equal(t, "foo", buffer.search.query) 120 121 // Expect that to find the match *after* the cursor's position. 122 require.NotNil(t, buffer.search.match) 123 assert.Equal(t, uint64(8), buffer.search.match.StartPos) 124 assert.Equal(t, uint64(11), buffer.search.match.EndPos) 125 } 126 127 func TestSearchForwardWithWraparoundCursorAtBeginning(t *testing.T) { 128 textTree, err := text.NewTreeFromString("abc") 129 require.NoError(t, err) 130 state := NewEditorState(100, 100, nil, nil) 131 buffer := state.documentBuffer 132 buffer.textTree = textTree 133 134 // Enter a search query matching at the cursor's current position. 135 StartSearch(state, SearchDirectionForward, SearchCompleteMoveCursorToMatch) 136 AppendRuneToSearchQuery(state, 'a') 137 AppendRuneToSearchQuery(state, 'b') 138 assert.Equal(t, "ab", buffer.search.query) 139 140 // Expect that to match the first position (wraparound back to start) 141 require.NotNil(t, buffer.search.match) 142 assert.Equal(t, uint64(0), buffer.search.match.StartPos) 143 assert.Equal(t, uint64(2), buffer.search.match.EndPos) 144 } 145 146 func TestSearchCaseSensitivity(t *testing.T) { 147 testCases := []struct { 148 name string 149 text string 150 query string 151 expectedMatchPos uint64 152 }{ 153 { 154 name: "lowercase query, case-insensitive search", 155 text: "abc Foo foo xyz", 156 query: "foo", 157 expectedMatchPos: 4, 158 }, 159 { 160 name: "mixed-case query, case-sensitive search", 161 text: "abc foo Foo xyz", 162 query: "Foo", 163 expectedMatchPos: 8, 164 }, 165 { 166 name: "lowercase query, force case-sensitive search", 167 text: "abc Foo foo xyz", 168 query: "foo\\C", 169 expectedMatchPos: 8, 170 }, 171 { 172 name: "mixed-case query, force case-insensitive search", 173 text: "abc Foo foo xyz", 174 query: "FOO\\c", 175 expectedMatchPos: 4, 176 }, 177 } 178 179 for _, tc := range testCases { 180 t.Run(tc.name, func(t *testing.T) { 181 textTree, err := text.NewTreeFromString(tc.text) 182 require.NoError(t, err) 183 state := NewEditorState(100, 100, nil, nil) 184 buffer := state.documentBuffer 185 buffer.textTree = textTree 186 187 StartSearch(state, SearchDirectionForward, SearchCompleteMoveCursorToMatch) 188 for _, r := range tc.query { 189 AppendRuneToSearchQuery(state, r) 190 } 191 CompleteSearch(state, true) 192 193 assert.Equal(t, cursorState{position: tc.expectedMatchPos}, buffer.cursor) 194 }) 195 } 196 } 197 198 func TestFindNextMatch(t *testing.T) { 199 testCases := []struct { 200 name string 201 text string 202 cursorPos uint64 203 query string 204 direction SearchDirection 205 reverse bool 206 expectedCursorPos uint64 207 }{ 208 { 209 name: "empty text", 210 text: "", 211 cursorPos: 0, 212 query: "abc", 213 direction: SearchDirectionForward, 214 expectedCursorPos: 0, 215 }, 216 { 217 name: "find next after cursor", 218 text: "foo bar baz", 219 cursorPos: 1, 220 query: "ba", 221 direction: SearchDirectionForward, 222 expectedCursorPos: 4, 223 }, 224 { 225 name: "find next after cursor already on match", 226 text: "foo bar baz", 227 cursorPos: 4, 228 query: "ba", 229 direction: SearchDirectionForward, 230 expectedCursorPos: 8, 231 }, 232 { 233 name: "find next at end of text, not found in wraparound", 234 text: "foo bar baz", 235 cursorPos: 10, 236 query: "xa", 237 direction: SearchDirectionForward, 238 expectedCursorPos: 10, 239 }, 240 { 241 name: "find next at end of text, found in wraparound", 242 text: "foo bar baz", 243 cursorPos: 10, 244 query: "ba", 245 direction: SearchDirectionForward, 246 expectedCursorPos: 4, 247 }, 248 { 249 name: "find next with multi-byte unicode", 250 text: "丂丄丅丆丏 ¢ह€한", 251 cursorPos: 0, 252 query: "丅丆", 253 direction: SearchDirectionForward, 254 expectedCursorPos: 2, 255 }, 256 { 257 name: "empty text, reverse search", 258 text: "", 259 cursorPos: 0, 260 query: "abc", 261 expectedCursorPos: 0, 262 direction: SearchDirectionForward, 263 reverse: true, 264 }, 265 { 266 name: "find prev", 267 text: "foo bar baz xyz", 268 cursorPos: 14, 269 query: "ba", 270 expectedCursorPos: 8, 271 direction: SearchDirectionForward, 272 reverse: true, 273 }, 274 { 275 name: "find prev from current match", 276 text: "foo bar baz xyz", 277 cursorPos: 8, 278 query: "ba", 279 direction: SearchDirectionForward, 280 expectedCursorPos: 4, 281 reverse: true, 282 }, 283 { 284 name: "find prev from middle of current match", 285 text: "foo bar baz xyz", 286 cursorPos: 9, 287 query: "ba", 288 direction: SearchDirectionForward, 289 expectedCursorPos: 8, 290 reverse: true, 291 }, 292 { 293 name: "find prev from start of text, not found in wraparound", 294 text: "foo bar baz xyz", 295 cursorPos: 0, 296 query: "lm", 297 direction: SearchDirectionForward, 298 expectedCursorPos: 0, 299 reverse: true, 300 }, 301 { 302 name: "find prev from start of text, found in wraparound", 303 text: "foo bar baz xyz", 304 cursorPos: 0, 305 query: "ba", 306 direction: SearchDirectionForward, 307 expectedCursorPos: 8, 308 reverse: true, 309 }, 310 { 311 name: "find prev with multi-byte unicode", 312 text: "丂丄丅丆丏 ¢ह€한", 313 cursorPos: 9, 314 query: "丅丆", 315 direction: SearchDirectionForward, 316 expectedCursorPos: 2, 317 reverse: true, 318 }, 319 { 320 name: "backward search equivalent to reverse forward search", 321 text: "foo bar baz xyz", 322 cursorPos: 14, 323 query: "ba", 324 direction: SearchDirectionBackward, 325 expectedCursorPos: 8, 326 reverse: false, 327 }, 328 { 329 name: "reverse backward search equivalent to forward search", 330 text: "foo bar baz xyz", 331 cursorPos: 0, 332 query: "ba", 333 direction: SearchDirectionBackward, 334 expectedCursorPos: 4, 335 reverse: true, 336 }, 337 { 338 name: "unicode normalization has different offsets", 339 text: "<p> & © Æ Ď\n¾ ℋ ⅆ\n∲ ≧̸</p>\nfoobar", 340 cursorPos: 0, 341 query: "foobar", 342 direction: SearchDirectionForward, 343 expectedCursorPos: 32, 344 }, 345 } 346 347 for _, tc := range testCases { 348 t.Run(tc.name, func(t *testing.T) { 349 textTree, err := text.NewTreeFromString(tc.text) 350 require.NoError(t, err) 351 state := NewEditorState(100, 100, nil, nil) 352 buffer := state.documentBuffer 353 buffer.textTree = textTree 354 buffer.cursor = cursorState{position: tc.cursorPos} 355 buffer.search.query = tc.query 356 buffer.search.direction = tc.direction 357 FindNextMatch(state, tc.reverse) 358 assert.Equal(t, tc.expectedCursorPos, buffer.cursor.position) 359 }) 360 } 361 } 362 363 func TestSearchWordUnderCursor(t *testing.T) { 364 testCases := []struct { 365 name string 366 inputText string 367 direction SearchDirection 368 count uint64 369 pos uint64 370 expectedQuery string 371 expectedPos uint64 372 }{ 373 { 374 name: "empty", 375 inputText: "", 376 direction: SearchDirectionForward, 377 count: 1, 378 pos: 0, 379 expectedQuery: "", 380 expectedPos: 0, 381 }, 382 { 383 name: "start of word under cursor, search forward", 384 inputText: "foo bar baz bar", 385 direction: SearchDirectionForward, 386 count: 1, 387 pos: 4, 388 expectedQuery: "bar\\C", 389 expectedPos: 12, 390 }, 391 { 392 name: "word under cursor, search forward", 393 inputText: "foo bar baz bar", 394 direction: SearchDirectionForward, 395 count: 1, 396 pos: 5, 397 expectedQuery: "bar\\C", 398 expectedPos: 12, 399 }, 400 { 401 name: "word under cursor, search backward", 402 inputText: "foo bar baz bar", 403 direction: SearchDirectionForward, 404 count: 1, 405 pos: 14, 406 expectedQuery: "bar\\C", 407 expectedPos: 4, 408 }, 409 { 410 name: "whitespace before word", 411 inputText: "foo bar baz bar", 412 direction: SearchDirectionForward, 413 count: 1, 414 pos: 3, 415 expectedQuery: "bar\\C", 416 expectedPos: 6, // differs from vim, which would advance to the next occurrence. 417 }, 418 { 419 name: "whitespace before end of line", 420 inputText: "foo bar \nbaz", 421 direction: SearchDirectionForward, 422 count: 1, 423 pos: 9, 424 expectedQuery: "baz\\C", // differs from vim, which aborts. 425 expectedPos: 11, 426 }, 427 { 428 name: "search forward with count", 429 inputText: "foo bar baz\nxyz\nfoo bar bat", 430 direction: SearchDirectionForward, 431 count: 2, 432 pos: 1, 433 expectedQuery: "foo bar\\C", 434 expectedPos: 16, 435 }, 436 { 437 name: "search case sensitive", 438 inputText: "foo bar FOO BAR bar", 439 direction: SearchDirectionForward, 440 count: 1, 441 pos: 5, 442 expectedQuery: "bar\\C", 443 expectedPos: 16, 444 }, 445 } 446 447 for _, tc := range testCases { 448 t.Run(tc.name, func(t *testing.T) { 449 textTree, err := text.NewTreeFromString(tc.inputText) 450 require.NoError(t, err) 451 state := NewEditorState(100, 100, nil, nil) 452 buffer := state.documentBuffer 453 buffer.textTree = textTree 454 buffer.cursor.position = tc.pos 455 456 // Search for the word under the cursor. 457 SearchWordUnderCursor(state, SearchDirectionForward, SearchCompleteMoveCursorToMatch, tc.count) 458 assert.Equal(t, InputModeNormal, state.inputMode) 459 assert.Equal(t, tc.expectedQuery, buffer.search.query) 460 assert.Nil(t, buffer.search.match) 461 assert.Equal(t, cursorState{position: tc.expectedPos}, buffer.cursor) 462 }) 463 } 464 } 465 466 func TestSearchForDelete(t *testing.T) { 467 testCases := []struct { 468 name string 469 inputText string 470 direction SearchDirection 471 pos uint64 472 query string 473 expectedText string 474 expectedPos uint64 475 }{ 476 { 477 name: "empty document", 478 inputText: "", 479 direction: SearchDirectionForward, 480 pos: 0, 481 query: "abc", 482 expectedText: "", 483 expectedPos: 0, 484 }, 485 { 486 name: "no match, forward search", 487 inputText: "abc def", 488 direction: SearchDirectionForward, 489 pos: 0, 490 query: "xyz", 491 expectedText: "abc def", 492 expectedPos: 0, 493 }, 494 { 495 name: "no match, backward search", 496 inputText: "abc def", 497 direction: SearchDirectionForward, 498 pos: 6, 499 query: "xyz", 500 expectedText: "abc def", 501 expectedPos: 6, 502 }, 503 { 504 name: "match, forward search", 505 inputText: "abc def xyz 123 xyz", 506 direction: SearchDirectionForward, 507 pos: 2, 508 query: "xyz", 509 expectedText: "abxyz 123 xyz", 510 expectedPos: 2, 511 }, 512 { 513 name: "match, backward search", 514 inputText: "abc def xyz 123 xyz abc", 515 direction: SearchDirectionBackward, 516 pos: 22, 517 query: "xyz", 518 expectedText: "abc def xyz 123 xyzc", 519 expectedPos: 19, 520 }, 521 { 522 name: "match, forward search, skip match on cursor", 523 inputText: "abc 123 abc 456 abc 789", 524 direction: SearchDirectionForward, 525 pos: 0, 526 query: "abc", 527 expectedText: "abc 456 abc 789", 528 expectedPos: 0, 529 }, 530 { 531 name: "match, forward search, wraparound", 532 inputText: "abc 123 xyz 456", 533 direction: SearchDirectionForward, 534 pos: 13, 535 query: "bc", 536 expectedText: "a56", 537 expectedPos: 1, 538 }, 539 { 540 name: "match, backward search, wraparound", 541 inputText: "abc 123 xyz 456", 542 direction: SearchDirectionBackward, 543 pos: 2, 544 query: "yz", 545 expectedText: "abyz 456", 546 expectedPos: 2, 547 }, 548 } 549 550 for _, tc := range testCases { 551 t.Run(tc.name, func(t *testing.T) { 552 textTree, err := text.NewTreeFromString(tc.inputText) 553 require.NoError(t, err) 554 state := NewEditorState(100, 100, nil, nil) 555 buffer := state.documentBuffer 556 buffer.textTree = textTree 557 buffer.cursor.position = tc.pos 558 559 // Search for the query, with a complete action to delete to the match. 560 StartSearch(state, tc.direction, SearchCompleteDeleteToMatch(clipboard.PageNull)) 561 for _, r := range tc.query { 562 AppendRuneToSearchQuery(state, r) 563 } 564 CompleteSearch(state, true) 565 566 assert.Equal(t, InputModeNormal, state.inputMode) 567 assert.Equal(t, tc.expectedPos, buffer.cursor.position) 568 assert.Equal(t, tc.expectedText, textTree.String()) 569 }) 570 } 571 } 572 573 func TestSearchForDeleteAndRepeatLastAction(t *testing.T) { 574 textTree, err := text.NewTreeFromString("abc xyz 123\nabc xyz 123\nabc xyz 123") 575 require.NoError(t, err) 576 state := NewEditorState(100, 100, nil, nil) 577 buffer := state.documentBuffer 578 buffer.textTree = textTree 579 buffer.cursor.position = 0 580 581 // Search for the query, with a complete action to delete to the match. 582 StartSearch(state, SearchDirectionForward, SearchCompleteDeleteToMatch(clipboard.PageNull)) 583 for _, r := range "xyz" { 584 AppendRuneToSearchQuery(state, r) 585 } 586 CompleteSearch(state, true) 587 assert.Equal(t, InputModeNormal, state.inputMode) 588 assert.Equal(t, uint64(0), buffer.cursor.position) 589 assert.Equal(t, "xyz 123\nabc xyz 123\nabc xyz 123", textTree.String()) 590 591 // Change the search query. This shouldn't affect the last action macro. 592 StartSearch(state, SearchDirectionForward, SearchCompleteMoveCursorToMatch) 593 for _, r := range "abc" { 594 AppendRuneToSearchQuery(state, r) 595 } 596 CompleteSearch(state, true) 597 assert.Equal(t, InputModeNormal, state.inputMode) 598 assert.Equal(t, uint64(8), buffer.cursor.position) 599 600 // Repeat the last action. 601 ReplayLastActionMacro(state, 1) 602 assert.Equal(t, InputModeNormal, state.inputMode) 603 assert.Equal(t, uint64(8), buffer.cursor.position) 604 assert.Equal(t, "xyz 123\nxyz 123\nabc xyz 123", textTree.String()) 605 606 // And again! 607 ReplayLastActionMacro(state, 1) 608 assert.Equal(t, InputModeNormal, state.inputMode) 609 assert.Equal(t, uint64(8), buffer.cursor.position) 610 assert.Equal(t, "xyz 123\nxyz 123", textTree.String()) 611 } 612 613 func TestSearchForChange(t *testing.T) { 614 textTree, err := text.NewTreeFromString("abc xyz 123\nabc xyz 123\nabc xyz 123") 615 require.NoError(t, err) 616 state := NewEditorState(100, 100, nil, nil) 617 buffer := state.documentBuffer 618 buffer.textTree = textTree 619 buffer.cursor.position = 0 620 621 // Search for the query, with a complete action to change to the match. 622 StartSearch(state, SearchDirectionForward, SearchCompleteChangeToMatch(clipboard.PageNull)) 623 for _, r := range "xyz" { 624 AppendRuneToSearchQuery(state, r) 625 } 626 CompleteSearch(state, true) 627 assert.Equal(t, InputModeInsert, state.inputMode) // Since it's a change, go to insert mode. 628 assert.Equal(t, uint64(0), buffer.cursor.position) 629 assert.Equal(t, "xyz 123\nabc xyz 123\nabc xyz 123", textTree.String()) 630 } 631 632 func TestSearchForCopy(t *testing.T) { 633 testCases := []struct { 634 name string 635 inputText string 636 direction SearchDirection 637 pos uint64 638 query string 639 expectedClipboardText string 640 }{ 641 { 642 name: "empty document", 643 inputText: "", 644 direction: SearchDirectionForward, 645 pos: 0, 646 query: "abc", 647 expectedClipboardText: "", 648 }, 649 { 650 name: "no match, forward search", 651 inputText: "abc def", 652 direction: SearchDirectionForward, 653 pos: 0, 654 query: "xyz", 655 expectedClipboardText: "", 656 }, 657 { 658 name: "no match, backward search", 659 inputText: "abc def", 660 direction: SearchDirectionForward, 661 pos: 6, 662 query: "xyz", 663 expectedClipboardText: "", 664 }, 665 { 666 name: "match, forward search", 667 inputText: "abc def xyz 123 xyz", 668 direction: SearchDirectionForward, 669 pos: 2, 670 query: "xyz", 671 expectedClipboardText: "c def ", 672 }, 673 { 674 name: "match, backward search", 675 inputText: "abc def xyz 123 xyz abc", 676 direction: SearchDirectionBackward, 677 pos: 22, 678 query: "xyz", 679 expectedClipboardText: " ab", 680 }, 681 { 682 name: "match, forward search, skip match on cursor", 683 inputText: "abc 123 abc 456 abc 789", 684 direction: SearchDirectionForward, 685 pos: 0, 686 query: "abc", 687 expectedClipboardText: "abc 123 ", 688 }, 689 { 690 name: "match, forward search, wraparound", 691 inputText: "abc 123 xyz 456", 692 direction: SearchDirectionForward, 693 pos: 13, 694 query: "bc", 695 expectedClipboardText: "", 696 }, 697 { 698 name: "match, backward search, wraparound", 699 inputText: "abc 123 xyz 456", 700 direction: SearchDirectionBackward, 701 pos: 2, 702 query: "yz", 703 expectedClipboardText: "", 704 }, 705 } 706 707 for _, tc := range testCases { 708 t.Run(tc.name, func(t *testing.T) { 709 textTree, err := text.NewTreeFromString(tc.inputText) 710 require.NoError(t, err) 711 state := NewEditorState(100, 100, nil, nil) 712 buffer := state.documentBuffer 713 buffer.textTree = textTree 714 buffer.cursor.position = tc.pos 715 716 // Search for the query, with a complete action to copy to the match. 717 StartSearch(state, tc.direction, SearchCompleteCopyToMatch(clipboard.PageDefault)) 718 for _, r := range tc.query { 719 AppendRuneToSearchQuery(state, r) 720 } 721 CompleteSearch(state, true) 722 723 // Back to normal mode, no change in cursor or document. 724 assert.Equal(t, InputModeNormal, state.inputMode) 725 assert.Equal(t, tc.pos, buffer.cursor.position) 726 assert.Equal(t, tc.inputText, textTree.String()) 727 728 // Check clipboard state. 729 page := state.clipboard.Get(clipboard.PageDefault) 730 assert.False(t, page.Linewise) 731 assert.Equal(t, tc.expectedClipboardText, page.Text) 732 }) 733 } 734 } 735 736 func TestSetSearchQueryToPrevInHistory(t *testing.T) { 737 textTree, err := text.NewTreeFromString("x abc def ghi") 738 require.NoError(t, err) 739 state := NewEditorState(100, 100, nil, nil) 740 buffer := state.documentBuffer 741 buffer.textTree = textTree 742 743 // First search query, aborted. 744 StartSearch(state, SearchDirectionForward, SearchCompleteMoveCursorToMatch) 745 for _, r := range "abc" { 746 AppendRuneToSearchQuery(state, r) 747 } 748 CompleteSearch(state, false) 749 750 // Second search query, committed. 751 StartSearch(state, SearchDirectionForward, SearchCompleteMoveCursorToMatch) 752 for _, r := range "def" { 753 AppendRuneToSearchQuery(state, r) 754 } 755 CompleteSearch(state, true) 756 757 // Start a search, go back in history. 758 StartSearch(state, SearchDirectionForward, SearchCompleteMoveCursorToMatch) 759 SetSearchQueryToPrevInHistory(state) 760 assert.Equal(t, "def", buffer.search.query) 761 require.NotNil(t, buffer.search.match) 762 assert.Equal(t, uint64(6), buffer.search.match.StartPos) 763 764 // Go back in the history again. 765 SetSearchQueryToPrevInHistory(state) 766 assert.Equal(t, "abc", buffer.search.query) 767 require.NotNil(t, buffer.search.match) 768 assert.Equal(t, uint64(2), buffer.search.match.StartPos) 769 770 // Go back in the history, no previous entry so no change. 771 SetSearchQueryToPrevInHistory(state) 772 assert.Equal(t, "abc", buffer.search.query) 773 require.NotNil(t, buffer.search.match) 774 assert.Equal(t, uint64(2), buffer.search.match.StartPos) 775 } 776 777 func TestSetSearchQueryToNextInHistory(t *testing.T) { 778 textTree, err := text.NewTreeFromString("x abc def ghi") 779 require.NoError(t, err) 780 state := NewEditorState(100, 100, nil, nil) 781 buffer := state.documentBuffer 782 buffer.textTree = textTree 783 784 // First search query, aborted. 785 StartSearch(state, SearchDirectionForward, SearchCompleteMoveCursorToMatch) 786 for _, r := range "abc" { 787 AppendRuneToSearchQuery(state, r) 788 } 789 CompleteSearch(state, false) 790 791 // Second search query, committed. 792 StartSearch(state, SearchDirectionForward, SearchCompleteMoveCursorToMatch) 793 for _, r := range "def" { 794 AppendRuneToSearchQuery(state, r) 795 } 796 CompleteSearch(state, true) 797 798 // Go back to beginning of history. 799 SetSearchQueryToPrevInHistory(state) 800 SetSearchQueryToPrevInHistory(state) 801 assert.Equal(t, "abc", buffer.search.query) 802 require.NotNil(t, buffer.search.match) 803 assert.Equal(t, uint64(2), buffer.search.match.StartPos) 804 805 // Go to next in history. 806 SetSearchQueryToNextInHistory(state) 807 assert.Equal(t, "def", buffer.search.query) 808 require.NotNil(t, buffer.search.match) 809 assert.Equal(t, uint64(6), buffer.search.match.StartPos) 810 811 // Forward again. No future entry, so no change. 812 SetSearchQueryToNextInHistory(state) 813 assert.Equal(t, "def", buffer.search.query) 814 require.NotNil(t, buffer.search.match) 815 assert.Equal(t, uint64(6), buffer.search.match.StartPos) 816 } 817 818 func TestSearchQueryToPrevInHistoryThenAppendRunes(t *testing.T) { 819 textTree, err := text.NewTreeFromString("x abc def ghi") 820 require.NoError(t, err) 821 state := NewEditorState(100, 100, nil, nil) 822 buffer := state.documentBuffer 823 buffer.textTree = textTree 824 825 // First search query, aborted. 826 StartSearch(state, SearchDirectionForward, SearchCompleteMoveCursorToMatch) 827 for _, r := range "abc" { 828 AppendRuneToSearchQuery(state, r) 829 } 830 CompleteSearch(state, false) 831 832 // Second search query, committed. 833 StartSearch(state, SearchDirectionForward, SearchCompleteMoveCursorToMatch) 834 for _, r := range "def" { 835 AppendRuneToSearchQuery(state, r) 836 } 837 CompleteSearch(state, true) 838 839 // Start a search, go back to beginning of history. 840 StartSearch(state, SearchDirectionForward, SearchCompleteMoveCursorToMatch) 841 SetSearchQueryToPrevInHistory(state) 842 SetSearchQueryToPrevInHistory(state) 843 assert.Equal(t, "abc", buffer.search.query) 844 require.NotNil(t, buffer.search.match) 845 assert.Equal(t, uint64(2), buffer.search.match.StartPos) 846 847 // Edit the query by appending runes. 848 AppendRuneToSearchQuery(state, 'x') 849 AppendRuneToSearchQuery(state, 'y') 850 AppendRuneToSearchQuery(state, 'z') 851 assert.Equal(t, "abcxyz", buffer.search.query) 852 assert.Nil(t, buffer.search.match) 853 854 // Go back in history, confirm that the edit reset to the last entry. 855 SetSearchQueryToPrevInHistory(state) 856 assert.Equal(t, "def", buffer.search.query) 857 require.NotNil(t, buffer.search.match) 858 assert.Equal(t, uint64(6), buffer.search.match.StartPos) 859 } 860 861 func TestSearchQueryToPrevInHistoryThenDeleteRunes(t *testing.T) { 862 textTree, err := text.NewTreeFromString("x abc def ghi") 863 require.NoError(t, err) 864 state := NewEditorState(100, 100, nil, nil) 865 buffer := state.documentBuffer 866 buffer.textTree = textTree 867 868 // First search query, aborted. 869 StartSearch(state, SearchDirectionForward, SearchCompleteMoveCursorToMatch) 870 for _, r := range "abc" { 871 AppendRuneToSearchQuery(state, r) 872 } 873 CompleteSearch(state, false) 874 875 // Second search query, committed. 876 StartSearch(state, SearchDirectionForward, SearchCompleteMoveCursorToMatch) 877 for _, r := range "def" { 878 AppendRuneToSearchQuery(state, r) 879 } 880 CompleteSearch(state, true) 881 882 // Start a search, go back to beginning of history. 883 StartSearch(state, SearchDirectionForward, SearchCompleteMoveCursorToMatch) 884 SetSearchQueryToPrevInHistory(state) 885 SetSearchQueryToPrevInHistory(state) 886 assert.Equal(t, "abc", buffer.search.query) 887 require.NotNil(t, buffer.search.match) 888 assert.Equal(t, uint64(2), buffer.search.match.StartPos) 889 890 // Edit the query by deleting runes. 891 DeleteRuneFromSearchQuery(state) 892 DeleteRuneFromSearchQuery(state) 893 assert.Equal(t, "a", buffer.search.query) 894 require.NotNil(t, buffer.search.match) 895 assert.Equal(t, uint64(2), buffer.search.match.StartPos) 896 897 // Go back in history, confirm that the edit reset to the last entry. 898 SetSearchQueryToPrevInHistory(state) 899 assert.Equal(t, "def", buffer.search.query) 900 require.NotNil(t, buffer.search.match) 901 assert.Equal(t, uint64(6), buffer.search.match.StartPos) 902 } 903 904 func TestSearchQueryHistoryExcludesEmptyQueries(t *testing.T) { 905 textTree, err := text.NewTreeFromString("x abc def ghi") 906 require.NoError(t, err) 907 state := NewEditorState(100, 100, nil, nil) 908 buffer := state.documentBuffer 909 buffer.textTree = textTree 910 911 // First search query. 912 StartSearch(state, SearchDirectionForward, SearchCompleteMoveCursorToMatch) 913 for _, r := range "abc" { 914 AppendRuneToSearchQuery(state, r) 915 } 916 CompleteSearch(state, false) 917 918 // Several empty search queries, should not be added to history. 919 for i := 0; i < 3; i++ { 920 StartSearch(state, SearchDirectionForward, SearchCompleteMoveCursorToMatch) 921 CompleteSearch(state, false) 922 } 923 924 // Start a search, back to previous entry. 925 StartSearch(state, SearchDirectionForward, SearchCompleteMoveCursorToMatch) 926 SetSearchQueryToPrevInHistory(state) 927 assert.Equal(t, "abc", buffer.search.query) 928 require.NotNil(t, buffer.search.match) 929 assert.Equal(t, uint64(2), buffer.search.match.StartPos) 930 } 931 932 func TestSearchQueryHistoryExcludesDuplicateQueries(t *testing.T) { 933 textTree, err := text.NewTreeFromString("x abc def ghi") 934 require.NoError(t, err) 935 state := NewEditorState(100, 100, nil, nil) 936 buffer := state.documentBuffer 937 buffer.textTree = textTree 938 939 // First search query. 940 StartSearch(state, SearchDirectionForward, SearchCompleteMoveCursorToMatch) 941 for _, r := range "abc" { 942 AppendRuneToSearchQuery(state, r) 943 } 944 CompleteSearch(state, false) 945 946 // Second search query. 947 StartSearch(state, SearchDirectionForward, SearchCompleteMoveCursorToMatch) 948 for _, r := range "def" { 949 AppendRuneToSearchQuery(state, r) 950 } 951 CompleteSearch(state, false) 952 953 // Repeat the query several times. 954 for i := 0; i < 3; i++ { 955 StartSearch(state, SearchDirectionForward, SearchCompleteMoveCursorToMatch) 956 for _, r := range "def" { 957 AppendRuneToSearchQuery(state, r) 958 } 959 CompleteSearch(state, false) 960 } 961 962 // Start a search, back to previous entry. 963 StartSearch(state, SearchDirectionForward, SearchCompleteMoveCursorToMatch) 964 SetSearchQueryToPrevInHistory(state) 965 assert.Equal(t, "def", buffer.search.query) 966 require.NotNil(t, buffer.search.match) 967 assert.Equal(t, uint64(6), buffer.search.match.StartPos) 968 969 // Back again, expect that we're at the first entry (duplicate entries were excluded from history). 970 SetSearchQueryToPrevInHistory(state) 971 assert.Equal(t, "abc", buffer.search.query) 972 require.NotNil(t, buffer.search.match) 973 assert.Equal(t, uint64(2), buffer.search.match.StartPos) 974 }