TextEditor: Add vim status indicators to the statusbar

When using the VimEditingEngine in the TextEditor, vim's mode and the
previous key are shown in the editor's statusbar.
This commit is contained in:
Zac 2021-01-27 15:49:29 +10:00 committed by Andreas Kling
parent e11ec20650
commit bd6d0d2295
Notes: sideshowbarker 2024-07-18 22:38:32 +09:00
10 changed files with 171 additions and 20 deletions

View file

@ -39,11 +39,13 @@
#include <LibGUI/BoxLayout.h>
#include <LibGUI/Button.h>
#include <LibGUI/CppSyntaxHighlighter.h>
#include <LibGUI/EditingEngine.h>
#include <LibGUI/FilePicker.h>
#include <LibGUI/FontPicker.h>
#include <LibGUI/GMLSyntaxHighlighter.h>
#include <LibGUI/INISyntaxHighlighter.h>
#include <LibGUI/JSSyntaxHighlighter.h>
#include <LibGUI/Label.h>
#include <LibGUI/Menu.h>
#include <LibGUI/MenuBar.h>
#include <LibGUI/MessageBox.h>
@ -57,6 +59,7 @@
#include <LibGUI/ToolBarContainer.h>
#include <LibGUI/VimEditingEngine.h>
#include <LibGfx/Font.h>
#include <LibGfx/FontDatabase.h>
#include <LibMarkdown/Document.h>
#include <LibWeb/OutOfProcessWebView.h>
#include <string.h>
@ -301,6 +304,21 @@ TextEditorWidget::TextEditorWidget()
m_statusbar = *find_descendant_of_type_named<GUI::StatusBar>("statusbar");
m_statusbar->label(m_vim_mode_statusbar_index)->set_visible(m_editor->editing_engine()->type() == GUI::EditingEngineType::Vim);
m_statusbar->label(m_vim_mode_statusbar_index)->set_font(Gfx::FontDatabase::default_bold_font());
m_statusbar->label(m_vim_previous_keys_statusbar_index)->set_visible(m_editor->editing_engine()->type() == GUI::EditingEngineType::Vim);
m_editor->on_vim_statusbar_messages_changed = [this] {
m_statusbar->set_text(m_vim_mode_statusbar_index, m_editor->vim_mode_statusbar_message());
m_statusbar->set_text(m_vim_previous_keys_statusbar_index, m_editor->vim_previous_keys_statusbar_message());
};
m_editor->on_editing_engine_changed = [this]() {
m_statusbar->label(m_vim_mode_statusbar_index)->set_visible(m_editor->editing_engine()->type() == GUI::EditingEngineType::Vim);
m_statusbar->label(m_vim_previous_keys_statusbar_index)->set_visible(m_editor->editing_engine()->type() == GUI::EditingEngineType::Vim);
};
m_editor->on_cursor_change = [this] { update_statusbar_cursor_position(); };
m_new_action = GUI::Action::create("New", { Mod_Ctrl, Key_N }, Gfx::Bitmap::load_from_file("/res/icons/16x16/new.png"), [this](const GUI::Action&) {
@ -666,5 +684,5 @@ void TextEditorWidget::update_statusbar_cursor_position()
{
StringBuilder builder;
builder.appendff("Line: {}, Column: {}", m_editor->cursor().line() + 1, m_editor->cursor().column());
m_statusbar->set_text(builder.to_string());
m_statusbar->set_text(m_cursor_position_statusbar_index, builder.to_string());
}

View file

@ -91,6 +91,9 @@ private:
RefPtr<GUI::Action> m_html_preview_action;
RefPtr<GUI::StatusBar> m_statusbar;
const int m_cursor_position_statusbar_index = 0;
const int m_vim_mode_statusbar_index = 1;
const int m_vim_previous_keys_statusbar_index = 2;
RefPtr<GUI::TextBox> m_find_textbox;
RefPtr<GUI::TextBox> m_replace_textbox;

View file

@ -84,5 +84,6 @@
@GUI::StatusBar {
name: "statusbar"
label_count: 3
}
}

View file

@ -37,6 +37,11 @@ enum CursorWidth {
WIDE
};
enum EditingEngineType {
Regular,
Vim
};
class EditingEngine {
AK_MAKE_NONCOPYABLE(EditingEngine);
AK_MAKE_NONMOVABLE(EditingEngine);
@ -51,11 +56,15 @@ public:
virtual bool on_key(const KeyEvent& event);
EditingEngineType type() const { return m_editing_engine_type; }
protected:
EditingEngine() { }
WeakPtr<TextEditor> m_editor;
EditingEngineType m_editing_engine_type;
void move_one_left(const KeyEvent& event);
void move_one_right(const KeyEvent& event);
void move_one_up(const KeyEvent& event);

View file

@ -30,6 +30,11 @@
namespace GUI {
RegularEditingEngine::RegularEditingEngine()
{
m_editing_engine_type = EditingEngineType::Regular;
}
CursorWidth RegularEditingEngine::cursor_width() const
{
return CursorWidth::NARROW;

View file

@ -33,6 +33,8 @@ namespace GUI {
class RegularEditingEngine final : public EditingEngine {
public:
RegularEditingEngine();
virtual CursorWidth cursor_width() const override;
virtual bool on_key(const KeyEvent& event) override;

View file

@ -40,6 +40,7 @@
#include <LibGUI/ScrollBar.h>
#include <LibGUI/SyntaxHighlighter.h>
#include <LibGUI/TextEditor.h>
#include <LibGUI/VimEditingEngine.h>
#include <LibGUI/Window.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/Font.h>
@ -1625,6 +1626,52 @@ void TextEditor::set_editing_engine(OwnPtr<EditingEngine> editing_engine)
update_cursor();
stop_timer();
start_timer(500);
if (on_editing_engine_changed)
on_editing_engine_changed();
if (m_editing_engine->type() == EditingEngineType::Vim) {
VimEditingEngine* vim = dynamic_cast<VimEditingEngine*>(m_editing_engine.ptr());
vim->on_mode_change = [&](VimMode mode) {
switch (mode) {
case Normal:
m_vim_mode_statusbar_message = {};
break;
case Insert:
m_vim_mode_statusbar_message = "-- INSERT --";
break;
case Visual:
m_vim_mode_statusbar_message = "-- VISUAL --";
break;
default:
dbgln("Unhandled vim mode");
m_vim_mode_statusbar_message = {};
}
if (on_vim_statusbar_messages_changed)
on_vim_statusbar_messages_changed();
};
// FIXME: Update this method to take multiple previous keys when that is implemented
vim->on_previous_keys_change = [&](const VimEditingEngine::PreviousKey& event, bool has_previous_key) {
if (has_previous_key) {
StringBuilder sb = StringBuilder(1);
sb.append_code_point(event.code_point);
m_vim_previous_keys_statusbar_message = sb.to_string();
} else {
m_vim_previous_keys_statusbar_message = {};
}
if (on_vim_statusbar_messages_changed)
on_vim_statusbar_messages_changed();
};
} else {
m_vim_mode_statusbar_message = {};
m_vim_previous_keys_statusbar_message = {};
if (on_vim_statusbar_messages_changed)
on_vim_statusbar_messages_changed();
if (on_editing_engine_changed) {
on_editing_engine_changed();
}
}
}
int TextEditor::line_height() const

View file

@ -150,6 +150,8 @@ public:
Function<void()> on_down_pressed;
Function<void()> on_pageup_pressed;
Function<void()> on_pagedown_pressed;
Function<void()> on_vim_statusbar_messages_changed;
Function<void()> on_editing_engine_changed;
Action& undo_action() { return *m_undo_action; }
Action& redo_action() { return *m_redo_action; }
@ -195,6 +197,9 @@ public:
void delete_text_range(TextRange);
String vim_mode_statusbar_message() const { return m_vim_mode_statusbar_message; }
String vim_previous_keys_statusbar_message() const { return m_vim_previous_keys_statusbar_message; }
protected:
explicit TextEditor(Type = Type::MultiLine);
@ -351,6 +356,9 @@ private:
Gfx::IntPoint m_last_mousemove_position;
RefPtr<Gfx::Bitmap> m_icon;
String m_vim_mode_statusbar_message {};
String m_vim_previous_keys_statusbar_message {};
};
}

View file

@ -30,6 +30,11 @@
namespace GUI {
VimEditingEngine::VimEditingEngine()
{
m_editing_engine_type = EditingEngineType::Vim;
}
CursorWidth VimEditingEngine::cursor_width() const
{
return m_vim_mode == VimMode::Insert ? CursorWidth::NARROW : CursorWidth::WIDE;
@ -101,19 +106,19 @@ bool VimEditingEngine::on_key_in_normal_mode(const KeyEvent& event)
delete_to.set_column(delete_to.column() + 1);
m_editor->delete_text_range(TextRange(m_editor->cursor(), delete_to).normalized());
}
m_previous_key = {};
clear_previous_key();
} else if (m_previous_key == KeyCode::Key_G) {
if (event.key() == KeyCode::Key_G) {
move_to_first_line();
} else if (event.key() == KeyCode::Key_E) {
move_to_end_of_previous_word();
}
m_previous_key = {};
clear_previous_key();
} else if (m_previous_key == KeyCode::Key_Y) {
if (event.key() == KeyCode::Key_Y) {
yank(Line);
}
m_previous_key = {};
clear_previous_key();
} else if (m_previous_key == KeyCode::Key_C) {
if (event.key() == KeyCode::Key_C) {
// Needed because the code to replace the deleted line is called after delete_line() so
@ -169,7 +174,7 @@ bool VimEditingEngine::on_key_in_normal_mode(const KeyEvent& event)
m_editor->delete_text_range(TextRange(adjusted_cursor, delete_to).normalized());
switch_to_insert_mode();
}
m_previous_key = {};
clear_previous_key();
} else {
// Handle first any key codes that are to be applied regardless of modifiers.
switch (event.key()) {
@ -240,7 +245,7 @@ bool VimEditingEngine::on_key_in_normal_mode(const KeyEvent& event)
move_to_beginning_of_previous_word();
break;
case (KeyCode::Key_C):
m_previous_key = event.key();
set_previous_key(event);
break;
case (KeyCode::Key_Backspace):
case (KeyCode::Key_H):
@ -248,13 +253,13 @@ bool VimEditingEngine::on_key_in_normal_mode(const KeyEvent& event)
move_one_left(event);
break;
case (KeyCode::Key_D):
m_previous_key = event.key();
set_previous_key(event);
break;
case (KeyCode::Key_E):
move_to_end_of_next_word();
break;
case (KeyCode::Key_G):
m_previous_key = event.key();
set_previous_key(event);
break;
case (KeyCode::Key_Down):
case (KeyCode::Key_J):
@ -293,7 +298,7 @@ bool VimEditingEngine::on_key_in_normal_mode(const KeyEvent& event)
switch_to_visual_mode();
break;
case (KeyCode::Key_Y):
m_previous_key = event.key();
set_previous_key(event);
break;
case (KeyCode::Key_P):
put(event);
@ -316,7 +321,7 @@ bool VimEditingEngine::on_key_in_visual_mode(const KeyEvent& event)
move_to_end_of_previous_word();
update_selection_on_cursor_move();
}
m_previous_key = {};
clear_previous_key();
} else {
// Handle first any key codes that are to be applied regardless of modifiers.
switch (event.key()) {
@ -391,7 +396,7 @@ bool VimEditingEngine::on_key_in_visual_mode(const KeyEvent& event)
update_selection_on_cursor_move();
break;
case (KeyCode::Key_G):
m_previous_key = event.key();
set_previous_key(event);
break;
case (KeyCode::Key_Down):
case (KeyCode::Key_J):
@ -448,26 +453,32 @@ void VimEditingEngine::switch_to_normal_mode()
{
m_vim_mode = VimMode::Normal;
m_editor->reset_cursor_blink();
m_previous_key = {};
clear_previous_key();
clear_visual_mode_data();
if (on_mode_change)
on_mode_change(m_vim_mode);
};
void VimEditingEngine::switch_to_insert_mode()
{
m_vim_mode = VimMode::Insert;
m_editor->reset_cursor_blink();
m_previous_key = {};
clear_previous_key();
clear_visual_mode_data();
if (on_mode_change)
on_mode_change(m_vim_mode);
};
void VimEditingEngine::switch_to_visual_mode()
{
m_vim_mode = VimMode::Visual;
m_editor->reset_cursor_blink();
m_previous_key = {};
clear_previous_key();
m_selection_start_position = m_editor->cursor();
m_editor->selection()->set(m_editor->cursor(), { m_editor->cursor().line(), m_editor->cursor().column() + 1 });
m_editor->did_update_selection();
if (on_mode_change)
on_mode_change(m_vim_mode);
}
void VimEditingEngine::update_selection_on_cursor_move()

View file

@ -30,20 +30,48 @@
namespace GUI {
enum VimMode {
Normal,
Insert,
Visual
};
class VimEditingEngine final : public EditingEngine {
public:
VimEditingEngine();
virtual CursorWidth cursor_width() const override;
virtual bool on_key(const KeyEvent& event) override;
private:
enum VimMode {
Normal,
Insert,
Visual
class PreviousKey {
public:
PreviousKey() = default;
PreviousKey(const KeyEvent& event)
: key(event.key())
, code_point(event.code_point())
{
}
bool operator==(const KeyCode& key) const
{
return this->key == key;
}
bool operator==(const u32& code_point) const
{
return this->code_point == code_point;
}
KeyCode key {};
u32 code_point {};
};
Function<void(VimMode)> on_mode_change;
Function<void(const PreviousKey&, bool has_previous_key)> on_previous_keys_change;
private:
enum YankType {
Line,
Selection
@ -61,7 +89,26 @@ private:
void update_selection_on_cursor_move();
void clear_visual_mode_data();
KeyCode m_previous_key {};
// FIXME Support multiple previous keys, this is a temporary measure.
PreviousKey m_previous_key {};
bool has_previous_key { false };
void set_previous_key(PreviousKey event)
{
m_previous_key = event;
has_previous_key = true;
if (on_previous_keys_change)
on_previous_keys_change(m_previous_key, has_previous_key);
}
void clear_previous_key()
{
m_previous_key = {};
has_previous_key = false;
if (on_previous_keys_change)
on_previous_keys_change(m_previous_key, has_previous_key);
}
void switch_to_normal_mode();
void switch_to_insert_mode();
void switch_to_visual_mode();