From 20a2bca52e0d159cdbab4ff6d38024cd1503a4f5 Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Sun, 27 Aug 2023 08:05:46 +1000 Subject: [PATCH] Do not make cursor visible after mouse scrolling and view commands ensure cursor is visible after user input except if the command implementation opted-out. Hooks and timers should not enforce visible cursor. PageUp/PageDown and `` / `` commands still move the cursor as this seemed a desired behaviour. --- src/client.cc | 3 ++ src/context.hh | 2 + src/highlighter.hh | 1 + src/highlighters.cc | 6 ++- src/input_handler.cc | 43 ++++++++++++---------- src/input_handler.hh | 8 +++- src/normal.cc | 7 ++-- src/window.cc | 17 ++++++--- test/regression/3219-scroll-json-ui/script | 4 +- 9 files changed, 58 insertions(+), 33 deletions(-) diff --git a/src/client.cc b/src/client.cc index 6996ee13..5d6cee62 100644 --- a/src/client.cc +++ b/src/client.cc @@ -109,7 +109,10 @@ bool Client::process_pending_inputs() else if (key == Key::FocusOut) context().hooks().run_hook(Hook::FocusOut, context().name(), context()); else + { + context().ensure_cursor_visible = true; m_input_handler.handle_key(key); + } context().hooks().run_hook(Hook::RawKey, to_string(key), context()); } diff --git a/src/context.hh b/src/context.hh index 69377148..f6f00b80 100644 --- a/src/context.hh +++ b/src/context.hh @@ -144,6 +144,8 @@ public: void repeat_last_select() { if (m_last_select) m_last_select(*this); } Buffer* last_buffer() const; + + bool ensure_cursor_visible = true; private: void begin_edition(); void end_edition(); diff --git a/src/highlighter.hh b/src/highlighter.hh index ad94f44d..925dcaa7 100644 --- a/src/highlighter.hh +++ b/src/highlighter.hh @@ -46,6 +46,7 @@ struct DisplaySetup DisplayCoord cursor_pos; // Offset of line and columns that must remain visible around cursor DisplayCoord scroll_offset; + bool ensure_cursor_visible; }; using HighlighterIdList = ConstArrayView; diff --git a/src/highlighters.cc b/src/highlighters.cc index 6f6d6e30..954f1417 100644 --- a/src/highlighters.cc +++ b/src/highlighters.cc @@ -752,7 +752,8 @@ struct WrapHighlighter : Highlighter win_line += wrap_count + 1; // scroll window to keep cursor visible, and update range as lines gets removed - while (buf_line >= cursor.line and setup.first_line < cursor.line and + while (setup.ensure_cursor_visible and + buf_line >= cursor.line and setup.first_line < cursor.line and setup.cursor_pos.line + setup.scroll_offset.line >= win_height) { auto remove_count = 1 + line_wrap_count(setup.first_line, indent); @@ -1660,7 +1661,8 @@ private: setup.cursor_pos.column += cursor_move; } - if (last.line >= setup.first_line and + if (setup.ensure_cursor_visible and + last.line >= setup.first_line and range.first.line <= setup.first_line + setup.line_count and range.first.line != last.line) { diff --git a/src/input_handler.cc b/src/input_handler.cc index 66d30391..a5c88a21 100644 --- a/src/input_handler.cc +++ b/src/input_handler.cc @@ -180,7 +180,7 @@ struct MouseHandler } case Key::Modifiers::Scroll: - scroll_window(context, static_cast(key.key), (bool)m_dragging); + scroll_window(context, static_cast(key.key), m_dragging ? OnHiddenCursor::MoveCursor : OnHiddenCursor::PreserveSelections); return true; default: return false; @@ -1852,7 +1852,7 @@ void hide_auto_info_ifn(const Context& context, bool hide) context.client().info_hide(); } -void scroll_window(Context& context, LineCount offset, bool mouse_dragging) +void scroll_window(Context& context, LineCount offset, OnHiddenCursor on_hidden_cursor) { Window& window = context.window(); Buffer& buffer = context.buffer(); @@ -1861,6 +1861,9 @@ void scroll_window(Context& context, LineCount offset, bool mouse_dragging) DisplayCoord win_pos = window.position(); DisplayCoord win_dim = window.dimensions(); + if (on_hidden_cursor == OnHiddenCursor::PreserveSelections) + context.ensure_cursor_visible = false; + if ((offset < 0 and win_pos.line == 0) or (offset > 0 and win_pos.line == line_count - 1)) return; @@ -1870,25 +1873,27 @@ void scroll_window(Context& context, LineCount offset, bool mouse_dragging) win_pos.line = clamp(win_pos.line + offset, 0_line, line_count-1); - ScopedSelectionEdition selection_edition{context}; - SelectionList& selections = context.selections(); - Selection& main_selection = selections.main(); - const BufferCoord anchor = main_selection.anchor(); - const BufferCoordAndTarget cursor = main_selection.cursor(); - - auto cursor_off = mouse_dragging ? win_pos.line - window.position().line : 0; - - auto line = clamp(cursor.line + cursor_off, win_pos.line + scrolloff.line, - win_pos.line + win_dim.line - 1 - scrolloff.line); - - const ColumnCount tabstop = context.options()["tabstop"].get(); - auto new_cursor = buffer.offset_coord(cursor, line - cursor.line, tabstop); - BufferCoord new_anchor = (mouse_dragging or new_cursor == cursor) ? anchor : new_cursor; - window.set_position(win_pos); - main_selection = { new_anchor, new_cursor }; + if (on_hidden_cursor != OnHiddenCursor::PreserveSelections) + { + ScopedSelectionEdition selection_edition{context}; + SelectionList& selections = context.selections(); + Selection& main_selection = selections.main(); + const BufferCoord anchor = main_selection.anchor(); + const BufferCoordAndTarget cursor = main_selection.cursor(); - selections.sort_and_merge_overlapping(); + auto cursor_off = win_pos.line - window.position().line; + + auto line = clamp(cursor.line + cursor_off, win_pos.line + scrolloff.line, + win_pos.line + win_dim.line - 1 - scrolloff.line); + + const ColumnCount tabstop = context.options()["tabstop"].get(); + auto new_cursor = buffer.offset_coord(cursor, line - cursor.line, tabstop); + + main_selection = {on_hidden_cursor == OnHiddenCursor::MoveCursor ? anchor : new_cursor, new_cursor}; + + selections.sort_and_merge_overlapping(); + } } } diff --git a/src/input_handler.hh b/src/input_handler.hh index 00d2d53a..afcc24d4 100644 --- a/src/input_handler.hh +++ b/src/input_handler.hh @@ -204,7 +204,13 @@ void on_next_key_with_autoinfo(const Context& context, StringView mode_name, }); } -void scroll_window(Context& context, LineCount offset, bool mouse_dragging = false); +enum class OnHiddenCursor { + PreserveSelections, + MoveCursor, + MoveCursorAndAnchor, +}; + +void scroll_window(Context& context, LineCount offset, OnHiddenCursor on_hidden_cursor); } diff --git a/src/normal.cc b/src/normal.cc index 79a434ac..81caeb2d 100644 --- a/src/normal.cc +++ b/src/normal.cc @@ -368,6 +368,7 @@ void view_commands(Context& context, NormalParams params) const int count = params.count; on_next_key_with_autoinfo(context, "view", KeymapMode::View, [count](Key key, Context& context) { + context.ensure_cursor_visible = false; if (key == Key::Escape) return; @@ -400,10 +401,10 @@ void view_commands(Context& context, NormalParams params) window.scroll(-std::max(1, count)); break; case 'j': - scroll_window(context, std::max(1, count)); + scroll_window(context, std::max(1, count), OnHiddenCursor::PreserveSelections); break; case 'k': - scroll_window(context, -std::max(1, count)); + scroll_window(context, -std::max(1, count), OnHiddenCursor::PreserveSelections); break; case 'l': window.scroll( std::max(1, count)); @@ -1408,7 +1409,7 @@ void scroll(Context& context, NormalParams params) const int count = params.count ? params.count : 1; const LineCount offset = (window.dimensions().line - 2) / (half ? 2 : 1) * count; - scroll_window(context, offset * direction); + scroll_window(context, offset * direction, OnHiddenCursor::MoveCursorAndAnchor); } template diff --git a/src/window.cc b/src/window.cc index 757723d2..b660bbdb 100644 --- a/src/window.cc +++ b/src/window.cc @@ -202,11 +202,14 @@ DisplaySetup Window::compute_display_setup(const Context& context) const const int tabstop = context.options()["tabstop"].get(); const auto& cursor = context.selections().main().cursor(); - // Ensure cursor line is visible - if (cursor.line - offset.line < win_pos.line) - win_pos.line = std::max(0_line, cursor.line - offset.line); - if (cursor.line + offset.line >= win_pos.line + m_dimensions.line) - win_pos.line = std::min(buffer().line_count()-1, cursor.line + offset.line - m_dimensions.line + 1); + bool ensure_cursor_visible = context.ensure_cursor_visible; + if (ensure_cursor_visible) + { + if (cursor.line - offset.line < win_pos.line) + win_pos.line = std::max(0_line, cursor.line - offset.line); + if (cursor.line + offset.line >= win_pos.line + m_dimensions.line) + win_pos.line = std::min(buffer().line_count()-1, cursor.line + offset.line - m_dimensions.line + 1); + } DisplaySetup setup{ win_pos.line, @@ -215,13 +218,15 @@ DisplaySetup Window::compute_display_setup(const Context& context) const 0_col, {cursor.line - win_pos.line, get_column(buffer(), tabstop, cursor) - win_pos.column}, - offset + offset, + ensure_cursor_visible }; for (auto pass : { HighlightPass::Move, HighlightPass::Wrap }) m_builtin_highlighters.compute_display_setup({context, setup, pass, {}}, setup); check_display_setup(setup, *this); // now ensure the cursor column is visible + if (ensure_cursor_visible) { auto underflow = std::max(-setup.first_column, setup.cursor_pos.column - setup.scroll_offset.column); diff --git a/test/regression/3219-scroll-json-ui/script b/test/regression/3219-scroll-json-ui/script index 0cfd5b58..2ba5f829 100644 --- a/test/regression/3219-scroll-json-ui/script +++ b/test/regression/3219-scroll-json-ui/script @@ -6,8 +6,8 @@ ui_out '{ "jsonrpc": "2.0", "method": "draw_status", "params": [[], [{ "face": { ui_out '{ "jsonrpc": "2.0", "method": "set_cursor", "params": ["buffer", { "line": 0, "column": 0 }] }' ui_out '{ "jsonrpc": "2.0", "method": "refresh", "params": [true] }' ui_in '{ "jsonrpc": "2.0", "method": "scroll", "params": [ 2 ] }' -ui_out '{ "jsonrpc": "2.0", "method": "draw", "params": [[[{ "face": { "fg": "black", "bg": "white", "underline": "default", "attributes": [] }, "contents": "0" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "3\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "04\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "05\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "06\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "07\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "08\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "09\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "10\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "11\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "12\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "13\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "14\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "15\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "16\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "17\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "18\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "19\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "20\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "21\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "22\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "23\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "24\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "25\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "26\u000a" }]], { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, { "fg": "blue", "bg": "default", "underline": "default", "attributes": [] }] }' +ui_out '{ "jsonrpc": "2.0", "method": "draw", "params": [[[{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "03\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "04\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "05\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "06\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "07\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "08\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "09\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "10\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "11\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "12\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "13\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "14\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "15\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "16\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "17\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "18\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "19\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "20\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "21\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "22\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "23\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "24\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "25\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "26\u000a" }]], { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, { "fg": "blue", "bg": "default", "underline": "default", "attributes": [] }] }' ui_out '{ "jsonrpc": "2.0", "method": "info_hide", "params": [] }' -ui_out '{ "jsonrpc": "2.0", "method": "draw_status", "params": [[], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "out 3:1 " }, { "face": { "fg": "black", "bg": "yellow", "underline": "default", "attributes": [] }, "contents": "" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " " }, { "face": { "fg": "blue", "bg": "default", "underline": "default", "attributes": [] }, "contents": "1 sel" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " - client0@[kak-tests]" }], { "fg": "cyan", "bg": "default", "underline": "default", "attributes": [] }] }' +ui_out '{ "jsonrpc": "2.0", "method": "draw_status", "params": [[], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "out 1:1 " }, { "face": { "fg": "black", "bg": "yellow", "underline": "default", "attributes": [] }, "contents": "" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " " }, { "face": { "fg": "blue", "bg": "default", "underline": "default", "attributes": [] }, "contents": "1 sel" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " - client0@[kak-tests]" }], { "fg": "cyan", "bg": "default", "underline": "default", "attributes": [] }] }' ui_out '{ "jsonrpc": "2.0", "method": "set_cursor", "params": ["buffer", { "line": 0, "column": 0 }] }' ui_out '{ "jsonrpc": "2.0", "method": "refresh", "params": [false] }'