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