VimEditingEngine.h 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. /*
  2. * Copyright (c) 2021, the SerenityOS developers.
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #pragma once
  7. #include <AK/Optional.h>
  8. #include <LibCore/Object.h>
  9. #include <LibGUI/EditingEngine.h>
  10. #include <LibGUI/TextRange.h>
  11. namespace GUI {
  12. // Wrapper over TextPosition that makes it easier to move it around as a cursor,
  13. // and to get the current line or character.
  14. class VimCursor {
  15. public:
  16. VimCursor(TextEditor& editor, TextPosition initial_position, bool forwards)
  17. : m_editor(editor)
  18. , m_position(initial_position)
  19. , m_forwards(forwards)
  20. {
  21. }
  22. void move_forwards();
  23. void move_backwards();
  24. // Move a single character in the current direction.
  25. void move();
  26. // Move a single character in reverse.
  27. void move_reverse();
  28. // Peek a single character in the current direction.
  29. u32 peek();
  30. // Peek a single character in reverse.
  31. u32 peek_reverse();
  32. // Get the character the cursor is currently on.
  33. u32 current_char();
  34. // Get the line the cursor is currently on.
  35. TextDocumentLine& current_line();
  36. // Get the current position.
  37. TextPosition& current_position() { return m_position; }
  38. // Did we hit the edge of the document?
  39. bool hit_edge() { return m_hit_edge; }
  40. // Will the next move cross a line boundary?
  41. bool will_cross_line_boundary();
  42. // Did we cross a line boundary?
  43. bool crossed_line_boundary() { return m_crossed_line_boundary; }
  44. // Are we on an empty line?
  45. bool on_empty_line();
  46. // Are we going forwards?
  47. bool forwards() { return m_forwards; }
  48. private:
  49. TextEditor& m_editor;
  50. TextPosition m_position;
  51. bool m_forwards;
  52. u32 m_cached_char { 0 };
  53. bool m_hit_edge { false };
  54. bool m_crossed_line_boundary { false };
  55. };
  56. class VimMotion {
  57. public:
  58. enum class Unit {
  59. // The motion isn't complete yet, or was invalid.
  60. Unknown,
  61. // Document. Anything non-negative is counted as G while anything else is gg.
  62. Document,
  63. // Lines.
  64. Line,
  65. // A sequence of letters, digits and underscores, or a sequence of other
  66. // non-blank characters separated by whitespace.
  67. Word,
  68. // A sequence of non-blank characters separated by whitespace.
  69. // This is how Vim separates w from W.
  70. WORD,
  71. // End of a word. This is basically the same as a word but it doesn't
  72. // trim the spaces at the end.
  73. EndOfWord,
  74. // End of a WORD.
  75. EndOfWORD,
  76. // Characters (or Unicode code points based on how pedantic you want to
  77. // get).
  78. Character,
  79. // Used for find-mode.
  80. Find
  81. };
  82. enum class FindMode {
  83. /// Find mode is not enabled.
  84. None,
  85. /// Finding until the given character.
  86. To,
  87. /// Finding through the given character.
  88. Find
  89. };
  90. void add_key_code(KeyCode key, bool ctrl, bool shift, bool alt);
  91. Optional<TextRange> get_range(class VimEditingEngine& engine, bool normalize_for_position = false);
  92. Optional<TextRange> get_repeat_range(class VimEditingEngine& engine, Unit, bool normalize_for_position = false);
  93. Optional<TextPosition> get_position(VimEditingEngine& engine, bool in_visual_mode = false);
  94. void reset();
  95. /// Returns whether the motion should consume the next character no matter what.
  96. /// Used for f and t motions.
  97. bool should_consume_next_character() { return m_should_consume_next_character; }
  98. bool is_complete() { return m_is_complete; }
  99. bool is_cancelled() { return m_is_complete && m_unit == Unit::Unknown; }
  100. Unit unit() { return m_unit; }
  101. int amount() { return m_amount; }
  102. // FIXME: come up with a better way to signal start/end of line than sentinels?
  103. static constexpr int START_OF_LINE = NumericLimits<int>::min();
  104. static constexpr int START_OF_NON_WHITESPACE = NumericLimits<int>::min() + 1;
  105. static constexpr int END_OF_LINE = NumericLimits<int>::max();
  106. private:
  107. void calculate_document_range(TextEditor&);
  108. void calculate_line_range(TextEditor&, bool normalize_for_position);
  109. void calculate_word_range(VimCursor&, int amount, bool normalize_for_position);
  110. void calculate_WORD_range(VimCursor&, int amount, bool normalize_for_position);
  111. void calculate_character_range(VimCursor&, int amount, bool normalize_for_position);
  112. void calculate_find_range(VimCursor&, int amount);
  113. Unit m_unit { Unit::Unknown };
  114. int m_amount { 0 };
  115. bool m_is_complete { false };
  116. bool m_guirky_mode { false };
  117. bool m_should_consume_next_character { false };
  118. FindMode m_find_mode { FindMode::None };
  119. u32 m_next_character { 0 };
  120. size_t m_start_line { 0 };
  121. size_t m_start_column { 0 };
  122. size_t m_end_line { 0 };
  123. size_t m_end_column { 0 };
  124. };
  125. class VimEditingEngine final : public EditingEngine {
  126. public:
  127. virtual CursorWidth cursor_width() const override;
  128. virtual bool on_key(KeyEvent const& event) override;
  129. private:
  130. enum VimMode {
  131. Normal,
  132. Insert,
  133. Visual
  134. };
  135. enum YankType {
  136. Line,
  137. Selection
  138. };
  139. VimMode m_vim_mode { VimMode::Normal };
  140. VimMotion m_motion;
  141. YankType m_yank_type {};
  142. String m_yank_buffer {};
  143. void yank(YankType);
  144. void yank(TextRange, YankType yank_type);
  145. void put_before();
  146. void put_after();
  147. TextPosition m_selection_start_position = {};
  148. void update_selection_on_cursor_move();
  149. void clamp_cursor_position();
  150. void clear_visual_mode_data();
  151. KeyCode m_previous_key {};
  152. void switch_to_normal_mode();
  153. void switch_to_insert_mode();
  154. void switch_to_visual_mode();
  155. void move_half_page_up();
  156. void move_half_page_down();
  157. void move_to_previous_empty_lines_block();
  158. void move_to_next_empty_lines_block();
  159. bool on_key_in_insert_mode(KeyEvent const& event);
  160. bool on_key_in_normal_mode(KeyEvent const& event);
  161. bool on_key_in_visual_mode(KeyEvent const& event);
  162. virtual EngineType engine_type() const override { return EngineType::Vim; }
  163. };
  164. }