|
@@ -6,10 +6,140 @@
|
|
|
|
|
|
#pragma once
|
|
|
|
|
|
+#include <AK/Optional.h>
|
|
|
+#include <LibCore/Object.h>
|
|
|
#include <LibGUI/EditingEngine.h>
|
|
|
+#include <LibGUI/TextRange.h>
|
|
|
|
|
|
namespace GUI {
|
|
|
|
|
|
+// Wrapper over TextPosition that makes it easier to move it around as a cursor,
|
|
|
+// and to get the current line or character.
|
|
|
+class VimCursor {
|
|
|
+public:
|
|
|
+ VimCursor(TextEditor& editor, TextPosition initial_position, bool forwards)
|
|
|
+ : m_editor(editor)
|
|
|
+ , m_position(initial_position)
|
|
|
+ , m_forwards(forwards)
|
|
|
+ {
|
|
|
+ }
|
|
|
+
|
|
|
+ void move_forwards();
|
|
|
+ void move_backwards();
|
|
|
+
|
|
|
+ // Move a single character in the current direction.
|
|
|
+ void move();
|
|
|
+ // Move a single character in reverse.
|
|
|
+ void move_reverse();
|
|
|
+ // Peek a single character in the current direction.
|
|
|
+ u32 peek();
|
|
|
+ // Peek a single character in reverse.
|
|
|
+ u32 peek_reverse();
|
|
|
+ // Get the character the cursor is currently on.
|
|
|
+ u32 current_char();
|
|
|
+ // Get the line the cursor is currently on.
|
|
|
+ TextDocumentLine& current_line();
|
|
|
+ // Get the current position.
|
|
|
+ TextPosition& current_position() { return m_position; }
|
|
|
+
|
|
|
+ // Did we hit the edge of the document?
|
|
|
+ bool hit_edge() { return m_hit_edge; }
|
|
|
+ // Will the next move cross a line boundary?
|
|
|
+ bool will_cross_line_boundary();
|
|
|
+ // Did we cross a line boundary?
|
|
|
+ bool crossed_line_boundary() { return m_crossed_line_boundary; }
|
|
|
+ // Are we on an empty line?
|
|
|
+ bool on_empty_line();
|
|
|
+ // Are we going forwards?
|
|
|
+ bool forwards() { return m_forwards; }
|
|
|
+
|
|
|
+private:
|
|
|
+ TextEditor& m_editor;
|
|
|
+ TextPosition m_position;
|
|
|
+ bool m_forwards;
|
|
|
+
|
|
|
+ u32 m_cached_char { 0 };
|
|
|
+
|
|
|
+ bool m_hit_edge { false };
|
|
|
+ bool m_crossed_line_boundary { false };
|
|
|
+};
|
|
|
+
|
|
|
+class VimMotion {
|
|
|
+public:
|
|
|
+ enum class Unit {
|
|
|
+ // The motion isn't complete yet, or was invalid.
|
|
|
+ Unknown,
|
|
|
+ // Document. Anything non-negative is counted as G while anything else is gg.
|
|
|
+ Document,
|
|
|
+ // Lines.
|
|
|
+ Line,
|
|
|
+ // A sequence of letters, digits and underscores, or a sequence of other
|
|
|
+ // non-blank characters separated by whitespace.
|
|
|
+ Word,
|
|
|
+ // A sequence of non-blank characters separated by whitespace.
|
|
|
+ // This is how Vim separates w from W.
|
|
|
+ WORD,
|
|
|
+ // End of a word. This is basically the same as a word but it doesn't
|
|
|
+ // trim the spaces at the end.
|
|
|
+ EndOfWord,
|
|
|
+ // End of a WORD.
|
|
|
+ EndOfWORD,
|
|
|
+ // Characters (or Unicode codepoints based on how pedantic you want to
|
|
|
+ // get).
|
|
|
+ Character,
|
|
|
+ // Used for find-mode.
|
|
|
+ Find
|
|
|
+ };
|
|
|
+ enum class FindMode {
|
|
|
+ /// Find mode is not enabled.
|
|
|
+ None,
|
|
|
+ /// Finding until the given character.
|
|
|
+ To,
|
|
|
+ /// Finding through the given character.
|
|
|
+ Find
|
|
|
+ };
|
|
|
+
|
|
|
+ void add_key_code(KeyCode key, bool ctrl, bool shift, bool alt);
|
|
|
+ Optional<TextRange> get_range(class VimEditingEngine& engine, bool normalize_for_position = false);
|
|
|
+ Optional<TextPosition> get_position(VimEditingEngine& engine);
|
|
|
+ void reset();
|
|
|
+
|
|
|
+ /// Returns whether the motion should consume the next character no matter what.
|
|
|
+ /// Used for f and t motions.
|
|
|
+ bool should_consume_next_character() { return m_should_consume_next_character; }
|
|
|
+ bool is_complete() { return m_is_complete; }
|
|
|
+ bool is_cancelled() { return m_is_complete && m_unit == Unit::Unknown; }
|
|
|
+ Unit unit() { return m_unit; }
|
|
|
+ int amount() { return m_amount; }
|
|
|
+
|
|
|
+ // FIXME: come up with a better way to signal start/end of line than sentinels?
|
|
|
+ static constexpr int START_OF_LINE = NumericLimits<int>::min();
|
|
|
+ static constexpr int START_OF_NON_WHITESPACE = NumericLimits<int>::min() + 1;
|
|
|
+ static constexpr int END_OF_LINE = NumericLimits<int>::max();
|
|
|
+
|
|
|
+private:
|
|
|
+ void calculate_document_range(TextEditor&);
|
|
|
+ void calculate_line_range(TextEditor&, bool normalize_for_position);
|
|
|
+ void calculate_word_range(VimCursor&, int amount, bool normalize_for_position);
|
|
|
+ void calculate_WORD_range(VimCursor&, int amount, bool normalize_for_position);
|
|
|
+ void calculate_character_range(VimCursor&, int amount, bool normalize_for_position);
|
|
|
+ void calculate_find_range(VimCursor&, int amount);
|
|
|
+
|
|
|
+ Unit m_unit { Unit::Unknown };
|
|
|
+ int m_amount { 0 };
|
|
|
+ bool m_is_complete { false };
|
|
|
+ bool m_guirky_mode { false };
|
|
|
+ bool m_should_consume_next_character { false };
|
|
|
+
|
|
|
+ FindMode m_find_mode { FindMode::None };
|
|
|
+ u32 m_next_character { 0 };
|
|
|
+
|
|
|
+ size_t m_start_line { 0 };
|
|
|
+ size_t m_start_column { 0 };
|
|
|
+ size_t m_end_line { 0 };
|
|
|
+ size_t m_end_column { 0 };
|
|
|
+};
|
|
|
+
|
|
|
class VimEditingEngine final : public EditingEngine {
|
|
|
|
|
|
public:
|
|
@@ -30,6 +160,7 @@ private:
|
|
|
};
|
|
|
|
|
|
VimMode m_vim_mode { VimMode::Normal };
|
|
|
+ VimMotion m_motion;
|
|
|
|
|
|
YankType m_yank_type {};
|
|
|
String m_yank_buffer {};
|
|
@@ -37,7 +168,6 @@ private:
|
|
|
void yank(TextRange);
|
|
|
void put(const GUI::KeyEvent&);
|
|
|
|
|
|
- TextPosition m_selection_start_position {};
|
|
|
void update_selection_on_cursor_move();
|
|
|
void clear_visual_mode_data();
|
|
|
|