Kaynağa Gözat

LibGUI: Implement Vim motion system

This patch implements Vim motions.  The VimMotion class will accept
keycodes from the editing engine to build up a motion, and will
signal when a motion is complete via VimMotion::is_complete().  The
editing engine can then call VimMotion::get_range() to obtain a
TextRange object which can be used to perform operations on the text,
or VimMotion::get_position() to obtain a TextPosition which is the
new position of the cursor after the motion.

Currently, the following motions are supported:

- h/j/k/l, regular Vim line and character movements
- 0/^/$, start/end of line and start of non-blank
- w/e/b/ge, word-related movements
- W/E/B/gE, WORD (anything non-blank) versions of the above motions
- gg/G, document related movements
- t/f, to/find character

All motions except gg/G accept a number prefix to repeat the motion that
many times.

This patch updates insert, normal and visual modes to use this motion
system for movement.
sin-ack 4 yıl önce
ebeveyn
işleme
53aec3e06d

+ 6 - 0
Userland/Libraries/LibGUI/EditingEngine.h

@@ -29,6 +29,12 @@ public:
     void attach(TextEditor& editor);
     void attach(TextEditor& editor);
     void detach();
     void detach();
 
 
+    TextEditor& editor()
+    {
+        VERIFY(!m_editor.is_null());
+        return *m_editor.unsafe_ptr();
+    }
+
     virtual bool on_key(const KeyEvent& event);
     virtual bool on_key(const KeyEvent& event);
 
 
 protected:
 protected:

Dosya farkı çok büyük olduğundan ihmal edildi
+ 930 - 253
Userland/Libraries/LibGUI/VimEditingEngine.cpp


+ 131 - 1
Userland/Libraries/LibGUI/VimEditingEngine.h

@@ -6,10 +6,140 @@
 
 
 #pragma once
 #pragma once
 
 
+#include <AK/Optional.h>
+#include <LibCore/Object.h>
 #include <LibGUI/EditingEngine.h>
 #include <LibGUI/EditingEngine.h>
+#include <LibGUI/TextRange.h>
 
 
 namespace GUI {
 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 {
 class VimEditingEngine final : public EditingEngine {
 
 
 public:
 public:
@@ -30,6 +160,7 @@ private:
     };
     };
 
 
     VimMode m_vim_mode { VimMode::Normal };
     VimMode m_vim_mode { VimMode::Normal };
+    VimMotion m_motion;
 
 
     YankType m_yank_type {};
     YankType m_yank_type {};
     String m_yank_buffer {};
     String m_yank_buffer {};
@@ -37,7 +168,6 @@ private:
     void yank(TextRange);
     void yank(TextRange);
     void put(const GUI::KeyEvent&);
     void put(const GUI::KeyEvent&);
 
 
-    TextPosition m_selection_start_position {};
     void update_selection_on_cursor_move();
     void update_selection_on_cursor_move();
     void clear_visual_mode_data();
     void clear_visual_mode_data();
 
 

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor