Serenity Operating System
at master 204 lines 6.3 kB view raw
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}