Serenity Operating System
at master 1588 lines 51 kB view raw
1/* 2 * Copyright (c) 2020-2021, the SerenityOS developers. 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include <AK/Assertions.h> 8#include <AK/DeprecatedString.h> 9#include <LibGUI/Event.h> 10#include <LibGUI/TextEditor.h> 11#include <LibGUI/VimEditingEngine.h> 12#include <string.h> 13 14namespace GUI { 15 16void VimCursor::move() 17{ 18 if (m_forwards) 19 move_forwards(); 20 else 21 move_backwards(); 22} 23 24void VimCursor::move_reverse() 25{ 26 if (m_forwards) 27 move_backwards(); 28 else 29 move_forwards(); 30} 31 32u32 VimCursor::peek() 33{ 34 TextPosition saved_position = m_position; 35 36 move(); 37 u32 peeked = current_char(); 38 39 m_position = saved_position; 40 return peeked; 41} 42 43u32 VimCursor::peek_reverse() 44{ 45 TextPosition saved_position = m_position; 46 47 move_reverse(); 48 u32 peeked = current_char(); 49 50 m_position = saved_position; 51 return peeked; 52} 53 54TextDocumentLine& VimCursor::current_line() 55{ 56 return m_editor.line(m_position.line()); 57} 58 59u32 VimCursor::current_char() 60{ 61 if (on_empty_line()) { 62 // Fails all of isspace, ispunct, isalnum so should be good. 63 return 0; 64 } else { 65 return current_line().view().code_points()[m_position.column()]; 66 } 67} 68 69bool VimCursor::on_empty_line() 70{ 71 return current_line().length() == 0; 72} 73 74bool VimCursor::will_cross_line_boundary() 75{ 76 if (on_empty_line()) 77 return true; 78 else if (m_forwards && m_position.column() == current_line().length() - 1) 79 return true; 80 else if (!m_forwards && m_position.column() == 0) 81 return true; 82 else 83 return false; 84} 85 86void VimCursor::move_forwards() 87{ 88 if (on_empty_line() || m_position.column() == current_line().length() - 1) { 89 if (m_position.line() == m_editor.line_count() - 1) { 90 // We have reached the end of the document, so any other 91 // forward movements are no-ops. 92 m_hit_edge = true; 93 } else { 94 m_position.set_column(0); 95 m_position.set_line(m_position.line() + 1); 96 m_crossed_line_boundary = true; 97 } 98 } else { 99 m_position.set_column(m_position.column() + 1); 100 m_crossed_line_boundary = false; 101 } 102} 103 104void VimCursor::move_backwards() 105{ 106 if (m_position.column() == 0) { 107 if (m_position.line() == 0) { 108 // We have reached the start of the document, so any other 109 // backward movements are no-ops. 110 m_hit_edge = true; 111 } else { 112 m_position.set_line(m_position.line() - 1); 113 if (!on_empty_line()) 114 m_position.set_column(current_line().length() - 1); 115 else 116 m_position.set_column(0); 117 m_crossed_line_boundary = true; 118 } 119 } else { 120 m_position.set_column(m_position.column() - 1); 121 m_crossed_line_boundary = false; 122 } 123} 124 125void VimMotion::add_key_code(KeyCode key, [[maybe_unused]] bool ctrl, bool shift, [[maybe_unused]] bool alt) 126{ 127 if (is_complete()) 128 return; 129 130 if (m_find_mode != FindMode::None) { 131 // We need to consume the next character because we are going to find 132 // until that character. 133 134 // HACK: there is no good way to obtain whether a character is alphanumeric 135 // from the keycode itself. 136 char const* keycode_str = key_code_to_string(key); 137 138 if (strlen(keycode_str) == 1 && (isalpha(keycode_str[0]) || isspace(keycode_str[0]))) { 139 m_next_character = tolower(keycode_str[0]); 140 m_unit = Unit::Find; 141 } else { 142 m_unit = Unit::Unknown; 143 } 144 145 m_is_complete = true; 146 m_should_consume_next_character = false; 147 return; 148 } 149 150 bool should_use_guirky = m_guirky_mode; 151 152 switch (key) { 153#define DIGIT(n) \ 154 case KeyCode::Key_##n: \ 155 m_amount = (m_amount * 10) + n; \ 156 break 157 158 // Digits add digits to the amount. 159 DIGIT(1); 160 DIGIT(2); 161 DIGIT(3); 162 DIGIT(4); 163 DIGIT(5); 164 DIGIT(6); 165 DIGIT(7); 166 DIGIT(8); 167 DIGIT(9); 168 169#undef DIGIT 170 171 // Home means to the beginning of the line. 172 case KeyCode::Key_Home: 173 m_unit = Unit::Character; 174 m_amount = START_OF_LINE; 175 m_is_complete = true; 176 break; 177 178 // If 0 appears while amount is 0, then it means beginning of line. 179 // Otherwise, it adds 0 to the amount. 180 case KeyCode::Key_0: 181 if (m_amount == 0) { 182 m_unit = Unit::Character; 183 m_amount = START_OF_LINE; 184 m_is_complete = true; 185 } else { 186 m_amount = m_amount * 10; 187 } 188 break; 189 190 // End or $ means end of line. 191 // TODO: d2$ in vim deletes to the end of the line and then the next line. 192 case KeyCode::Key_End: 193 case KeyCode::Key_Dollar: 194 m_unit = Unit::Character; 195 m_amount = END_OF_LINE; 196 m_is_complete = true; 197 break; 198 199 // ^ means the first non-whitespace character for this line. 200 // It deletes backwards if you're in front of it, and forwards if you're behind. 201 case KeyCode::Key_Circumflex: 202 m_unit = Unit::Character; 203 m_amount = START_OF_NON_WHITESPACE; 204 m_is_complete = true; 205 break; 206 207 // j, down or + operates on this line and amount line(s) after. 208 case KeyCode::Key_J: 209 case KeyCode::Key_Down: 210 case KeyCode::Key_Plus: 211 m_unit = Unit::Line; 212 213 if (m_amount == 0) 214 m_amount = 1; 215 216 m_is_complete = true; 217 break; 218 219 // k, up or - operates on this line and amount line(s) before. 220 case KeyCode::Key_K: 221 case KeyCode::Key_Up: 222 case KeyCode::Key_Minus: 223 m_unit = Unit::Line; 224 225 if (m_amount == 0) 226 m_amount = -1; 227 else 228 m_amount = -m_amount; 229 230 m_is_complete = true; 231 break; 232 233 // BS, h or left operates on this character and amount character(s) before. 234 case KeyCode::Key_Backspace: 235 case KeyCode::Key_H: 236 case KeyCode::Key_Left: 237 m_unit = Unit::Character; 238 239 if (m_amount == 0) 240 m_amount = -1; 241 else 242 m_amount = -m_amount; 243 244 m_is_complete = true; 245 break; 246 247 // l or right operates on this character and amount character(s) after. 248 case KeyCode::Key_L: 249 case KeyCode::Key_Right: 250 m_unit = Unit::Character; 251 252 if (m_amount > 0) 253 m_amount--; 254 255 m_is_complete = true; 256 break; 257 258 // w operates on amount word(s) after. 259 // W operates on amount WORD(s) after. 260 case KeyCode::Key_W: 261 if (shift) 262 m_unit = Unit::WORD; 263 else 264 m_unit = Unit::Word; 265 266 if (m_amount == 0) 267 m_amount = 1; 268 269 m_is_complete = true; 270 break; 271 272 // b operates on amount word(s) before. 273 // B operates on amount WORD(s) before. 274 case KeyCode::Key_B: 275 if (shift) 276 m_unit = Unit::WORD; 277 else 278 m_unit = Unit::Word; 279 280 if (m_amount == 0) 281 m_amount = -1; 282 else 283 m_amount = -m_amount; 284 285 m_is_complete = true; 286 break; 287 288 // e operates on amount of word(s) after, till the end of the last word. 289 // E operates on amount of WORD(s) after, till the end of the last WORD. 290 // ge operates on amount of word(s) before, till the end of the last word. 291 // gE operates on amount of WORD(s) before, till the end of the last WORD. 292 case KeyCode::Key_E: 293 if (shift) 294 m_unit = Unit::EndOfWORD; 295 else 296 m_unit = Unit::EndOfWord; 297 298 if (m_guirky_mode) { 299 if (m_amount == 0) 300 m_amount = -1; 301 else 302 m_amount = -m_amount; 303 304 m_guirky_mode = false; 305 } else { 306 if (m_amount == 0) 307 m_amount = 1; 308 } 309 310 m_is_complete = true; 311 break; 312 313 // g enables guirky (g-prefix commands) mode. 314 // gg operates from the start of the document to the cursor. 315 // G operates from the cursor to the end of the document. 316 case KeyCode::Key_G: 317 if (m_guirky_mode) { 318 if (shift) { 319 // gG is not a valid command in vim. 320 m_guirky_mode = false; 321 m_unit = Unit::Unknown; 322 m_is_complete = true; 323 } else { 324 m_guirky_mode = false; 325 m_unit = Unit::Document; 326 m_amount = -1; 327 m_is_complete = true; 328 } 329 } else { 330 if (shift) { 331 m_unit = Unit::Document; 332 m_amount = 1; 333 m_is_complete = true; 334 } else { 335 m_guirky_mode = true; 336 } 337 } 338 break; 339 340 // t operates until the given character. 341 case KeyCode::Key_T: 342 m_find_mode = FindMode::To; 343 m_should_consume_next_character = true; 344 345 if (m_amount == 0) 346 m_amount = 1; 347 break; 348 349 // f operates through the given character. 350 case KeyCode::Key_F: 351 m_find_mode = FindMode::Find; 352 m_should_consume_next_character = true; 353 354 if (m_amount == 0) 355 m_amount = 1; 356 break; 357 358 default: 359 m_unit = Unit::Unknown; 360 m_is_complete = true; 361 break; 362 } 363 364 if (should_use_guirky && m_guirky_mode) { 365 // If we didn't use the g then we cancel the motion. 366 m_guirky_mode = false; 367 m_unit = Unit::Unknown; 368 m_is_complete = true; 369 } 370} 371 372Optional<TextRange> VimMotion::get_range(VimEditingEngine& engine, bool normalize_for_position) 373{ 374 if (!is_complete() || is_cancelled()) 375 return {}; 376 377 TextEditor& editor = engine.editor(); 378 379 auto position = editor.cursor(); 380 int amount = abs(m_amount); 381 bool forwards = m_amount >= 0; 382 VimCursor cursor { editor, position, forwards }; 383 384 m_start_line = m_end_line = position.line(); 385 m_start_column = m_end_column = position.column(); 386 387 switch (m_unit) { 388 case Unit::Unknown: 389 VERIFY_NOT_REACHED(); 390 case Unit::Document: { 391 calculate_document_range(editor); 392 break; 393 } 394 case Unit::Line: { 395 calculate_line_range(editor, normalize_for_position); 396 break; 397 } 398 case Unit::EndOfWord: 399 case Unit::Word: 400 case Unit::EndOfWORD: 401 case Unit::WORD: { 402 calculate_word_range(cursor, amount, normalize_for_position); 403 break; 404 } 405 case Unit::Character: { 406 calculate_character_range(cursor, amount, normalize_for_position); 407 break; 408 } 409 case Unit::Find: { 410 calculate_find_range(cursor, amount); 411 break; 412 } 413 } 414 415 return { TextRange { { m_start_line, m_start_column }, { m_end_line, m_end_column } } }; 416} 417 418Optional<TextRange> VimMotion::get_repeat_range(VimEditingEngine& engine, VimMotion::Unit unit, bool normalize_for_position) 419{ 420 TextEditor& editor = engine.editor(); 421 422 if (m_amount > 0) { 423 m_amount--; 424 } else if (m_amount < 0) { 425 m_amount++; 426 } 427 auto position = editor.cursor(); 428 int amount = abs(m_amount); 429 bool forwards = m_amount >= 0; 430 VimCursor cursor { editor, position, forwards }; 431 432 m_start_line = m_end_line = position.line(); 433 m_start_column = m_end_column = position.column(); 434 435 switch (unit) { 436 case Unit::Line: { 437 calculate_line_range(editor, normalize_for_position); 438 break; 439 } 440 case Unit::Character: { 441 calculate_character_range(cursor, amount, normalize_for_position); 442 break; 443 } 444 default: 445 return {}; 446 } 447 448 return { TextRange { { m_start_line, m_start_column }, { m_end_line, m_end_column } } }; 449} 450 451void VimMotion::calculate_document_range(TextEditor& editor) 452{ 453 if (m_amount >= 0) { 454 m_end_line = editor.line_count() - 1; 455 auto& last_line = editor.line(m_end_line); 456 m_end_column = last_line.length(); 457 } else { 458 m_start_line = 0; 459 m_start_column = 0; 460 } 461} 462 463void VimMotion::calculate_line_range(TextEditor& editor, bool normalize_for_position) 464{ 465 // Use this line +/- m_amount lines. 466 m_start_column = 0; 467 m_end_column = 0; 468 469 if (m_amount >= 0) { 470 m_end_line = min(m_end_line + !normalize_for_position + m_amount, editor.line_count()); 471 472 // We can't delete to "last line + 1", so if we're on the last line, 473 // delete until the end. 474 if (m_end_line == editor.line_count()) { 475 m_end_line--; 476 m_end_column = editor.line(m_end_line).length(); 477 } 478 } else { 479 // Can't write it as max(start_line + m_amount, 0) because of unsigned 480 // shenanigans. 481 if (m_start_line <= (unsigned)-m_amount) 482 m_start_line = 0; 483 else 484 m_start_line += m_amount; 485 486 if (m_end_line == editor.line_count() - 1) 487 m_end_column = editor.line(m_end_line).length(); 488 else 489 m_end_line++; 490 } 491} 492 493void VimMotion::calculate_word_range(VimCursor& cursor, int amount, bool normalize_for_position) 494{ 495 enum { 496 Whitespace, 497 Word, 498 Punctuation, 499 Unknown 500 }; 501 // Word is defined as a-zA-Z0-9_. 502 auto part_of_word = [](u32 ch) { return ch == '_' || isalnum(ch); }; 503 auto part_of_punctuation = [](u32 ch) { return ch != '_' && ispunct(ch); }; 504 auto classify = [&](u32 ch) { 505 if (isspace(ch)) 506 return Whitespace; 507 else if (part_of_word(ch)) 508 return Word; 509 else if (part_of_punctuation(ch)) 510 return Punctuation; 511 else 512 return Unknown; 513 }; 514 515 // A small explanation for the code below: Because the direction of the 516 // movement for this motion determines what the "start" and "end" of a word 517 // is, the code below treats the motions like so: 518 // - Start of word: w/W/ge/gE 519 // - End of word: e/E/b/B 520 521 while (amount > 0) { 522 if (cursor.hit_edge()) 523 break; 524 525 if ((!cursor.forwards() && (m_unit == Unit::Word || m_unit == Unit::WORD)) 526 || (cursor.forwards() && (m_unit == Unit::EndOfWord || m_unit == Unit::EndOfWORD))) { 527 // End-of-word motions peek at the "next" character and if its class 528 // is not the same as ours, they move over one character (to end up 529 // at the new character class). This is required because we don't 530 // want to exit the word with end-of-word motions. 531 532 if (m_unit == Unit::Word || m_unit == Unit::EndOfWord) { 533 // Word-style peeking 534 int current_class = classify(cursor.current_char()); 535 int peeked_class = classify(cursor.peek()); 536 if (current_class != peeked_class) { 537 cursor.move(); 538 } 539 } else { 540 // WORD-style peeking, much simpler 541 if (isspace(cursor.peek())) { 542 cursor.move(); 543 } 544 } 545 } else { 546 // Start-of-word motions want to exit the word no matter which part 547 // of it we're in. 548 if (m_unit == Unit::Word || m_unit == Unit::EndOfWord) { 549 // Word-style consumption 550 if (part_of_word(cursor.current_char())) { 551 do { 552 cursor.move(); 553 if (cursor.hit_edge() || cursor.crossed_line_boundary()) 554 break; 555 } while (part_of_word(cursor.current_char())); 556 } else if (part_of_punctuation(cursor.current_char())) { 557 do { 558 cursor.move(); 559 if (cursor.hit_edge() || cursor.crossed_line_boundary()) 560 break; 561 } while (part_of_punctuation(cursor.current_char())); 562 } else if (cursor.on_empty_line()) { 563 cursor.move(); 564 } 565 } else { 566 // WORD-style consumption 567 if (!isspace(cursor.current_char())) { 568 do { 569 cursor.move(); 570 if (cursor.hit_edge() || cursor.crossed_line_boundary()) 571 break; 572 } while (!isspace(cursor.current_char())); 573 } else if (cursor.on_empty_line()) { 574 cursor.move(); 575 } 576 } 577 } 578 579 // Now consume any space if it exists. 580 if (isspace(cursor.current_char())) { 581 do { 582 cursor.move(); 583 if (cursor.hit_edge()) 584 break; 585 } while (isspace(cursor.current_char())); 586 } 587 588 if ((!cursor.forwards() && (m_unit == Unit::Word || m_unit == Unit::WORD)) 589 || (cursor.forwards() && (m_unit == Unit::EndOfWord || m_unit == Unit::EndOfWORD))) { 590 // End-of-word motions consume until the class doesn't match. 591 592 if (m_unit == Unit::Word || m_unit == Unit::EndOfWord) { 593 // Word-style consumption 594 int current_class = classify(cursor.current_char()); 595 while (classify(cursor.current_char()) == current_class) { 596 cursor.move(); 597 if (cursor.hit_edge() || cursor.crossed_line_boundary()) 598 break; 599 } 600 } else { 601 // WORD-style consumption 602 while (!isspace(cursor.current_char())) { 603 cursor.move(); 604 if (cursor.hit_edge() || cursor.crossed_line_boundary()) 605 break; 606 } 607 } 608 } 609 610 amount--; 611 } 612 613 // If we need to normalize for position then we do a move_reverse for 614 // end-of-word motions, because vim acts on end-of-word ranges through the 615 // character your cursor is placed on but acts on start-of-words *until* the 616 // character your cursor is placed on. 617 if (normalize_for_position) { 618 if ((!cursor.forwards() && (m_unit == Unit::Word || m_unit == Unit::WORD)) 619 || (cursor.forwards() && (m_unit == Unit::EndOfWord || m_unit == Unit::EndOfWORD))) { 620 if (!cursor.hit_edge()) 621 cursor.move_reverse(); 622 } 623 } 624 625 if (cursor.forwards()) { 626 m_end_line = cursor.current_position().line(); 627 m_end_column = cursor.current_position().column() + normalize_for_position; 628 } else { 629 m_start_line = cursor.current_position().line(); 630 m_start_column = cursor.current_position().column(); 631 } 632} 633 634void VimMotion::calculate_character_range(VimCursor& cursor, int amount, bool normalize_for_position) 635{ 636 if (m_amount == START_OF_LINE) { 637 m_start_column = 0; 638 } else if (m_amount == END_OF_LINE) { 639 m_end_column = cursor.current_line().length(); 640 } else if (m_amount == START_OF_NON_WHITESPACE) { 641 // Find the first non-whitespace character and set the range from current 642 // position to it. 643 TextPosition cursor_copy = cursor.current_position(); 644 cursor.current_position().set_column(0); 645 646 while (isspace(cursor.current_char())) { 647 if (cursor.will_cross_line_boundary()) 648 break; 649 650 cursor.move_forwards(); 651 } 652 653 if (cursor_copy < cursor.current_position()) 654 m_end_column = cursor.current_position().column() + 1; 655 else 656 m_start_column = cursor.current_position().column(); 657 } else { 658 while (amount > 0) { 659 if (cursor.hit_edge() || cursor.will_cross_line_boundary()) 660 break; 661 662 cursor.move(); 663 amount--; 664 } 665 666 if (cursor.forwards()) { 667 m_end_column = cursor.current_position().column() + 1 + normalize_for_position; 668 } else { 669 m_start_column = cursor.current_position().column(); 670 } 671 } 672} 673 674void VimMotion::calculate_find_range(VimCursor& cursor, int amount) 675{ 676 // Find the searched character (case-insensitive). 677 while (amount > 0) { 678 cursor.move_forwards(); 679 680 while ((unsigned)tolower(cursor.current_char()) != m_next_character) { 681 if (cursor.will_cross_line_boundary()) 682 break; 683 684 cursor.move_forwards(); 685 } 686 687 amount--; 688 } 689 690 // If we didn't find our character before reaching the end of the line, then 691 // we want the range to be invalid so no operation is performed. 692 if ((unsigned)tolower(cursor.current_char()) == m_next_character) { 693 // We found our character. 694 bool in_find_mode = m_find_mode == FindMode::Find; 695 m_end_column = cursor.current_position().column() + in_find_mode; 696 } 697 698 m_find_mode = FindMode::None; 699} 700 701Optional<TextPosition> VimMotion::get_position(VimEditingEngine& engine, bool in_visual_mode) 702{ 703 auto range_optional = get_range(engine, true); 704 if (!range_optional.has_value()) 705 return {}; 706 707 auto range = range_optional.value(); 708 if (!range.is_valid()) 709 return {}; 710 711 TextEditor& editor = engine.editor(); 712 auto cursor_position = editor.cursor(); 713 714 switch (m_unit) { 715 case Unit::Document: { 716 if (range.start().line() < cursor_position.line()) { 717 cursor_position.set_line(range.start().line()); 718 } else { 719 cursor_position.set_line(range.end().line()); 720 } 721 cursor_position.set_column(0); 722 723 return { cursor_position }; 724 } 725 case Unit::Line: { 726 size_t line_number; 727 // Because we select lines from start to end, we can't use that 728 // to get the new position, so we do some correction here. 729 if (range.start().line() < cursor_position.line() || m_amount < 0) { 730 line_number = range.start().line(); 731 } else { 732 line_number = range.end().line(); 733 } 734 735 auto& line = editor.line(line_number); 736 737 cursor_position.set_line(line_number); 738 if (line.length() <= cursor_position.column()) { 739 cursor_position.set_column(line.length() - 1); 740 } 741 742 return { cursor_position }; 743 } 744 default: { 745 if (range.start() < cursor_position) { 746 return { range.start() }; 747 } else { 748 // Ranges are end-exclusive. The normalize_for_position argument we pass 749 // above in get_range normalizes some values which shouldn't be 750 // end-exclusive during normal operations. 751 bool is_at_start = range.end().column() == 0; 752 auto& line = editor.line(range.end().line()); 753 754 size_t column = is_at_start ? 0 : range.end().column() - 1; 755 column = min(column, line.length() - (in_visual_mode ? 0 : 1)); 756 // Need to not go beyond the last character, as standard in vim. 757 758 return { TextPosition { range.end().line(), column } }; 759 } 760 } 761 } 762} 763 764void VimMotion::reset() 765{ 766 m_unit = Unit::Unknown; 767 m_amount = 0; 768 m_is_complete = false; 769} 770 771CursorWidth VimEditingEngine::cursor_width() const 772{ 773 return m_vim_mode == VimMode::Insert ? CursorWidth::NARROW : CursorWidth::WIDE; 774} 775 776bool VimEditingEngine::on_key(KeyEvent const& event) 777{ 778 switch (m_vim_mode) { 779 case (VimMode::Insert): 780 return on_key_in_insert_mode(event); 781 case (VimMode::Visual): 782 return on_key_in_visual_mode(event); 783 case (VimMode::VisualLine): 784 return on_key_in_visual_line_mode(event); 785 case (VimMode::Normal): 786 return on_key_in_normal_mode(event); 787 default: 788 VERIFY_NOT_REACHED(); 789 } 790 791 return false; 792} 793 794bool VimEditingEngine::on_key_in_insert_mode(KeyEvent const& event) 795{ 796 if (EditingEngine::on_key(event)) 797 return true; 798 799 if (event.ctrl()) { 800 switch (event.key()) { 801 case KeyCode::Key_W: 802 m_editor->delete_previous_word(); 803 return true; 804 case KeyCode::Key_H: 805 m_editor->delete_previous_char(); 806 return true; 807 case KeyCode::Key_U: 808 m_editor->delete_from_line_start_to_cursor(); 809 return true; 810 default: 811 break; 812 } 813 } 814 815 if (event.key() == KeyCode::Key_Escape || (event.ctrl() && event.key() == KeyCode::Key_LeftBracket) || (event.ctrl() && event.key() == KeyCode::Key_C)) { 816 if (m_editor->cursor().column() > 0) 817 move_one_left(); 818 switch_to_normal_mode(); 819 return true; 820 } 821 return false; 822} 823 824bool VimEditingEngine::on_key_in_normal_mode(KeyEvent const& event) 825{ 826 // Ignore auxiliary keypress events. 827 if (event.key() == KeyCode::Key_LeftShift 828 || event.key() == KeyCode::Key_RightShift 829 || event.key() == KeyCode::Key_Control 830 || event.key() == KeyCode::Key_Alt) { 831 return false; 832 } 833 834 if (m_previous_key == KeyCode::Key_D) { 835 if (event.key() == KeyCode::Key_D && !m_motion.should_consume_next_character()) { 836 if (m_motion.amount()) { 837 auto range = m_motion.get_repeat_range(*this, VimMotion::Unit::Line); 838 VERIFY(range.has_value()); 839 yank(*range, Line); 840 m_editor->delete_text_range(*range); 841 } else { 842 yank(Line); 843 delete_line(); 844 } 845 m_motion.reset(); 846 m_previous_key = {}; 847 } else { 848 m_motion.add_key_code(event.key(), event.ctrl(), event.shift(), event.alt()); 849 if (m_motion.is_complete()) { 850 if (!m_motion.is_cancelled()) { 851 auto range = m_motion.get_range(*this); 852 VERIFY(range.has_value()); 853 854 if (range->is_valid()) { 855 m_editor->delete_text_range(*range); 856 } 857 } 858 859 m_motion.reset(); 860 m_previous_key = {}; 861 } 862 } 863 } else if (m_previous_key == KeyCode::Key_Y) { 864 if (event.key() == KeyCode::Key_Y && !m_motion.should_consume_next_character()) { 865 if (m_motion.amount()) { 866 auto range = m_motion.get_repeat_range(*this, VimMotion::Unit::Line); 867 VERIFY(range.has_value()); 868 yank(*range, Line); 869 } else { 870 yank(Line); 871 } 872 m_motion.reset(); 873 m_previous_key = {}; 874 } else { 875 m_motion.add_key_code(event.key(), event.ctrl(), event.shift(), event.alt()); 876 if (m_motion.is_complete()) { 877 if (!m_motion.is_cancelled()) { 878 auto range = m_motion.get_range(*this); 879 VERIFY(range.has_value()); 880 881 if (range->is_valid()) { 882 m_editor->set_selection(*range); 883 yank(Selection); 884 m_editor->clear_selection(); 885 } 886 } 887 888 m_motion.reset(); 889 m_previous_key = {}; 890 } 891 } 892 } else if (m_previous_key == KeyCode::Key_C) { 893 if (event.key() == KeyCode::Key_C && !m_motion.should_consume_next_character()) { 894 // Needed because the code to replace the deleted line is called after delete_line() so 895 // what was the second last line before the delete, is now the last line. 896 bool was_second_last_line = m_editor->cursor().line() == m_editor->line_count() - 2; 897 yank(Line); 898 delete_line(); 899 if (was_second_last_line || (m_editor->cursor().line() != 0 && m_editor->cursor().line() != m_editor->line_count() - 1)) { 900 move_one_up(event); 901 move_to_logical_line_end(); 902 m_editor->add_code_point(0x0A); 903 } else if (m_editor->cursor().line() == 0) { 904 move_to_logical_line_beginning(); 905 m_editor->add_code_point(0x0A); 906 move_one_up(event); 907 } else if (m_editor->cursor().line() == m_editor->line_count() - 1) { 908 m_editor->add_code_point(0x0A); 909 } 910 switch_to_insert_mode(); 911 } else { 912 m_motion.add_key_code(event.key(), event.ctrl(), event.shift(), event.alt()); 913 if (m_motion.is_complete()) { 914 if (!m_motion.is_cancelled()) { 915 auto range = m_motion.get_range(*this); 916 VERIFY(range.has_value()); 917 918 if (range->is_valid()) { 919 m_editor->set_selection(*range); 920 yank(Selection); 921 m_editor->delete_text_range(*range); 922 switch_to_insert_mode(); 923 } 924 } 925 926 m_motion.reset(); 927 m_previous_key = {}; 928 } 929 } 930 } else { 931 if (m_motion.should_consume_next_character()) { 932 // We must consume the next character. 933 // FIXME: deduplicate with code below. 934 m_motion.add_key_code(event.key(), event.ctrl(), event.shift(), event.alt()); 935 if (m_motion.is_complete()) { 936 if (!m_motion.is_cancelled()) { 937 auto maybe_new_position = m_motion.get_position(*this); 938 if (maybe_new_position.has_value()) { 939 auto new_position = maybe_new_position.value(); 940 m_editor->set_cursor(new_position); 941 } 942 } 943 944 m_motion.reset(); 945 } 946 return true; 947 } 948 949 // Handle first any key codes that are to be applied regardless of modifiers. 950 switch (event.key()) { 951 case (KeyCode::Key_Escape): 952 if (m_editor->on_escape_pressed) 953 m_editor->on_escape_pressed(); 954 return true; 955 default: 956 break; 957 } 958 959 // SHIFT is pressed. 960 if (event.shift() && !event.ctrl() && !event.alt()) { 961 switch (event.key()) { 962 case (KeyCode::Key_A): 963 move_to_logical_line_end(); 964 switch_to_insert_mode(); 965 return true; 966 case (KeyCode::Key_D): 967 m_editor->delete_text_range({ m_editor->cursor(), { m_editor->cursor().line(), m_editor->current_line().length() } }); 968 if (m_editor->cursor().column() != 0) 969 move_one_left(); 970 break; 971 case (KeyCode::Key_I): 972 move_to_logical_line_beginning(); 973 switch_to_insert_mode(); 974 return true; 975 case (KeyCode::Key_O): 976 move_to_logical_line_beginning(); 977 m_editor->add_code_point(0x0A); 978 move_one_up(event); 979 switch_to_insert_mode(); 980 return true; 981 case (KeyCode::Key_LeftBrace): { 982 auto amount = m_motion.amount() > 0 ? m_motion.amount() : 1; 983 m_motion.reset(); 984 for (int i = 0; i < amount; i++) 985 move_to_previous_empty_lines_block(); 986 return true; 987 } 988 case (KeyCode::Key_RightBrace): { 989 auto amount = m_motion.amount() > 0 ? m_motion.amount() : 1; 990 m_motion.reset(); 991 for (int i = 0; i < amount; i++) 992 move_to_next_empty_lines_block(); 993 return true; 994 } 995 case (KeyCode::Key_J): { 996 // Looks a bit strange, but join without a repeat, with 1 as the repeat or 2 as the repeat all join the current and next lines 997 auto amount = (m_motion.amount() > 2) ? (m_motion.amount() - 1) : 1; 998 m_motion.reset(); 999 for (int i = 0; i < amount; i++) { 1000 if (m_editor->cursor().line() + 1 >= m_editor->line_count()) 1001 return true; 1002 move_to_logical_line_end(); 1003 m_editor->add_code_point(' '); 1004 TextPosition next_line = { m_editor->cursor().line() + 1, 0 }; 1005 m_editor->delete_text_range({ m_editor->cursor(), next_line }); 1006 move_one_left(); 1007 } 1008 return true; 1009 } 1010 case (KeyCode::Key_P): 1011 put_before(); 1012 break; 1013 case (KeyCode::Key_V): 1014 switch_to_visual_line_mode(); 1015 return true; 1016 default: 1017 break; 1018 } 1019 } 1020 1021 // CTRL is pressed. 1022 if (event.ctrl() && !event.shift() && !event.alt()) { 1023 switch (event.key()) { 1024 case (KeyCode::Key_D): 1025 move_half_page_down(); 1026 return true; 1027 case (KeyCode::Key_R): 1028 m_editor->redo(); 1029 return true; 1030 case (KeyCode::Key_U): 1031 move_half_page_up(); 1032 return true; 1033 default: 1034 break; 1035 } 1036 } 1037 1038 // FIXME: H and L movement keys will move to the previous or next line when reaching the beginning or end 1039 // of the line and pressed again. 1040 1041 // No modifier is pressed. 1042 if (!event.ctrl() && !event.shift() && !event.alt()) { 1043 switch (event.key()) { 1044 case (KeyCode::Key_A): 1045 move_one_right(); 1046 switch_to_insert_mode(); 1047 return true; 1048 case (KeyCode::Key_C): 1049 m_previous_key = event.key(); 1050 return true; 1051 case (KeyCode::Key_D): 1052 m_previous_key = event.key(); 1053 return true; 1054 case (KeyCode::Key_I): 1055 switch_to_insert_mode(); 1056 return true; 1057 case (KeyCode::Key_O): 1058 move_to_logical_line_end(); 1059 m_editor->add_code_point(0x0A); 1060 switch_to_insert_mode(); 1061 return true; 1062 case (KeyCode::Key_U): 1063 m_editor->undo(); 1064 return true; 1065 case (KeyCode::Key_X): { 1066 TextRange range = { m_editor->cursor(), { m_editor->cursor().line(), m_editor->cursor().column() + 1 } }; 1067 if (m_motion.amount()) { 1068 auto opt = m_motion.get_repeat_range(*this, VimMotion::Unit::Character); 1069 VERIFY(opt.has_value()); 1070 range = *opt; 1071 m_motion.reset(); 1072 } 1073 yank(range, Selection); 1074 m_editor->delete_text_range(range); 1075 return true; 1076 } 1077 case (KeyCode::Key_V): 1078 switch_to_visual_mode(); 1079 return true; 1080 case (KeyCode::Key_Y): 1081 m_previous_key = event.key(); 1082 return true; 1083 case (KeyCode::Key_P): 1084 put_after(); 1085 return true; 1086 case (KeyCode::Key_PageUp): 1087 move_page_up(); 1088 return true; 1089 case (KeyCode::Key_PageDown): 1090 move_page_down(); 1091 return true; 1092 default: 1093 break; 1094 } 1095 } 1096 1097 // If nothing else handled the key, we'll be feeding the motion state 1098 // machine instead. 1099 m_motion.add_key_code(event.key(), event.ctrl(), event.shift(), event.alt()); 1100 if (m_motion.is_complete()) { 1101 if (!m_motion.is_cancelled()) { 1102 auto maybe_new_position = m_motion.get_position(*this); 1103 if (maybe_new_position.has_value()) { 1104 auto new_position = maybe_new_position.value(); 1105 m_editor->set_cursor(new_position); 1106 } 1107 } 1108 1109 m_motion.reset(); 1110 } 1111 } 1112 return true; 1113} 1114 1115bool VimEditingEngine::on_key_in_visual_mode(KeyEvent const& event) 1116{ 1117 // If the motion state machine requires the next character, feed it. 1118 if (m_motion.should_consume_next_character()) { 1119 m_motion.add_key_code(event.key(), event.ctrl(), event.shift(), event.alt()); 1120 if (m_motion.is_complete()) { 1121 if (!m_motion.is_cancelled()) { 1122 auto maybe_new_position = m_motion.get_position(*this, true); 1123 if (maybe_new_position.has_value()) { 1124 auto new_position = maybe_new_position.value(); 1125 m_editor->set_cursor(new_position); 1126 update_selection_on_cursor_move(); 1127 } 1128 } 1129 1130 m_motion.reset(); 1131 } 1132 1133 return true; 1134 } 1135 1136 // Handle first any key codes that are to be applied regardless of modifiers. 1137 switch (event.key()) { 1138 case (KeyCode::Key_Escape): 1139 switch_to_normal_mode(); 1140 if (m_editor->on_escape_pressed) 1141 m_editor->on_escape_pressed(); 1142 return true; 1143 default: 1144 break; 1145 } 1146 1147 // SHIFT is pressed. 1148 if (event.shift() && !event.ctrl() && !event.alt()) { 1149 switch (event.key()) { 1150 case (KeyCode::Key_A): 1151 move_to_logical_line_end(); 1152 switch_to_insert_mode(); 1153 return true; 1154 case (KeyCode::Key_I): 1155 move_to_logical_line_beginning(); 1156 switch_to_insert_mode(); 1157 return true; 1158 case (KeyCode::Key_U): 1159 casefold_selection(Casing::Uppercase); 1160 switch_to_normal_mode(); 1161 return true; 1162 case (KeyCode::Key_Tilde): 1163 casefold_selection(Casing::Invertcase); 1164 switch_to_normal_mode(); 1165 return true; 1166 default: 1167 break; 1168 } 1169 } 1170 1171 // CTRL is pressed. 1172 if (event.ctrl() && !event.shift() && !event.alt()) { 1173 switch (event.key()) { 1174 case (KeyCode::Key_D): 1175 move_half_page_down(); 1176 update_selection_on_cursor_move(); 1177 return true; 1178 case (KeyCode::Key_U): 1179 move_half_page_up(); 1180 update_selection_on_cursor_move(); 1181 return true; 1182 default: 1183 break; 1184 } 1185 } 1186 1187 // No modifier is pressed. 1188 if (!event.ctrl() && !event.shift() && !event.alt()) { 1189 switch (event.key()) { 1190 case (KeyCode::Key_D): 1191 yank(Selection); 1192 m_editor->do_delete(); 1193 switch_to_normal_mode(); 1194 return true; 1195 case (KeyCode::Key_X): 1196 yank(Selection); 1197 m_editor->do_delete(); 1198 switch_to_normal_mode(); 1199 return true; 1200 case (KeyCode::Key_V): 1201 switch_to_normal_mode(); 1202 return true; 1203 case (KeyCode::Key_C): 1204 yank(Selection); 1205 m_editor->do_delete(); 1206 switch_to_insert_mode(); 1207 return true; 1208 case (KeyCode::Key_Y): 1209 yank(Selection); 1210 switch_to_normal_mode(); 1211 return true; 1212 case (KeyCode::Key_U): 1213 casefold_selection(Casing::Lowercase); 1214 switch_to_normal_mode(); 1215 return true; 1216 case (KeyCode::Key_PageUp): 1217 move_page_up(); 1218 update_selection_on_cursor_move(); 1219 return true; 1220 case (KeyCode::Key_PageDown): 1221 move_page_down(); 1222 update_selection_on_cursor_move(); 1223 return true; 1224 default: 1225 break; 1226 } 1227 } 1228 1229 // By default, we feed the motion state machine. 1230 m_motion.add_key_code(event.key(), event.ctrl(), event.shift(), event.alt()); 1231 if (m_motion.is_complete()) { 1232 if (!m_motion.is_cancelled()) { 1233 auto maybe_new_position = m_motion.get_position(*this, true); 1234 if (maybe_new_position.has_value()) { 1235 auto new_position = maybe_new_position.value(); 1236 m_editor->set_cursor(new_position); 1237 update_selection_on_cursor_move(); 1238 } 1239 } 1240 1241 m_motion.reset(); 1242 } 1243 1244 return true; 1245} 1246 1247bool VimEditingEngine::on_key_in_visual_line_mode(KeyEvent const& event) 1248{ 1249 // If the motion state machine requires the next character, feed it. 1250 if (m_motion.should_consume_next_character()) { 1251 m_motion.add_key_code(event.key(), event.ctrl(), event.shift(), event.alt()); 1252 if (m_motion.is_complete()) { 1253 if (!m_motion.is_cancelled()) { 1254 auto maybe_new_position = m_motion.get_position(*this, true); 1255 if (maybe_new_position.has_value()) { 1256 auto new_position = maybe_new_position.value(); 1257 m_editor->set_cursor(new_position); 1258 update_selection_on_cursor_move(); 1259 } 1260 } 1261 1262 m_motion.reset(); 1263 } 1264 1265 return true; 1266 } 1267 1268 // Handle first any key codes that are to be applied regardless of modifiers. 1269 switch (event.key()) { 1270 case (KeyCode::Key_Escape): 1271 switch_to_normal_mode(); 1272 if (m_editor->on_escape_pressed) 1273 m_editor->on_escape_pressed(); 1274 return true; 1275 default: 1276 break; 1277 } 1278 1279 // SHIFT is pressed. 1280 if (event.shift() && !event.ctrl() && !event.alt()) { 1281 switch (event.key()) { 1282 case (KeyCode::Key_U): 1283 casefold_selection(Casing::Uppercase); 1284 switch_to_normal_mode(); 1285 return true; 1286 case (KeyCode::Key_Tilde): 1287 casefold_selection(Casing::Invertcase); 1288 switch_to_normal_mode(); 1289 return true; 1290 default: 1291 break; 1292 } 1293 } 1294 1295 // CTRL is pressed. 1296 if (event.ctrl() && !event.shift() && !event.alt()) { 1297 switch (event.key()) { 1298 case (KeyCode::Key_D): 1299 move_half_page_down(); 1300 update_selection_on_cursor_move(); 1301 return true; 1302 case (KeyCode::Key_U): 1303 move_half_page_up(); 1304 update_selection_on_cursor_move(); 1305 return true; 1306 default: 1307 break; 1308 } 1309 } 1310 1311 // No modifier is pressed. 1312 if (!event.ctrl() && !event.shift() && !event.alt()) { 1313 switch (event.key()) { 1314 case (KeyCode::Key_D): 1315 yank(m_editor->selection(), Line); 1316 m_editor->do_delete(); 1317 switch_to_normal_mode(); 1318 return true; 1319 case (KeyCode::Key_X): 1320 yank(m_editor->selection(), Line); 1321 m_editor->do_delete(); 1322 switch_to_normal_mode(); 1323 return true; 1324 case (KeyCode::Key_C): 1325 yank(m_editor->selection(), Line); 1326 m_editor->do_delete(); 1327 switch_to_insert_mode(); 1328 return true; 1329 case (KeyCode::Key_Y): 1330 yank(m_editor->selection(), Line); 1331 switch_to_normal_mode(); 1332 return true; 1333 case (KeyCode::Key_U): 1334 casefold_selection(Casing::Lowercase); 1335 switch_to_normal_mode(); 1336 return true; 1337 case (KeyCode::Key_PageUp): 1338 move_page_up(); 1339 update_selection_on_cursor_move(); 1340 return true; 1341 case (KeyCode::Key_PageDown): 1342 move_page_down(); 1343 update_selection_on_cursor_move(); 1344 return true; 1345 default: 1346 break; 1347 } 1348 } 1349 1350 // By default, we feed the motion state machine. 1351 m_motion.add_key_code(event.key(), event.ctrl(), event.shift(), event.alt()); 1352 if (m_motion.is_complete()) { 1353 if (!m_motion.is_cancelled()) { 1354 auto maybe_new_position = m_motion.get_position(*this, true); 1355 if (maybe_new_position.has_value()) { 1356 auto new_position = maybe_new_position.value(); 1357 m_editor->set_cursor(new_position); 1358 update_selection_on_cursor_move(); 1359 } 1360 } 1361 1362 m_motion.reset(); 1363 } 1364 1365 return true; 1366} 1367 1368void VimEditingEngine::switch_to_normal_mode() 1369{ 1370 m_vim_mode = VimMode::Normal; 1371 m_editor->reset_cursor_blink(); 1372 m_previous_key = {}; 1373 clear_visual_mode_data(); 1374 m_motion.reset(); 1375}; 1376 1377void VimEditingEngine::switch_to_insert_mode() 1378{ 1379 m_vim_mode = VimMode::Insert; 1380 m_editor->reset_cursor_blink(); 1381 m_previous_key = {}; 1382 clear_visual_mode_data(); 1383 m_motion.reset(); 1384}; 1385 1386void VimEditingEngine::switch_to_visual_mode() 1387{ 1388 m_vim_mode = VimMode::Visual; 1389 m_editor->reset_cursor_blink(); 1390 m_previous_key = {}; 1391 m_selection_start_position = m_editor->cursor(); 1392 m_editor->selection().set(m_editor->cursor(), { m_editor->cursor().line(), m_editor->cursor().column() + 1 }); 1393 m_editor->did_update_selection(); 1394 m_motion.reset(); 1395} 1396 1397void VimEditingEngine::switch_to_visual_line_mode() 1398{ 1399 m_vim_mode = VimMode::VisualLine; 1400 m_editor->reset_cursor_blink(); 1401 m_previous_key = {}; 1402 m_selection_start_position = TextPosition { m_editor->cursor().line(), 0 }; 1403 m_editor->selection().set(m_selection_start_position, { m_editor->cursor().line(), m_editor->current_line().length() }); 1404 m_editor->did_update_selection(); 1405 m_motion.reset(); 1406} 1407 1408void VimEditingEngine::update_selection_on_cursor_move() 1409{ 1410 auto cursor = m_editor->cursor(); 1411 auto start = m_selection_start_position < cursor ? m_selection_start_position : cursor; 1412 auto end = m_selection_start_position < cursor ? cursor : m_selection_start_position; 1413 1414 if (end.column() >= m_editor->current_line().length()) { 1415 if (end.line() != m_editor->line_count() - 1) 1416 end = { end.line() + 1, 0 }; 1417 } else { 1418 end.set_column(end.column() + 1); 1419 } 1420 1421 if (m_vim_mode == VimMode::VisualLine) { 1422 start = TextPosition { start.line(), 0 }; 1423 end = TextPosition { end.line(), m_editor->line(end.line()).length() }; 1424 } 1425 1426 m_editor->selection().set(start, end); 1427 m_editor->did_update_selection(); 1428} 1429 1430void VimEditingEngine::clamp_cursor_position() 1431{ 1432 auto cursor = m_editor->cursor(); 1433 if (cursor.column() >= m_editor->current_line().length()) { 1434 cursor.set_column(m_editor->current_line().length() - 1); 1435 m_editor->set_cursor(cursor); 1436 } 1437} 1438 1439void VimEditingEngine::clear_visual_mode_data() 1440{ 1441 if (m_editor->has_selection()) { 1442 m_editor->selection().clear(); 1443 m_editor->did_update_selection(); 1444 clamp_cursor_position(); 1445 } 1446 m_selection_start_position = {}; 1447} 1448 1449void VimEditingEngine::move_half_page_up() 1450{ 1451 move_up(0.5); 1452}; 1453 1454void VimEditingEngine::move_half_page_down() 1455{ 1456 move_down(0.5); 1457}; 1458 1459void VimEditingEngine::yank(YankType type) 1460{ 1461 m_yank_type = type; 1462 if (type == YankType::Line) { 1463 m_yank_buffer = m_editor->current_line().to_utf8(); 1464 } else { 1465 m_yank_buffer = m_editor->selected_text(); 1466 } 1467 1468 // When putting this, auto indentation (if enabled) will indent as far as 1469 // is necessary, then any whitespace captured before the yanked text will be placed 1470 // after the indentation, doubling the indentation. 1471 if (m_editor->is_automatic_indentation_enabled()) 1472 m_yank_buffer = m_yank_buffer.trim_whitespace(TrimMode::Left); 1473} 1474 1475void VimEditingEngine::yank(TextRange range, YankType yank_type) 1476{ 1477 m_yank_type = yank_type; 1478 m_yank_buffer = m_editor->document().text_in_range(range).trim_whitespace(AK::TrimMode::Right); 1479} 1480 1481void VimEditingEngine::put_before() 1482{ 1483 auto amount = m_motion.amount() ? m_motion.amount() : 1; 1484 m_motion.reset(); 1485 if (m_yank_type == YankType::Line) { 1486 move_to_logical_line_beginning(); 1487 StringBuilder sb = StringBuilder(amount * (m_yank_buffer.length() + 1)); 1488 for (auto i = 0; i < amount; i++) { 1489 sb.append(m_yank_buffer); 1490 sb.append_code_point(0x0A); 1491 } 1492 m_editor->insert_at_cursor_or_replace_selection(sb.to_deprecated_string()); 1493 m_editor->set_cursor({ m_editor->cursor().line(), m_editor->current_line().first_non_whitespace_column() }); 1494 } else { 1495 StringBuilder sb = StringBuilder(m_yank_buffer.length() * amount); 1496 for (auto i = 0; i < amount; i++) { 1497 sb.append(m_yank_buffer); 1498 } 1499 m_editor->insert_at_cursor_or_replace_selection(sb.to_deprecated_string()); 1500 move_one_left(); 1501 } 1502} 1503 1504void VimEditingEngine::put_after() 1505{ 1506 auto amount = m_motion.amount() ? m_motion.amount() : 1; 1507 m_motion.reset(); 1508 if (m_yank_type == YankType::Line) { 1509 move_to_logical_line_end(); 1510 StringBuilder sb = StringBuilder(m_yank_buffer.length() + 1); 1511 for (auto i = 0; i < amount; i++) { 1512 sb.append_code_point(0x0A); 1513 sb.append(m_yank_buffer); 1514 } 1515 m_editor->insert_at_cursor_or_replace_selection(sb.to_deprecated_string()); 1516 m_editor->set_cursor({ m_editor->cursor().line(), m_editor->current_line().first_non_whitespace_column() }); 1517 } else { 1518 // FIXME: If attempting to put on the last column a line, 1519 // the buffer will bne placed on the next line due to the move_one_left/right behavior. 1520 move_one_right(); 1521 StringBuilder sb = StringBuilder(m_yank_buffer.length() * amount); 1522 for (auto i = 0; i < amount; i++) { 1523 sb.append(m_yank_buffer); 1524 } 1525 m_editor->insert_at_cursor_or_replace_selection(sb.to_deprecated_string()); 1526 move_one_left(); 1527 } 1528} 1529 1530void VimEditingEngine::move_to_previous_empty_lines_block() 1531{ 1532 VERIFY(!m_editor.is_null()); 1533 size_t line_idx = m_editor->cursor().line(); 1534 bool skipping_initial_empty_lines = true; 1535 while (line_idx > 0) { 1536 if (m_editor->document().line(line_idx).is_empty()) { 1537 if (!skipping_initial_empty_lines) 1538 break; 1539 } else { 1540 skipping_initial_empty_lines = false; 1541 } 1542 line_idx--; 1543 } 1544 1545 TextPosition new_cursor = { line_idx, 0 }; 1546 1547 m_editor->set_cursor(new_cursor); 1548}; 1549 1550void VimEditingEngine::move_to_next_empty_lines_block() 1551{ 1552 VERIFY(!m_editor.is_null()); 1553 size_t line_idx = m_editor->cursor().line(); 1554 bool skipping_initial_empty_lines = true; 1555 while (line_idx < m_editor->line_count() - 1) { 1556 if (m_editor->document().line(line_idx).is_empty()) { 1557 if (!skipping_initial_empty_lines) 1558 break; 1559 } else { 1560 skipping_initial_empty_lines = false; 1561 } 1562 line_idx++; 1563 } 1564 1565 TextPosition new_cursor = { line_idx, 0 }; 1566 1567 m_editor->set_cursor(new_cursor); 1568}; 1569 1570void VimEditingEngine::casefold_selection(Casing casing) 1571{ 1572 VERIFY(!m_editor.is_null()); 1573 VERIFY(m_editor->has_selection()); 1574 1575 switch (casing) { 1576 case Casing::Uppercase: 1577 m_editor->insert_at_cursor_or_replace_selection(m_editor->selected_text().to_uppercase()); 1578 return; 1579 case Casing::Lowercase: 1580 m_editor->insert_at_cursor_or_replace_selection(m_editor->selected_text().to_lowercase()); 1581 return; 1582 case Casing::Invertcase: 1583 m_editor->insert_at_cursor_or_replace_selection(m_editor->selected_text().invert_case()); 1584 return; 1585 } 1586} 1587 1588}