mirror of
https://github.com/SerenityOS/serenity.git
synced 2025-01-24 10:22:05 -05:00
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.
This commit is contained in:
parent
bb096429ad
commit
53aec3e06d
3 changed files with 1078 additions and 265 deletions
|
@ -29,6 +29,12 @@ public:
|
|||
void attach(TextEditor& editor);
|
||||
void detach();
|
||||
|
||||
TextEditor& editor()
|
||||
{
|
||||
VERIFY(!m_editor.is_null());
|
||||
return *m_editor.unsafe_ptr();
|
||||
}
|
||||
|
||||
virtual bool on_key(const KeyEvent& event);
|
||||
|
||||
protected:
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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();
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue