diff --git a/src/context.hh b/src/context.hh index 69377148..119e4495 100644 --- a/src/context.hh +++ b/src/context.hh @@ -144,6 +144,7 @@ public: void repeat_last_select() { if (m_last_select) m_last_select(*this); } Buffer* last_buffer() const; + private: void begin_edition(); void end_edition(); @@ -215,9 +216,16 @@ struct ScopedEdition ScopedEdition(Context& context) : m_context{context}, m_buffer{context.has_buffer() ? &context.buffer() : nullptr} - { if (m_buffer) m_context.begin_edition(); } + { + if (m_buffer) + m_context.begin_edition(); + } - ~ScopedEdition() { if (m_buffer) m_context.end_edition(); } + ~ScopedEdition() + { + if (m_buffer) + m_context.end_edition(); + } Context& context() const { return m_context; } private: @@ -230,11 +238,19 @@ struct ScopedSelectionEdition ScopedSelectionEdition(Context& context) : m_context{context}, m_buffer{not (m_context.flags() & Context::Flags::Draft) and context.has_buffer() ? &context.buffer() : nullptr} - { if (m_buffer) m_context.m_selection_history.begin_edition(); } + { + if (m_buffer) + m_context.m_selection_history.begin_edition(); + } + ScopedSelectionEdition(ScopedSelectionEdition&& other) : m_context{other.m_context}, m_buffer{other.m_buffer} { other.m_buffer = nullptr; } - ~ScopedSelectionEdition() { if (m_buffer) m_context.m_selection_history.end_edition(); } + ~ScopedSelectionEdition() + { + if (m_buffer) + m_context.m_selection_history.end_edition(); + } private: Context& m_context; SafePtr m_buffer; 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 65f801cb..156b4b2c 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..ef45e149 100644 --- a/src/input_handler.cc +++ b/src/input_handler.cc @@ -1870,25 +1870,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 (mouse_dragging) + { + 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 = { anchor, new_cursor }; + + selections.sort_and_merge_overlapping(); + } } } diff --git a/src/normal.cc b/src/normal.cc index b16b9c37..8b26b1ff 100644 --- a/src/normal.cc +++ b/src/normal.cc @@ -848,7 +848,7 @@ void regex_prompt(Context& context, String prompt, char reg, T func) { selections.update(); context.selections_write_only() = selections; - if (context.has_window()) + if (context.has_window() and event != PromptEvent::Validate) context.window().set_position(position); context.input_handler().set_prompt_face(context.faces()["Prompt"]); diff --git a/src/window.cc b/src/window.cc index 2a0469cf..b5d1db40 100644 --- a/src/window.cc +++ b/src/window.cc @@ -85,37 +85,34 @@ static uint32_t compute_faces_hash(const FaceRegistry& faces) Window::Setup Window::build_setup(const Context& context) const { - Vector selections; - for (auto& sel : context.selections()) - selections.push_back({sel.cursor(), sel.anchor()}); - - return { m_position, m_dimensions, - context.buffer().timestamp(), - compute_faces_hash(context.faces()), - context.selections().main_index(), - std::move(selections) }; + return {m_position, m_dimensions, + context.buffer().timestamp(), + compute_faces_hash(context.faces()), + context.selections().main_index(), + context.selections() | gather>()}; } bool Window::needs_redraw(const Context& context) const { auto& selections = context.selections(); - - if (m_position != m_last_setup.position or + return m_position != m_last_setup.position or m_dimensions != m_last_setup.dimensions or context.buffer().timestamp() != m_last_setup.timestamp or selections.main_index() != m_last_setup.main_selection or selections.size() != m_last_setup.selections.size() or - compute_faces_hash(context.faces()) != m_last_setup.faces_hash) - return true; + compute_faces_hash(context.faces()) != m_last_setup.faces_hash or + not std::equal(selections.begin(), selections.end(), + m_last_setup.selections.begin(), m_last_setup.selections.end()); +} - for (int i = 0; i < selections.size(); ++i) - { - if (selections[i].cursor() != m_last_setup.selections[i].begin or - selections[i].anchor() != m_last_setup.selections[i].end) - return true; - } - - return false; +bool Window::should_make_cursor_visible(const Context& context) const +{ + auto& selections = context.selections(); + return context.buffer().timestamp() != m_last_setup.timestamp or + selections.main_index() != m_last_setup.main_selection or + selections.size() != m_last_setup.selections.size() or + not std::equal(selections.begin(), selections.end(), + m_last_setup.selections.begin(), m_last_setup.selections.end()); } const DisplayBuffer& Window::update_display_buffer(const Context& context) @@ -215,11 +212,15 @@ 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 = should_make_cursor_visible(context); + + 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, @@ -228,13 +229,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/src/window.hh b/src/window.hh index dd7a325b..4a6a5e47 100644 --- a/src/window.hh +++ b/src/window.hh @@ -43,7 +43,7 @@ public: Buffer& buffer() const { return *m_buffer; } bool needs_redraw(const Context& context) const; - void force_redraw() { m_last_setup = Setup{}; } + void force_redraw() { m_last_setup.dimensions = {}; } void set_client(Client* client) { m_client = client; } @@ -60,6 +60,8 @@ private: void run_hook_in_own_context(Hook hook, StringView param, String client_name = ""); + bool should_make_cursor_visible(const Context& context) const; + SafePtr m_buffer; SafePtr m_client; @@ -77,7 +79,7 @@ private: size_t timestamp; size_t faces_hash; size_t main_selection; - Vector selections; + Vector selections; }; Setup build_setup(const Context& context) const; Setup m_last_setup; 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] }' diff --git a/test/regression/4839-scroll-invalid-cursor/cmd b/test/regression/4839-scroll-invalid-cursor/cmd index ec0f73a0..10ba8aba 100644 --- a/test/regression/4839-scroll-invalid-cursor/cmd +++ b/test/regression/4839-scroll-invalid-cursor/cmd @@ -1 +1 @@ -l +l diff --git a/test/regression/4839-scroll-invalid-cursor/script b/test/regression/4839-scroll-invalid-cursor/script index 03227629..31d58187 100644 --- a/test/regression/4839-scroll-invalid-cursor/script +++ b/test/regression/4839-scroll-invalid-cursor/script @@ -1,2 +1,2 @@ -ui_out -ignore 1 -ui_out '{ "jsonrpc": "2.0", "method": "draw", "params": [[[{ "face": { "fg": "black", "bg": "white", "underline": "default", "attributes": [] }, "contents": "😃" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "\u000a" }]], { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, { "fg": "blue", "bg": "default", "underline": "default", "attributes": [] }] }' +ui_out -ignore 7 +ui_out '{ "jsonrpc": "2.0", "method": "draw", "params": [[[{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "😃\u000a" }]], { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, { "fg": "blue", "bg": "default", "underline": "default", "attributes": [] }] }'