From dbe8528231f61717210f796c93c85b852b760b33 Mon Sep 17 00:00:00 2001 From: Tobias Pisani Date: Fri, 23 Feb 2024 16:53:30 +0100 Subject: Make insert repeat (.) more consistent Insert repeat will now only record non-synthesized keys, and when played back execute mappings as well. Constructing some tests, and with the specific goal of fixing https://github.com/alexherbo2/auto-pairs.kak/issues/38, this appeared to be the best approach. Other options could be evaluating the maps only when recording, but this gave other issues (see tests/normal/repeat-insert/repeat-insert-mapped) At this point, repeat-insert may be essentially just a hardcoded macro, at least I haven't identified the difference. If this really is the case, it may make sense to give it a dedicated register, and implement it as a macro. Fixes #3600 --- src/input_handler.cc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'src/input_handler.cc') diff --git a/src/input_handler.cc b/src/input_handler.cc index 17e72874..9d81f711 100644 --- a/src/input_handler.cc +++ b/src/input_handler.cc @@ -1582,7 +1582,7 @@ void InputHandler::repeat_last_insert() // refill last_insert, this is very inefficient, but necessary at the moment // to properly handle insert completion m_last_insert.keys.push_back(key); - current_mode().handle_key(key, true); + handle_key(key); } kak_assert(dynamic_cast(¤t_mode()) != nullptr); } @@ -1655,11 +1655,12 @@ void InputHandler::handle_key(Key key) auto dec = on_scope_end([this]{ --m_handle_key_level;} ); auto process_key = [&](Key key, bool synthesized) { - if (m_last_insert.recording) - m_last_insert.keys.push_back(key); current_mode().handle_key(key, synthesized); }; + if (m_last_insert.recording and m_handle_key_level <= 1) + m_last_insert.keys.push_back(key); + const auto keymap_mode = current_mode().keymap_mode(); KeymapManager& keymaps = m_context.keymaps(); if (keymaps.is_mapped(key, keymap_mode) and not m_context.keymaps_disabled()) -- cgit v1.2.3 From 6598d7b1b2d4d69f805288384b72a076666ca5ca Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Fri, 22 Mar 2024 22:06:11 +1100 Subject: Fix invalid access when recording keys / handling in insert was always dropping the last key in the last_insert() vector (in order to replace it with the actual completion text inserted), this was not valid for synthetized keys that are not added to that vector in the first place. Take the opportunity to merge insert completion handling code between / and direct menu selection. Fixes #5120 --- src/input_handler.cc | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) (limited to 'src/input_handler.cc') diff --git a/src/input_handler.cc b/src/input_handler.cc index 9d81f711..5800a6a0 100644 --- a/src/input_handler.cc +++ b/src/input_handler.cc @@ -1191,7 +1191,7 @@ public: } } - void on_key(Key key, bool) override + void on_key(Key key, bool synthesized) override { auto& buffer = context().buffer(); @@ -1289,22 +1289,13 @@ public: }, "enter register name", register_doc.str()); update_completions = false; } - else if (key == ctrl('n')) - { - last_insert().keys.pop_back(); - m_completer.select(1, true, last_insert().keys); - update_completions = false; - } - else if (key == ctrl('p')) - { - last_insert().keys.pop_back(); - m_completer.select(-1, true, last_insert().keys); - update_completions = false; - } - else if (key.modifiers == Key::Modifiers::MenuSelect) + else if (key == ctrl('n') or key == ctrl('p') or key.modifiers == Key::Modifiers::MenuSelect) { - last_insert().keys.pop_back(); - m_completer.select(key.key, false, last_insert().keys); + if (not synthesized) + last_insert().keys.pop_back(); + bool relative = key.modifiers != Key::Modifiers::MenuSelect; + int index = relative ? (key == ctrl('n') ? 1 : -1) : key.key; + m_completer.select(index, relative, last_insert().keys); update_completions = false; } else if (key == ctrl('x')) -- cgit v1.2.3 From 6e4bb5fbc5bc01c9143e40e3f4cb5b48efb020ec Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Sat, 23 Mar 2024 16:25:32 +1100 Subject: Refactor last insert recording logic Only record non-synthetized insertions, removing the need to re-record on replay and fixing the last replay getting dropped by macro execution. Fixes #5122 --- src/input_handler.cc | 48 +++++++++++++++++++++--------------------------- 1 file changed, 21 insertions(+), 27 deletions(-) (limited to 'src/input_handler.cc') diff --git a/src/input_handler.cc b/src/input_handler.cc index 5800a6a0..b4bb32a4 100644 --- a/src/input_handler.cc +++ b/src/input_handler.cc @@ -56,7 +56,6 @@ public: } using Insertion = InputHandler::Insertion; - Insertion& last_insert() { return m_input_handler.m_last_insert; } protected: virtual void on_key(Key key, bool synthesized) = 0; @@ -1140,11 +1139,12 @@ private: class Insert : public InputMode { public: - Insert(InputHandler& input_handler, InsertMode mode, int count) + Insert(InputHandler& input_handler, InsertMode mode, int count, Insertion* last_insert) : InputMode(input_handler), m_edition(context()), m_selection_edition(context()), m_completer(context()), + m_last_insert(last_insert), m_restore_cursor(mode == InsertMode::Append), m_auto_complete{context().options()["autocomplete"].get() & AutoComplete::Insert}, m_idle_timer{TimePoint::max(), context().flags() & Context::Flags::Draft ? @@ -1157,11 +1157,14 @@ public: { context().buffer().throw_if_read_only(); - last_insert().recording.set(); - last_insert().mode = mode; - last_insert().keys.clear(); - last_insert().disable_hooks = context().hooks_disabled(); - last_insert().count = count; + if (m_last_insert) + { + m_last_insert->recording.set(); + m_last_insert->mode = mode; + m_last_insert->keys.clear(); + m_last_insert->disable_hooks = context().hooks_disabled(); + m_last_insert->count = count; + } prepare(mode, count); } @@ -1177,7 +1180,8 @@ public: if (not from_push) { - last_insert().recording.unset(); + if (m_last_insert) + m_last_insert->recording.unset(); auto& selections = context().selections(); if (m_restore_cursor) @@ -1291,11 +1295,11 @@ public: } else if (key == ctrl('n') or key == ctrl('p') or key.modifiers == Key::Modifiers::MenuSelect) { - if (not synthesized) - last_insert().keys.pop_back(); + if (m_last_insert and not synthesized) + m_last_insert->keys.pop_back(); bool relative = key.modifiers != Key::Modifiers::MenuSelect; int index = relative ? (key == ctrl('n') ? 1 : -1) : key.key; - m_completer.select(index, relative, last_insert().keys); + m_completer.select(index, relative, m_last_insert ? &m_last_insert->keys : nullptr); update_completions = false; } else if (key == ctrl('x')) @@ -1494,6 +1498,7 @@ private: ScopedEdition m_edition; ScopedSelectionEdition m_selection_edition; InsertCompleter m_completer; + Insertion* m_last_insert; const bool m_restore_cursor; bool m_auto_complete; Timer m_idle_timer; @@ -1550,7 +1555,7 @@ void InputHandler::reset_normal_mode() void InputHandler::insert(InsertMode mode, int count) { - push_mode(new InputModes::Insert(*this, mode, count)); + push_mode(new InputModes::Insert(*this, mode, count, m_handle_key_level <= 1 ? &m_last_insert : nullptr)); } void InputHandler::repeat_last_insert() @@ -1562,19 +1567,12 @@ void InputHandler::repeat_last_insert() m_last_insert.recording) throw runtime_error{"repeating last insert not available in this context"}; - Vector keys; - swap(keys, m_last_insert.keys); ScopedSetBool disable_hooks(context().hooks_disabled(), m_last_insert.disable_hooks); - push_mode(new InputModes::Insert(*this, m_last_insert.mode, m_last_insert.count)); - for (auto& key : keys) - { - // refill last_insert, this is very inefficient, but necessary at the moment - // to properly handle insert completion - m_last_insert.keys.push_back(key); + push_mode(new InputModes::Insert(*this, m_last_insert.mode, m_last_insert.count, nullptr)); + for (auto& key : m_last_insert.keys) handle_key(key); - } kak_assert(dynamic_cast(¤t_mode()) != nullptr); } @@ -1645,10 +1643,6 @@ void InputHandler::handle_key(Key key) ++m_handle_key_level; auto dec = on_scope_end([this]{ --m_handle_key_level;} ); - auto process_key = [&](Key key, bool synthesized) { - current_mode().handle_key(key, synthesized); - }; - if (m_last_insert.recording and m_handle_key_level <= 1) m_last_insert.keys.push_back(key); @@ -1659,10 +1653,10 @@ void InputHandler::handle_key(Key key) ScopedSetBool noninteractive{context().noninteractive()}; for (auto& k : keymaps.get_mapping_keys(key, keymap_mode)) - process_key(k, true); + current_mode().handle_key(k, true); } else - process_key(key, m_handle_key_level > 1); + current_mode().handle_key(key, m_handle_key_level > 1); // do not record the key that made us enter or leave recording mode, // and the ones that are triggered recursively by previous keys. -- cgit v1.2.3 From 390bff3435e571e3a2ed81004aae74e0c5851218 Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Wed, 27 Mar 2024 19:07:09 +1100 Subject: Do not make cursor visible when not dragging Fixes #5130 --- src/input_handler.cc | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src/input_handler.cc') diff --git a/src/input_handler.cc b/src/input_handler.cc index b4bb32a4..585d3498 100644 --- a/src/input_handler.cc +++ b/src/input_handler.cc @@ -161,7 +161,10 @@ struct MouseHandler case Key::Modifiers::MouseRelease: { if (not m_dragging) + { + context.ensure_cursor_visible = false; return true; + } auto& selections = context.selections(); cursor = context.window().buffer_coord(key.coord()); selections.main() = {buffer.clamp(m_anchor), cursor}; @@ -172,7 +175,10 @@ struct MouseHandler case Key::Modifiers::MousePos: { if (not m_dragging) + { + context.ensure_cursor_visible = false; return true; + } cursor = context.window().buffer_coord(key.coord()); auto& selections = context.selections(); selections.main() = {buffer.clamp(m_anchor), cursor}; -- cgit v1.2.3 From 303d87978556b3548ca4bab98f5daba7f804e4b8 Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Sun, 31 Mar 2024 21:38:51 +1100 Subject: Support exposing some env vars as part of the mode information This should implement what #5131 proposed in a different way. Closes #5131 --- src/input_handler.cc | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) (limited to 'src/input_handler.cc') diff --git a/src/input_handler.cc b/src/input_handler.cc index 585d3498..2c085f08 100644 --- a/src/input_handler.cc +++ b/src/input_handler.cc @@ -42,7 +42,7 @@ public: bool enabled() const { return &m_input_handler.current_mode() == this; } Context& context() const { return m_input_handler.context(); } - virtual DisplayLine mode_line() const = 0; + virtual ModeInfo mode_info() const = 0; virtual KeymapMode keymap_mode() const = 0; @@ -370,7 +370,7 @@ public: m_idle_timer.set_next_date(Clock::now() + get_idle_timeout(context())); } - DisplayLine mode_line() const override + ModeInfo mode_info() const override { AtomList atoms; auto num_sel = context().selections().size(); @@ -390,7 +390,7 @@ public: atoms.emplace_back(" reg=", context().faces()["StatusLineInfo"]); atoms.emplace_back(StringView(m_params.reg).str(), context().faces()["StatusLineValue"]); } - return atoms; + return {atoms, {{"count", to_string(m_params.count)}, {"register", StringView(m_params.reg).str()}}}; } KeymapMode keymap_mode() const override { return KeymapMode::Normal; } @@ -951,9 +951,9 @@ public: } } - DisplayLine mode_line() const override + ModeInfo mode_info() const override { - return { "prompt", context().faces()["StatusLineMode"] }; + return {{ "prompt", context().faces()["StatusLineMode"] }, {}}; } KeymapMode keymap_mode() const override { return KeymapMode::Prompt; } @@ -1126,9 +1126,9 @@ public: m_callback(key, context()); } - DisplayLine mode_line() const override + ModeInfo mode_info() const override { - return { "enter key", context().faces()["StatusLineMode"] }; + return {{ "enter key", context().faces()["StatusLineMode"] }, {}}; } KeymapMode keymap_mode() const override { return m_keymap_mode; } @@ -1376,15 +1376,16 @@ public: m_idle_timer.set_next_date(Clock::now() + get_idle_timeout(context())); } - DisplayLine mode_line() const override + ModeInfo mode_info() const override { auto num_sel = context().selections().size(); auto main_index = context().selections().main_index(); - return {AtomList{ { "insert", context().faces()["StatusLineMode"] }, - { " ", context().faces()["StatusLine"] }, - { num_sel == 1 ? format("{} sel", num_sel) - : format("{} sels ({})", num_sel, main_index + 1), - context().faces()["StatusLineInfo"] } }}; + return {{AtomList{ { "insert", context().faces()["StatusLineMode"] }, + { " ", context().faces()["StatusLine"] }, + { num_sel == 1 ? format("{} sel", num_sel) + : format("{} sels ({})", num_sel, main_index + 1), + context().faces()["StatusLineInfo"] } }}, + {}}; } KeymapMode keymap_mode() const override { return KeymapMode::Insert; } @@ -1707,9 +1708,9 @@ void InputHandler::stop_recording() m_recording_level = -1; } -DisplayLine InputHandler::mode_line() const +ModeInfo InputHandler::mode_info() const { - return current_mode().mode_line(); + return current_mode().mode_info(); } std::pair InputHandler::get_cursor_info() const -- cgit v1.2.3 From 917db454d9502d087b43cd1ab455616735e86f69 Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Mon, 1 Apr 2024 11:03:22 +1100 Subject: Change mode_info to contain an optional NormalParams As @topisani pointed out in #5131, it is more user friendly to always provide a %val{register} and %val{count} regardless of the mode. --- src/input_handler.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/input_handler.cc') diff --git a/src/input_handler.cc b/src/input_handler.cc index 2c085f08..e11740c7 100644 --- a/src/input_handler.cc +++ b/src/input_handler.cc @@ -390,7 +390,7 @@ public: atoms.emplace_back(" reg=", context().faces()["StatusLineInfo"]); atoms.emplace_back(StringView(m_params.reg).str(), context().faces()["StatusLineValue"]); } - return {atoms, {{"count", to_string(m_params.count)}, {"register", StringView(m_params.reg).str()}}}; + return {atoms, m_params}; } KeymapMode keymap_mode() const override { return KeymapMode::Normal; } -- cgit v1.2.3 From aad0c7cef84990595cdb649b679063da9e8cb581 Mon Sep 17 00:00:00 2001 From: Johannes Altmanninger Date: Sun, 21 Apr 2024 20:18:28 +0200 Subject: Don't capture local-scoped faces in prompt ASan shows that we resolve a face spec owned by a freed stack variable. ================================================================= ==2263300==ERROR: AddressSanitizer: stack-use-after-return on address 0x7a9316c33918 at pc 0x633ea421d8ea bp 0x7ffca001e980 sp 0x7ffca001e970 READ of size 8 at 0x7a9316c33918 thread T0 ... #6 0x633ea421d8e9 in Kakoune::FaceRegistry::resolve_spec(Kakoune::FaceSpec const&) const src/face_registry.cc:128 ... Address 0x7a9316c33918 is located in stack of thread T0 at offset 2328 in frame #0 0x633ea427a095 in operator() src/commands.cc:2267 This frame has 26 object(s): [32, 36) '' ... [544, 560) 'disable_hooks' (line 2269) ... [928, 2432) 'local_scope' (line 2271) <== Memory access at offset 2328 is inside this variable --- src/input_handler.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/input_handler.cc') diff --git a/src/input_handler.cc b/src/input_handler.cc index e11740c7..99fb684e 100644 --- a/src/input_handler.cc +++ b/src/input_handler.cc @@ -657,7 +657,8 @@ public: : InputMode(input_handler), m_callback(std::move(callback)), m_completer(std::move(completer)), m_prompt(prompt.str()), m_prompt_face(face), m_empty_text{std::move(emptystr)}, - m_line_editor{context().faces()}, m_flags(flags), + // This prompt may outlive local scopes so ignore local faces. + m_line_editor{context().faces(false)}, m_flags(flags), m_was_interactive{not context().noninteractive()}, m_history{RegisterManager::instance()[history_register]}, m_current_history{-1}, -- cgit v1.2.3