From 0fddb3fef8b668582c4b1246e0fb6fb1da0c6af7 Mon Sep 17 00:00:00 2001 From: Enrico Zandomeni Borba Date: Tue, 6 Aug 2024 09:03:38 +0200 Subject: fix mouse scrolling --- src/input_handler.cc | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'src/input_handler.cc') diff --git a/src/input_handler.cc b/src/input_handler.cc index 2703b51a..94f4cb79 100644 --- a/src/input_handler.cc +++ b/src/input_handler.cc @@ -127,7 +127,10 @@ struct MouseHandler Buffer& buffer = context.buffer(); BufferCoord cursor; - constexpr auto modifiers = Key::Modifiers::Control | Key::Modifiers::Alt | Key::Modifiers::Shift | Key::Modifiers::MouseButtonMask; + // bits above these potentially store additional information + constexpr auto mask = (Key::Modifiers) 0x7FF; + constexpr auto modifiers = Key::Modifiers::Control | Key::Modifiers::Alt | Key::Modifiers::Shift | Key::Modifiers::MouseButtonMask | ~mask; + switch ((key.modifiers & ~modifiers).value) { case Key::Modifiers::MousePress: @@ -193,7 +196,7 @@ struct MouseHandler } case Key::Modifiers::Scroll: - scroll_window(context, static_cast(key.key), m_dragging ? OnHiddenCursor::MoveCursor : OnHiddenCursor::PreserveSelections); + scroll_window(context, key.scroll_amount(), m_dragging ? OnHiddenCursor::MoveCursor : OnHiddenCursor::PreserveSelections); return true; default: return false; -- cgit v1.2.3 From 6af7a847c74a05748ba139aed6386c0a6df0220b Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Thu, 8 Aug 2024 12:56:07 +1000 Subject: Delay NormalMode clearing of status line and info box to next idle A common pattern is for info/echo messages to be generated by idle hooks but the clearing of previous info/echo was done immediately on normal mode events. This led to flickering of the info box especially when a hook was repeatidly generating the same info (like moving a cursor in the same word where the hook reacts to the word under the cursor). --- src/input_handler.cc | 49 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 11 deletions(-) (limited to 'src/input_handler.cc') diff --git a/src/input_handler.cc b/src/input_handler.cc index 2703b51a..89613aee 100644 --- a/src/input_handler.cc +++ b/src/input_handler.cc @@ -23,13 +23,6 @@ namespace Kakoune { -static void clear_info_and_echo(Context& context) -{ - context.print_status({}); - if (context.has_client()) - context.client().info_hide(); -} - class InputMode : public RefCountable { public: @@ -101,8 +94,6 @@ void InputMode::paste(StringView content) context().print_status({error.what().str(), context().faces()["Error"] }); context().hooks().run_hook(Hook::RuntimeError, error.what(), context()); } - - clear_info_and_echo(context()); } namespace InputModes @@ -221,6 +212,14 @@ constexpr StringView register_doc = class Normal : public InputMode { + enum class PendingClear + { + None = 0, + Info = 0b01, + StatusLine = 0b10 + }; + friend constexpr bool with_bit_ops(Meta::Type) { return true; } + public: Normal(InputHandler& input_handler, bool single_command = false) : InputMode(input_handler), @@ -228,6 +227,15 @@ public: context().flags() & Context::Flags::Draft ? Timer::Callback{} : [this](Timer&) { RefPtr keep_alive{this}; // hook could trigger pop_mode() + if (context().has_client()) + { + if (m_pending_clear & PendingClear::StatusLine) + context().client().print_status({}); + if (m_pending_clear & PendingClear::Info) + context().client().info_hide(); + } + m_pending_clear = PendingClear::None; + context().hooks().run_hook(Hook::NormalIdle, "", context()); }}, m_fs_check_timer{TimePoint::max(), @@ -275,6 +283,8 @@ public: void on_key(Key key, bool) override { + bool should_clear = false; + kak_assert(m_state != State::PopOnEnabled); ScopedSetBool set_in_on_key{m_in_on_key}; @@ -291,7 +301,7 @@ public: if (m_mouse_handler.handle_key(key, context())) { - clear_info_and_echo(context()); + should_clear = true; if (not transient) m_idle_timer.set_next_date(Clock::now() + get_idle_timeout(context())); @@ -338,7 +348,7 @@ public: m_state = State::PopOnEnabled; }); - clear_info_and_echo(context()); + should_clear = true; // Hack to parse keys sent by terminals using the 8th bit to mark the // meta key. In normal mode, give priority to a potential alt-key than @@ -369,7 +379,12 @@ public: context().hooks().run_hook(Hook::NormalKey, to_string(key), context()); if (enabled() and not transient) // The hook might have changed mode + { + if (should_clear and context().has_client()) + m_pending_clear = (context().client().info_pending() ? PendingClear::None : PendingClear::Info) + | (context().client().status_line_pending() ? PendingClear::None : PendingClear::StatusLine); m_idle_timer.set_next_date(Clock::now() + get_idle_timeout(context())); + } } ModeInfo mode_info() const override @@ -395,6 +410,17 @@ public: return {atoms, m_params}; } + void paste(StringView content) override + { + InputMode::paste(content); + if (not (context().flags() & Context::Flags::Draft)) + { + if (context().has_client()) + m_pending_clear = PendingClear::Info | PendingClear::StatusLine; + m_idle_timer.set_next_date(Clock::now() + get_idle_timeout(context())); + } + } + KeymapMode keymap_mode() const override { return KeymapMode::Normal; } StringView name() const override { return "normal"; } @@ -408,6 +434,7 @@ private: Timer m_idle_timer; Timer m_fs_check_timer; MouseHandler m_mouse_handler; + PendingClear m_pending_clear = PendingClear::None; enum class State { Normal, SingleCommand, PopOnEnabled }; State m_state; -- cgit v1.2.3 From a250b96c18659d34d50e13de8d5126ca7fed6814 Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Mon, 12 Aug 2024 17:10:12 +1000 Subject: Move most info/status clear logic to client This makes it possible to remove the pending clears whenever an info/status line is explicitely added, removing a class of race conditions introduced by the previous implementation. --- src/input_handler.cc | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) (limited to 'src/input_handler.cc') diff --git a/src/input_handler.cc b/src/input_handler.cc index 89613aee..56999d45 100644 --- a/src/input_handler.cc +++ b/src/input_handler.cc @@ -212,14 +212,6 @@ constexpr StringView register_doc = class Normal : public InputMode { - enum class PendingClear - { - None = 0, - Info = 0b01, - StatusLine = 0b10 - }; - friend constexpr bool with_bit_ops(Meta::Type) { return true; } - public: Normal(InputHandler& input_handler, bool single_command = false) : InputMode(input_handler), @@ -228,13 +220,7 @@ public: Timer::Callback{} : [this](Timer&) { RefPtr keep_alive{this}; // hook could trigger pop_mode() if (context().has_client()) - { - if (m_pending_clear & PendingClear::StatusLine) - context().client().print_status({}); - if (m_pending_clear & PendingClear::Info) - context().client().info_hide(); - } - m_pending_clear = PendingClear::None; + context().client().clear_pending(); context().hooks().run_hook(Hook::NormalIdle, "", context()); }}, @@ -381,8 +367,7 @@ public: if (enabled() and not transient) // The hook might have changed mode { if (should_clear and context().has_client()) - m_pending_clear = (context().client().info_pending() ? PendingClear::None : PendingClear::Info) - | (context().client().status_line_pending() ? PendingClear::None : PendingClear::StatusLine); + context().client().schedule_clear(); m_idle_timer.set_next_date(Clock::now() + get_idle_timeout(context())); } } @@ -416,7 +401,7 @@ public: if (not (context().flags() & Context::Flags::Draft)) { if (context().has_client()) - m_pending_clear = PendingClear::Info | PendingClear::StatusLine; + context().client().schedule_clear(); m_idle_timer.set_next_date(Clock::now() + get_idle_timeout(context())); } } @@ -434,7 +419,6 @@ private: Timer m_idle_timer; Timer m_fs_check_timer; MouseHandler m_mouse_handler; - PendingClear m_pending_clear = PendingClear::None; enum class State { Normal, SingleCommand, PopOnEnabled }; State m_state; -- cgit v1.2.3 From 560e3631ec57d34c679e6b0faec1e0efdd18d915 Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Sat, 10 Aug 2024 11:26:26 +1000 Subject: Move debug utils to debug.hh/debug.cc Lots of code includes buffer_utils.hh just for write_to_debug_buffer which pulls many unnecessary dependencies. Reorganise to reduce compile times. --- 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 56999d45..615dbf10 100644 --- a/src/input_handler.cc +++ b/src/input_handler.cc @@ -1,7 +1,7 @@ #include "input_handler.hh" #include "buffer_manager.hh" -#include "buffer_utils.hh" +#include "debug.hh" #include "command_manager.hh" #include "client.hh" #include "event_manager.hh" -- cgit v1.2.3 From 64ed046e5a841520506e1954ff0bd756ea112d94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Arrufat?= Date: Fri, 16 Aug 2024 08:49:19 +0900 Subject: include headers cleanup --- src/input_handler.cc | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'src/input_handler.cc') diff --git a/src/input_handler.cc b/src/input_handler.cc index 8ab88f20..aa43783e 100644 --- a/src/input_handler.cc +++ b/src/input_handler.cc @@ -1,6 +1,6 @@ #include "input_handler.hh" -#include "buffer_manager.hh" +#include "buffer.hh" #include "debug.hh" #include "command_manager.hh" #include "client.hh" @@ -11,9 +11,7 @@ #include "option_types.hh" #include "regex.hh" #include "register_manager.hh" -#include "hash_map.hh" #include "user_interface.hh" -#include "utf8.hh" #include "window.hh" #include "word_db.hh" -- cgit v1.2.3 From 6e5bc9dd6c477a71020dcbe6c7a387673825d941 Mon Sep 17 00:00:00 2001 From: Johannes Altmanninger Date: Wed, 28 Aug 2024 15:47:27 +0200 Subject: Fix use-after-free InsertCompletionHide touches used register Before performing the insertion, InsertCompleter::insert calls try_accept() to accept any selected completion candidate. If there is one, we fire InsertCompletionHide. If that one modifies the register used by , the inserted StringViews will be dangling. Fix this by running try_insert first, and read from the register later. Note that we call try_accept() twice but that's fine. It would probably make more sense to copy the register before calling insert() but I don't think it matters. Closes #5220 --- src/input_handler.cc | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'src/input_handler.cc') diff --git a/src/input_handler.cc b/src/input_handler.cc index aa43783e..85a8fbc1 100644 --- a/src/input_handler.cc +++ b/src/input_handler.cc @@ -15,6 +15,7 @@ #include "window.hh" #include "word_db.hh" +#include #include #include @@ -1310,7 +1311,7 @@ public: auto cp = key.codepoint(); if (not cp or key == Key::Escape) return; - insert(RegisterManager::instance()[*cp].get(context())); + insert([&] { return RegisterManager::instance()[*cp].get(context()); }); }, "enter register name", register_doc.str()); update_completions = false; } @@ -1430,6 +1431,13 @@ private: }, false); } + template + void insert(Func&& lazy_strings) + { + m_completer.try_accept(); + insert(std::forward(lazy_strings)()); + } + void insert(Codepoint key) { String str{key}; -- cgit v1.2.3 From c1ce1d70146dd4b3cda76adc98bfac90da55d18c Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Mon, 2 Sep 2024 20:35:07 +1000 Subject: Explicitely call try_accept on InsertCompleter Calling it as part of the insert method was error prone and often led to slightly surprising behaviour, such as the hiding the menu on . --- src/input_handler.cc | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) (limited to 'src/input_handler.cc') diff --git a/src/input_handler.cc b/src/input_handler.cc index 85a8fbc1..0693bc43 100644 --- a/src/input_handler.cc +++ b/src/input_handler.cc @@ -1303,15 +1303,19 @@ public: selections.sort_and_merge_overlapping(); } else if (auto cp = key.codepoint()) + { + m_completer.try_accept(); insert(*cp); + } else if (key == ctrl('r')) { + m_completer.try_accept(); on_next_key_with_autoinfo(context(), "register", KeymapMode::None, [this](Key key, Context&) { auto cp = key.codepoint(); if (not cp or key == Key::Escape) return; - insert([&] { return RegisterManager::instance()[*cp].get(context()); }); + insert(RegisterManager::instance()[*cp].get(context())); }, "enter register name", register_doc.str()); update_completions = false; } @@ -1359,6 +1363,7 @@ public: } else if (key == ctrl('v')) { + m_completer.try_accept(); on_next_key_with_autoinfo(context(), "raw-insert", KeymapMode::None, [this, transient](Key key, Context&) { if (auto cp = get_raw_codepoint(key)) @@ -1387,6 +1392,7 @@ public: void paste(StringView content) override { + m_completer.try_accept(); insert(ConstArrayView{content}); m_idle_timer.set_next_date(Clock::now() + get_idle_timeout(context())); } @@ -1424,20 +1430,13 @@ private: template void insert(ConstArrayView strings) { - m_completer.try_accept(); + kak_assert(not m_completer.has_candidate_selected()); context().selections().for_each([strings, &buffer=context().buffer()] (size_t index, Selection& sel) { Kakoune::insert(buffer, sel, sel.cursor(), strings[std::min(strings.size()-1, index)]); }, false); } - template - void insert(Func&& lazy_strings) - { - m_completer.try_accept(); - insert(std::forward(lazy_strings)()); - } - void insert(Codepoint key) { String str{key}; -- cgit v1.2.3 From 88fa43988acc2389d92ead46a41ff8c7f0608c1a Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Mon, 16 Sep 2024 07:25:02 +0100 Subject: Do not return beginning of buffer whenever display to buffer coord fails Use an empty Optional to show that resolution failed and just do not do anything in the mouse event handler in that case. --- src/input_handler.cc | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) (limited to 'src/input_handler.cc') diff --git a/src/input_handler.cc b/src/input_handler.cc index 0693bc43..6c6439bb 100644 --- a/src/input_handler.cc +++ b/src/input_handler.cc @@ -116,7 +116,6 @@ struct MouseHandler return false; Buffer& buffer = context.buffer(); - BufferCoord cursor; // bits above these potentially store additional information constexpr auto mask = (Key::Modifiers) 0x7FF; constexpr auto modifiers = Key::Modifiers::Control | Key::Modifiers::Alt | Key::Modifiers::Shift | Key::Modifiers::MouseButtonMask | ~mask; @@ -128,20 +127,31 @@ struct MouseHandler { case Key::MouseButton::Right: { m_dragging.reset(); - cursor = context.window().buffer_coord(key.coord()); + auto cursor = context.window().buffer_coord(key.coord()); + if (not cursor) + { + context.ensure_cursor_visible = false; + return true; + } ScopedSelectionEdition selection_edition{context}; auto& selections = context.selections(); if (key.modifiers & Key::Modifiers::Control) - selections = {{selections.begin()->anchor(), cursor}}; + selections = {{selections.begin()->anchor(), *cursor}}; else - selections.main() = {selections.main().anchor(), cursor}; + selections.main() = {selections.main().anchor(), *cursor}; selections.sort_and_merge_overlapping(); return true; } case Key::MouseButton::Left: { m_dragging.reset(new ScopedSelectionEdition{context}); - m_anchor = context.window().buffer_coord(key.coord()); + auto anchor = context.window().buffer_coord(key.coord()); + if (not anchor) + { + context.ensure_cursor_visible = false; + return true; + } + m_anchor = *anchor; if (not (key.modifiers & Key::Modifiers::Control)) context.selections_write_only() = { buffer, m_anchor}; else @@ -159,28 +169,28 @@ struct MouseHandler } case Key::Modifiers::MouseRelease: { - if (not m_dragging) + auto cursor = context.window().buffer_coord(key.coord()); + if (not m_dragging or not cursor) { 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}; + selections.main() = {buffer.clamp(m_anchor), *cursor}; selections.sort_and_merge_overlapping(); m_dragging.reset(); return true; } case Key::Modifiers::MousePos: { - if (not m_dragging) + auto cursor = context.window().buffer_coord(key.coord()); + if (not m_dragging or not cursor) { 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}; + selections.main() = {buffer.clamp(m_anchor), *cursor}; selections.sort_and_merge_overlapping(); return true; } -- cgit v1.2.3 From 2e1e15106e4e16f8c4de43b87ec901f569a582cf Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Mon, 16 Sep 2024 07:39:21 +0100 Subject: Rename Window::display_position to display_coord --- 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 6c6439bb..3ddbfdfb 100644 --- a/src/input_handler.cc +++ b/src/input_handler.cc @@ -50,7 +50,7 @@ public: virtual std::pair get_cursor_info() const { const auto cursor = context().selections().main().cursor(); - auto coord = context().window().display_position(cursor).value_or(DisplayCoord{}); + auto coord = context().window().display_coord(cursor).value_or(DisplayCoord{}); return {CursorMode::Buffer, coord}; } -- cgit v1.2.3 From 50917b39ca410feac0f8dbf37b6db408aeedc2b8 Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Tue, 22 Oct 2024 21:18:23 +1100 Subject: Remove now unused CompletionFlags Since shell-script-completions now runs the script asynchronously and unconditionally, there is no use for the CompletionFlags::Fast anymore which means we can remove that type altogether. --- src/input_handler.cc | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'src/input_handler.cc') diff --git a/src/input_handler.cc b/src/input_handler.cc index 3ddbfdfb..0f2c8987 100644 --- a/src/input_handler.cc +++ b/src/input_handler.cc @@ -692,7 +692,7 @@ public: Timer::Callback{} : [this](Timer&) { RefPtr keep_alive{this}; // hook or m_callback could trigger pop_mode() if (m_auto_complete and m_refresh_completion_pending) - refresh_completions(CompletionFlags::Fast); + refresh_completions(); if (m_line_changed) { m_callback(m_line_editor.line(), PromptEvent::Change, context()); @@ -829,10 +829,10 @@ public: CandidateList& candidates = m_completions.candidates; if (m_auto_complete and m_refresh_completion_pending) - refresh_completions(CompletionFlags::Fast); + refresh_completions(); if (candidates.empty()) // manual completion, we need to ask our completer for completions { - refresh_completions(CompletionFlags::None); + refresh_completions(); if ((not m_prefix_in_completions and candidates.size() > 1) or candidates.size() > 2) return; @@ -890,7 +890,7 @@ public: }); if (m_explicit_completer) - refresh_completions(CompletionFlags::None); + refresh_completions(); }, "enter completion type", "f: filename\n" "w: buffer word\n"); @@ -901,7 +901,7 @@ public: m_auto_complete = not m_auto_complete; if (m_auto_complete) - refresh_completions(CompletionFlags::Fast); + refresh_completions(); else if (context().has_client()) { clear_completions(); @@ -996,7 +996,7 @@ private: template void use_explicit_completer(Completer&& completer) { - m_explicit_completer = [completer](const Context& context, CompletionFlags flags, StringView content, ByteCount cursor_pos) { + m_explicit_completer = [completer](const Context& context, StringView content, ByteCount cursor_pos) { Optional last_token; CommandParser parser{content.substr(0_byte, cursor_pos)}; while (auto token = parser.read_token(false)) @@ -1012,7 +1012,7 @@ private: }; } - void refresh_completions(CompletionFlags flags) + void refresh_completions() { try { @@ -1022,7 +1022,7 @@ private: return; m_current_completion = -1; const String& line = m_line_editor.line(); - m_completions = completer(context(), flags, line, + m_completions = completer(context(), line, line.byte_count_to(m_line_editor.cursor_pos())); if (not context().has_client()) return; -- cgit v1.2.3