From 31cbd34d9724922026a5ae00846ce8105605df5d Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sat, 3 Feb 2018 20:11:31 +0100 Subject: UI: add "compositor" layer to merge grids for TUI use in a correct way Initially we will use this for the popupmenu, floating windows will follow soon NB: writedelay + compositor is weird, we need more flexible redraw introspection. --- src/nvim/CMakeLists.txt | 3 + src/nvim/api/ui.c | 2 + src/nvim/api/ui_events.in.h | 26 +- src/nvim/generators/c_grammar.lua | 2 + src/nvim/generators/gen_api_ui_events.lua | 30 ++- src/nvim/grid_defs.h | 11 +- src/nvim/highlight.c | 3 + src/nvim/main.c | 1 + src/nvim/os_unix.c | 2 +- src/nvim/popupmnu.c | 82 +++++-- src/nvim/screen.c | 44 ++-- src/nvim/ui.c | 83 +++---- src/nvim/ui.h | 9 +- src/nvim/ui_compositor.c | 385 ++++++++++++++++++++++++++++++ src/nvim/ui_compositor.h | 12 + 15 files changed, 583 insertions(+), 112 deletions(-) create mode 100644 src/nvim/ui_compositor.c create mode 100644 src/nvim/ui_compositor.h (limited to 'src') diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 928d473b04..8abc43c2aa 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -23,6 +23,7 @@ set(GENERATOR_DIR ${CMAKE_CURRENT_LIST_DIR}/generators) set(GENERATED_DIR ${PROJECT_BINARY_DIR}/src/nvim/auto) set(API_DISPATCH_GENERATOR ${GENERATOR_DIR}/gen_api_dispatch.lua) set(API_UI_EVENTS_GENERATOR ${GENERATOR_DIR}/gen_api_ui_events.lua) +set(GENERATOR_C_GRAMMAR ${GENERATOR_DIR}/c_grammar.lua) set(API_METADATA ${PROJECT_BINARY_DIR}/api_metadata.mpack) set(FUNCS_DATA ${PROJECT_BINARY_DIR}/funcs_data.mpack) set(MSGPACK_LUA_C_BINDINGS ${GENERATED_DIR}/msgpack_lua_c_bindings.generated.c) @@ -269,6 +270,7 @@ add_custom_command( ${API_HEADERS} ${MSGPACK_RPC_HEADERS} ${API_DISPATCH_GENERATOR} + ${GENERATOR_C_GRAMMAR} ${CMAKE_CURRENT_LIST_DIR}/api/dispatch_deprecated.lua ) @@ -300,6 +302,7 @@ add_custom_command( ${GENERATED_UI_EVENTS_METADATA} DEPENDS ${API_UI_EVENTS_GENERATOR} + ${GENERATOR_C_GRAMMAR} ${CMAKE_CURRENT_LIST_DIR}/api/ui_events.in.h ) diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index bc8a1a941f..9d577db022 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -113,6 +113,8 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, ui->set_title = remote_ui_set_title; ui->set_icon = remote_ui_set_icon; ui->option_set = remote_ui_option_set; + ui->win_scroll_over_start = remote_ui_win_scroll_over_start; + ui->win_scroll_over_reset = remote_ui_win_scroll_over_reset; ui->event = remote_ui_event; ui->inspect = remote_ui_inspect; diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h index 59a7780651..70f8b9d85c 100644 --- a/src/nvim/api/ui_events.in.h +++ b/src/nvim/api/ui_events.in.h @@ -38,6 +38,9 @@ void set_icon(String icon) FUNC_API_SINCE(3); void option_set(String name, Object value) FUNC_API_SINCE(4) FUNC_API_BRIDGE_IMPL; +// Stop event is not exported as such, represented by EOF in the msgpack stream. +void stop(void) + FUNC_API_NOEXPORT; // First revison of the grid protocol, used by default void update_fg(Integer fg) @@ -71,28 +74,39 @@ void hl_attr_define(Integer id, HlAttrs rgb_attrs, HlAttrs cterm_attrs, Array info) FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_BRIDGE_IMPL; void grid_resize(Integer grid, Integer width, Integer height) - FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL; + FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL; void grid_clear(Integer grid) - FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL; + FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL; void grid_cursor_goto(Integer grid, Integer row, Integer col) - FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL; + FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL; void grid_line(Integer grid, Integer row, Integer col_start, Array data) FUNC_API_SINCE(5) FUNC_API_REMOTE_ONLY; void grid_scroll(Integer grid, Integer top, Integer bot, Integer left, Integer right, Integer rows, Integer cols) - FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL; + FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL; void grid_destroy(Integer grid) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; +// For perfomance and simplicity, we use the dense screen representation +// in internal code, such as compositor and TUI. The remote_ui module will +// translate this in to the public grid_line format. +void raw_line(Integer grid, Integer row, Integer startcol, + Integer endcol, Integer clearcol, Integer clearattr, + Boolean wrap, const schar_T *chunk, const sattr_T *attrs) + FUNC_API_NOEXPORT FUNC_API_COMPOSITOR_IMPL; + +void event(char *name, Array args, bool *args_consumed) + FUNC_API_NOEXPORT; + void win_pos(Integer grid, Integer win, Integer startrow, Integer startcol, Integer width, Integer height) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; void win_hide(Integer grid) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; void win_scroll_over_start(void) - FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; + FUNC_API_SINCE(6) FUNC_API_BRIDGE_IMPL FUNC_API_COMPOSITOR_IMPL; void win_scroll_over_reset(void) - FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; + FUNC_API_SINCE(6) FUNC_API_BRIDGE_IMPL FUNC_API_COMPOSITOR_IMPL; void popupmenu_show(Array items, Integer selected, Integer row, Integer col, Integer grid) diff --git a/src/nvim/generators/c_grammar.lua b/src/nvim/generators/c_grammar.lua index d3047e1a9c..40bdc3e30c 100644 --- a/src/nvim/generators/c_grammar.lua +++ b/src/nvim/generators/c_grammar.lua @@ -25,6 +25,7 @@ local c_id = ( local c_void = P('void') local c_param_type = ( ((P('Error') * fill * P('*') * fill) * Cc('error')) + + C((P('const ') ^ -1) * (c_id) * (ws ^ 1) * P('*')) + (C(c_id) * (ws ^ 1)) ) local c_type = (C(c_void) * (ws ^ 1)) + c_param_type @@ -43,6 +44,7 @@ local c_proto = Ct( (fill * Cg((P('FUNC_API_REMOTE_ONLY') * Cc(true)), 'remote_only') ^ -1) * (fill * Cg((P('FUNC_API_REMOTE_IMPL') * Cc(true)), 'remote_impl') ^ -1) * (fill * Cg((P('FUNC_API_BRIDGE_IMPL') * Cc(true)), 'bridge_impl') ^ -1) * + (fill * Cg((P('FUNC_API_COMPOSITOR_IMPL') * Cc(true)), 'compositor_impl') ^ -1) * fill * P(';') ) diff --git a/src/nvim/generators/gen_api_ui_events.lua b/src/nvim/generators/gen_api_ui_events.lua index e76b601d8a..c8ab310b02 100644 --- a/src/nvim/generators/gen_api_ui_events.lua +++ b/src/nvim/generators/gen_api_ui_events.lua @@ -54,7 +54,7 @@ for i = 1, #events do ev = events[i] assert(ev.return_type == 'void') - if ev.since == nil then + if ev.since == nil and not ev.noexport then print("Ui event "..ev.name.." lacks since field.\n") os.exit(1) end @@ -65,7 +65,7 @@ for i = 1, #events do write_signature(proto_output, ev, 'UI *ui') proto_output:write(';\n') - if not ev.remote_impl then + if not ev.remote_impl and not ev.noexport then remote_output:write('static void remote_ui_'..ev.name) write_signature(remote_output, ev, 'UI *ui') remote_output:write('\n{\n') @@ -74,8 +74,7 @@ for i = 1, #events do remote_output:write('}\n\n') end - if not ev.bridge_impl then - + if not ev.bridge_impl and not ev.noexport then send, argv, recv, recv_argv, recv_cleanup = '', '', '', '', '' argc = 1 for j = 1, #ev.parameters do @@ -138,21 +137,36 @@ for i = 1, #events do call_output:write('\n{\n') if ev.remote_only then write_arglist(call_output, ev, false) - call_output:write(' UI_LOG('..ev.name..', 0);\n') + call_output:write(' UI_LOG('..ev.name..');\n') call_output:write(' ui_event("'..ev.name..'", args);\n') + elseif ev.compositor_impl then + call_output:write(' UI_CALL') + write_signature(call_output, ev, '!ui->composed, '..ev.name..', ui', true) + call_output:write(";\n") else call_output:write(' UI_CALL') - write_signature(call_output, ev, ev.name, true) + write_signature(call_output, ev, 'true, '..ev.name..', ui', true) call_output:write(";\n") end call_output:write("}\n\n") end + if ev.compositor_impl then + call_output:write('void ui_composed_call_'..ev.name) + write_signature(call_output, ev, '') + call_output:write('\n{\n') + call_output:write(' UI_CALL') + write_signature(call_output, ev, 'ui->composed, '..ev.name..', ui', true) + call_output:write(";\n") + call_output:write("}\n\n") + end + end proto_output:close() call_output:close() remote_output:close() +bridge_output:close() -- don't expose internal attributes like "impl_name" in public metadata exported_attributes = {'name', 'parameters', @@ -168,7 +182,9 @@ for _,ev in ipairs(events) do p[1] = 'Dictionary' end end - exported_events[#exported_events+1] = ev_exported + if not ev.noexport then + exported_events[#exported_events+1] = ev_exported + end end packed = mpack.pack(exported_events) diff --git a/src/nvim/grid_defs.h b/src/nvim/grid_defs.h index cf9e550f48..d9ccdbbdab 100644 --- a/src/nvim/grid_defs.h +++ b/src/nvim/grid_defs.h @@ -1,6 +1,8 @@ #ifndef NVIM_GRID_DEFS_H #define NVIM_GRID_DEFS_H +#include +#include #include #include "nvim/types.h" @@ -48,8 +50,15 @@ typedef struct { // offsets for the grid relative to the global screen int row_offset; int col_offset; + + // state owned by the compositor. + int comp_row; + int comp_col; + size_t comp_index; + bool comp_disabled; } ScreenGrid; -#define SCREEN_GRID_INIT { 0, NULL, NULL, NULL, NULL, 0, 0, 0, 0 } +#define SCREEN_GRID_INIT { 0, NULL, NULL, NULL, NULL, 0, 0, 0, 0, 0, 0, 0, \ + false } #endif // NVIM_GRID_DEFS_H diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c index 41d60fa3ea..20cdbc7ec9 100644 --- a/src/nvim/highlight.c +++ b/src/nvim/highlight.c @@ -101,6 +101,9 @@ static int get_attr_entry(HlEntry entry) /// When a UI connects, we need to send it the table of highlights used so far. void ui_send_all_hls(UI *ui) { + if (!ui->hl_attr_define) { + return; + } for (size_t i = 1; i < kv_size(attr_entries); i++) { Array inspect = hl_inspect((int)i); ui->hl_attr_define(ui, (Integer)i, kv_A(attr_entries, i).attr, diff --git a/src/nvim/main.c b/src/nvim/main.c index 17f3a894d4..9c8711495c 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -154,6 +154,7 @@ void event_init(void) remote_ui_init(); api_vim_init(); terminal_init(); + ui_init(); } /// @returns false if main_loop could not be closed gracefully diff --git a/src/nvim/os_unix.c b/src/nvim/os_unix.c index 09ba718302..351350d939 100644 --- a/src/nvim/os_unix.c +++ b/src/nvim/os_unix.c @@ -139,7 +139,7 @@ void mch_exit(int r) exiting = true; ui_flush(); - ui_builtin_stop(); + ui_call_stop(); ml_close_all(true); // remove all memfiles if (!event_teardown() && r == 0) { diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c index 056770f2c0..86cb66c6f3 100644 --- a/src/nvim/popupmnu.c +++ b/src/nvim/popupmnu.c @@ -19,6 +19,7 @@ #include "nvim/move.h" #include "nvim/option.h" #include "nvim/screen.h" +#include "nvim/ui_compositor.h" #include "nvim/search.h" #include "nvim/strings.h" #include "nvim/memory.h" @@ -43,6 +44,8 @@ static int pum_col; // left column of pum static bool pum_is_visible = false; static bool pum_external = false; +static ScreenGrid pum_grid = SCREEN_GRID_INIT; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "popupmnu.c.generated.h" #endif @@ -317,7 +320,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed) /// Redraw the popup menu, using "pum_first" and "pum_selected". void pum_redraw(void) { - int row = pum_row; + int row = 0; int col; int attr_norm = win_hl_attr(curwin, HLF_PNI); int attr_select = win_hl_attr(curwin, HLF_PSI); @@ -334,6 +337,37 @@ void pum_redraw(void) int round; int n; + int grid_width = pum_width; + int col_off = 0; + bool extra_space = false; + if (curwin->w_p_rl) { + col_off = pum_width; + if (pum_col < curwin->w_wincol + curwin->w_width - 1) { + grid_width += 1; + extra_space = true; + } + } else if (pum_col > 0) { + grid_width += 1; + col_off = 1; + extra_space = true; + } + if (pum_scrollbar > 0) { + grid_width++; + } + + grid_assign_handle(&pum_grid); + bool moved = ui_comp_put_grid(&pum_grid, pum_row, pum_col-col_off, + pum_height, grid_width); + + if (!pum_grid.chars + || pum_grid.Rows != pum_height || pum_grid.Columns != grid_width) { + grid_alloc(&pum_grid, pum_height, grid_width, !moved); + ui_call_grid_resize(pum_grid.handle, pum_grid.Columns, pum_grid.Rows); + } else if (moved) { + grid_invalidate(&pum_grid); + } + + // Never display more than we have if (pum_first > pum_size - pum_height) { pum_first = pum_size - pum_height; @@ -356,17 +390,17 @@ void pum_redraw(void) screen_puts_line_start(row); // prepend a space if there is room - if (curwin->w_p_rl) { - if (pum_col < curwin->w_wincol + curwin->w_width - 1) { - grid_putchar(&default_grid, ' ', row, pum_col + 1, attr); + if (extra_space) { + if (curwin->w_p_rl) { + grid_putchar(&pum_grid, ' ', row, col_off + 1, attr); + } else { + grid_putchar(&pum_grid, ' ', row, col_off - 1, attr); } - } else if (pum_col > 0) { - grid_putchar(&default_grid, ' ', row, pum_col - 1, attr); } // Display each entry, use two spaces for a Tab. // Do this 3 times: For the main text, kind and extra info - col = pum_col; + col = col_off; totwidth = 0; for (round = 1; round <= 3; ++round) { @@ -423,13 +457,13 @@ void pum_redraw(void) size++; } } - grid_puts_len(&default_grid, rt, (int)STRLEN(rt), row, + grid_puts_len(&pum_grid, rt, (int)STRLEN(rt), row, col - size + 1, attr); xfree(rt_start); xfree(st); col -= width; } else { - grid_puts_len(&default_grid, st, (int)STRLEN(st), row, col, attr); + grid_puts_len(&pum_grid, st, (int)STRLEN(st), row, col, attr); xfree(st); col += width; } @@ -440,11 +474,11 @@ void pum_redraw(void) // Display two spaces for a Tab. if (curwin->w_p_rl) { - grid_puts_len(&default_grid, (char_u *)" ", 2, row, col - 1, + grid_puts_len(&pum_grid, (char_u *)" ", 2, row, col - 1, attr); col -= 2; } else { - grid_puts_len(&default_grid, (char_u *)" ", 2, row, col, attr); + grid_puts_len(&pum_grid, (char_u *)" ", 2, row, col, attr); col += 2; } totwidth += 2; @@ -475,37 +509,37 @@ void pum_redraw(void) } if (curwin->w_p_rl) { - grid_fill(&default_grid, row, row + 1, pum_col - pum_base_width - n + 1, + grid_fill(&pum_grid, row, row + 1, col_off - pum_base_width - n + 1, col + 1, ' ', ' ', attr); - col = pum_col - pum_base_width - n + 1; + col = col_off - pum_base_width - n + 1; } else { - grid_fill(&default_grid, row, row + 1, col, - pum_col + pum_base_width + n, ' ', ' ', attr); - col = pum_col + pum_base_width + n; + grid_fill(&pum_grid, row, row + 1, col, + col_off + pum_base_width + n, ' ', ' ', attr); + col = pum_base_width + n; } totwidth = pum_base_width + n; } if (curwin->w_p_rl) { - grid_fill(&default_grid, row, row + 1, pum_col - pum_width + 1, col + 1, + grid_fill(&pum_grid, row, row + 1, col_off - pum_width + 1, col + 1, ' ', ' ', attr); } else { - grid_fill(&default_grid, row, row + 1, col, pum_col + pum_width, ' ', ' ', + grid_fill(&pum_grid, row, row + 1, col, col_off + pum_width, ' ', ' ', attr); } if (pum_scrollbar > 0) { if (curwin->w_p_rl) { - grid_putchar(&default_grid, ' ', row, pum_col - pum_width, + grid_putchar(&pum_grid, ' ', row, col_off - pum_width, i >= thumb_pos && i < thumb_pos + thumb_heigth ? attr_thumb : attr_scroll); } else { - grid_putchar(&default_grid, ' ', row, pum_col + pum_width, + grid_putchar(&pum_grid, ' ', row, col_off + pum_width, i >= thumb_pos && i < thumb_pos + thumb_heigth ? attr_thumb : attr_scroll); } } - grid_puts_line_flush(&default_grid, false); + grid_puts_line_flush(&pum_grid, false); row++; } } @@ -733,9 +767,9 @@ void pum_undisplay(void) if (pum_external) { ui_call_popupmenu_hide(); } else { - redraw_all_later(SOME_VALID); - redraw_tabline = true; - status_redraw_all(); + ui_comp_remove_grid(&pum_grid); + // TODO(bfredl): consider the possibility of keeping float grids allocated. + grid_free(&pum_grid); } } diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 2038fb4d2c..309a8d5439 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -110,6 +110,7 @@ #include "nvim/syntax.h" #include "nvim/terminal.h" #include "nvim/ui.h" +#include "nvim/ui_compositor.h" #include "nvim/undo.h" #include "nvim/version.h" #include "nvim/window.h" @@ -153,6 +154,8 @@ static bool highlights_invalid = false; static bool conceal_cursor_used = false; +static bool floats_invalid = false; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "screen.c.generated.h" #endif @@ -453,17 +456,20 @@ void update_screen(int type) /* redraw status line after the window to minimize cursor movement */ if (wp->w_redr_status) { - win_redr_status(wp, true); // any popup menu will be redrawn below + win_redr_status(wp); } } - send_grid_resize = false; - highlights_invalid = false; + end_search_hl(); // May need to redraw the popup menu. - if (pum_drawn()) { + if (pum_drawn() && floats_invalid) { pum_redraw(); } + send_grid_resize = false; + highlights_invalid = false; + floats_invalid = false; + /* Reset b_mod_set flags. Going through all windows is probably faster * than going through all buffers (there could be many buffers). */ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { @@ -4287,7 +4293,7 @@ win_line ( /// screen positions. static void screen_adjust_grid(ScreenGrid **grid, int *row_off, int *col_off) { - if (!ui_is_external(kUIMultigrid) && *grid != &default_grid) { + if (!(*grid)->chars && *grid != &default_grid) { *row_off += (*grid)->row_offset; *col_off += (*grid)->col_offset; *grid = &default_grid; @@ -4537,7 +4543,7 @@ void redraw_statuslines(void) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_redr_status) { - win_redr_status(wp, false); + win_redr_status(wp); } } if (redraw_tabline) @@ -4808,7 +4814,7 @@ win_redr_status_matches ( /// If inversion is possible we use it. Else '=' characters are used. /// If "ignore_pum" is true, also redraw statusline when the popup menu is /// displayed. -static void win_redr_status(win_T *wp, int ignore_pum) +static void win_redr_status(win_T *wp) { int row; char_u *p; @@ -4831,7 +4837,7 @@ static void win_redr_status(win_T *wp, int ignore_pum) if (wp->w_status_height == 0) { // no status line, can only be last window redraw_cmdline = true; - } else if (!redrawing() || (!ignore_pum && pum_drawn())) { + } else if (!redrawing()) { // Don't redraw right now, do it later. Don't update status line when // popup menu is visible and may be drawn over it wp->w_redr_status = true; @@ -5454,10 +5460,10 @@ void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, grid->chars[off + 1][0] = 0; grid->attrs[off + 1] = attr; } - if (put_dirty_first == -1) { + if (put_dirty_first == -1 || col < put_dirty_first) { put_dirty_first = col; } - put_dirty_last = col+mbyte_cells; + put_dirty_last = MAX(put_dirty_last, col+mbyte_cells); } off += mbyte_cells; @@ -5864,7 +5870,7 @@ void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, if (dirty_last > dirty_first) { // TODO(bfredl): support a cleared suffix even with a batched line? if (put_dirty_row == row) { - if (put_dirty_first == -1) { + if (put_dirty_first == -1 || dirty_first < put_dirty_first) { put_dirty_first = dirty_first; } put_dirty_last = MAX(put_dirty_last, dirty_last); @@ -6030,6 +6036,10 @@ retry: */ ++RedrawingDisabled; + // win_new_shellsize will recompute floats posititon, but tell the + // compositor to now redraw them yet + ui_comp_invalidate_screen(); + win_new_shellsize(); /* fit the windows in the new sized shell */ comp_col(); /* recompute columns for shown command and ruler */ @@ -6188,6 +6198,7 @@ static void screenclear2(void) default_grid.line_wraps[i] = false; } + floats_invalid = true; ui_call_grid_clear(1); // clear the display clear_cmdline = false; mode_displayed = false; @@ -6218,7 +6229,7 @@ static void grid_clear_line(ScreenGrid *grid, unsigned off, int width, (void)memset(grid->attrs + off, fill, (size_t)width * sizeof(sattr_T)); } -static void grid_invalidate(ScreenGrid *grid) +void grid_invalidate(ScreenGrid *grid) { (void)memset(grid->attrs, -1, grid->Rows * grid->Columns * sizeof(sattr_T)); } @@ -6916,11 +6927,6 @@ void showruler(int always) { if (!always && !redrawing()) return; - if (pum_drawn()) { - // Don't redraw right now, do it later. - curwin->w_redr_status = true; - return; - } if ((*p_stl != NUL || *curwin->w_p_stl != NUL) && curwin->w_status_height) { redraw_custom_statusline(curwin); } else { @@ -6955,10 +6961,6 @@ static void win_redr_ruler(win_T *wp, int always) if (wp == lastwin && lastwin->w_status_height == 0) if (edit_submode != NULL) return; - // Don't draw the ruler when the popup menu is visible, it may overlap. - if (pum_drawn()) { - return; - } if (*p_ruf) { int save_called_emsg = called_emsg; diff --git a/src/nvim/ui.c b/src/nvim/ui.c index b65f3be746..3dc6ccaf52 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -33,6 +33,7 @@ #include "nvim/popupmnu.h" #include "nvim/screen.h" #include "nvim/highlight.h" +#include "nvim/ui_compositor.h" #include "nvim/window.h" #include "nvim/cursor_shape.h" #ifdef FEAT_TUI @@ -60,11 +61,11 @@ static bool pending_mode_update = false; static handle_T cursor_grid_handle = DEFAULT_GRID_HANDLE; #if MIN_LOG_LEVEL > DEBUG_LOG_LEVEL -# define UI_LOG(funname, ...) +# define UI_LOG(funname) #else static size_t uilog_seen = 0; static char uilog_last_event[1024] = { 0 }; -# define UI_LOG(funname, ...) \ +# define UI_LOG(funname) \ do { \ if (strequal(uilog_last_event, STR(funname))) { \ uilog_seen++; \ @@ -80,42 +81,34 @@ static char uilog_last_event[1024] = { 0 }; } while (0) #endif -// UI_CALL invokes a function on all registered UI instances. The functions can -// have 0-10 arguments (configurable by SELECT_NTH). -// -// See http://stackoverflow.com/a/11172679 for how it works. -#ifdef _MSC_VER -# define UI_CALL(funname, ...) \ - do { \ - UI_LOG(funname, 0); \ - for (size_t i = 0; i < ui_count; i++) { \ - UI *ui = uis[i]; \ - UI_CALL_MORE(funname, __VA_ARGS__); \ - } \ - } while (0) -#else -# define UI_CALL(...) \ - do { \ - UI_LOG(__VA_ARGS__, 0); \ - for (size_t i = 0; i < ui_count; i++) { \ - UI *ui = uis[i]; \ - UI_CALL_HELPER(CNT(__VA_ARGS__), __VA_ARGS__); \ +// UI_CALL invokes a function on all registered UI instances. +// This is called by code generated by generators/gen_api_ui_events.lua +// C code should use ui_call_{funname} instead. +# define UI_CALL(cond, funname, ...) \ + do { \ + bool any_call = false; \ + for (size_t i = 0; i < ui_count; i++) { \ + UI *ui = uis[i]; \ + if (ui->funname && (cond)) { \ + ui->funname(__VA_ARGS__); \ + any_call = true; \ } \ - } while (0) -#endif -#define CNT(...) SELECT_NTH(__VA_ARGS__, MORE, MORE, MORE, MORE, MORE, \ - MORE, MORE, MORE, MORE, ZERO, ignore) -#define SELECT_NTH(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, ...) a11 -#define UI_CALL_HELPER(c, ...) UI_CALL_HELPER2(c, __VA_ARGS__) -// Resolves to UI_CALL_MORE or UI_CALL_ZERO. -#define UI_CALL_HELPER2(c, ...) UI_CALL_##c(__VA_ARGS__) -#define UI_CALL_MORE(method, ...) if (ui->method) ui->method(ui, __VA_ARGS__) -#define UI_CALL_ZERO(method) if (ui->method) ui->method(ui) + } \ + if (any_call) { \ + UI_LOG(funname); \ + } \ + } while (0) #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ui_events_call.generated.h" #endif +void ui_init(void) +{ + default_grid.handle = 1; + ui_comp_init(); +} + void ui_builtin_start(void) { #ifdef FEAT_TUI @@ -135,17 +128,12 @@ void ui_builtin_start(void) #endif } -void ui_builtin_stop(void) -{ - UI_CALL(stop); -} - bool ui_rgb_attached(void) { if (!headless_mode && p_tgc) { return true; } - for (size_t i = 0; i < ui_count; i++) { + for (size_t i = 1; i < ui_count; i++) { if (uis[i]->rgb) { return true; } @@ -155,13 +143,13 @@ bool ui_rgb_attached(void) bool ui_active(void) { - return ui_count != 0; + return ui_count > 1; } void ui_event(char *name, Array args) { bool args_consumed = false; - UI_CALL(event, name, args, &args_consumed); + ui_call_event(name, args, &args_consumed); if (!args_consumed) { api_free_array(args); } @@ -257,6 +245,9 @@ void ui_attach_impl(UI *ui) if (ui_count == MAX_UI_COUNT) { abort(); } + if (!ui->ui_ext[kUIMultigrid]) { + ui_comp_attach(ui); + } uis[ui_count++] = ui; ui_refresh_options(); @@ -303,6 +294,10 @@ void ui_detach_impl(UI *ui) && !exiting) { ui_schedule_refresh(); } + + if (!ui->ui_ext[kUIMultigrid]) { + ui_comp_detach(ui); + } } void ui_set_ext_option(UI *ui, UIExtension ext, bool active) @@ -322,9 +317,9 @@ void ui_line(ScreenGrid *grid, int row, int startcol, int endcol, int clearcol, { size_t off = grid->line_offset[row] + (size_t)startcol; - UI_CALL(raw_line, grid->handle, row, startcol, endcol, clearcol, clearattr, - wrap, (const schar_T *)grid->chars + off, - (const sattr_T *)grid->attrs + off); + ui_call_raw_line(grid->handle, row, startcol, endcol, + clearcol, clearattr, wrap, (const schar_T *)grid->chars + off, + (const sattr_T *)grid->attrs + off); if (p_wd) { // 'writedelay': flush & delay each time. int old_row = cursor_row, old_col = cursor_col; @@ -421,7 +416,7 @@ bool ui_is_external(UIExtension widget) Array ui_array(void) { Array all_uis = ARRAY_DICT_INIT; - for (size_t i = 0; i < ui_count; i++) { + for (size_t i = 1; i < ui_count; i++) { UI *ui = uis[i]; Dictionary info = ARRAY_DICT_INIT; PUT(info, "width", INTEGER_OBJ(ui->width)); diff --git a/src/nvim/ui.h b/src/nvim/ui.h index 16237214cb..8aeb632f6d 100644 --- a/src/nvim/ui.h +++ b/src/nvim/ui.h @@ -36,6 +36,7 @@ typedef struct ui_t UI; struct ui_t { bool rgb; + bool composed; bool ui_ext[kUIExtCount]; ///< Externalized widgets int width, height; void *data; @@ -44,14 +45,6 @@ struct ui_t { # include "ui_events.generated.h" #endif - // For perfomance and simplicity, we use the dense screen representation - // in the bridge and the TUI. The remote_ui module will translate this - // in to the public grid_line format. - void (*raw_line)(UI *ui, Integer grid, Integer row, Integer startcol, - Integer endcol, Integer clearcol, Integer clearattr, - Boolean wrap, const schar_T *chunk, const sattr_T *attrs); - void (*event)(UI *ui, char *name, Array args, bool *args_consumed); - void (*stop)(UI *ui); void (*inspect)(UI *ui, Dictionary *info); }; diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c new file mode 100644 index 0000000000..2ca0d1a6eb --- /dev/null +++ b/src/nvim/ui_compositor.c @@ -0,0 +1,385 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +// Compositor: merge floating grids with the main grid for display in +// TUI and non-multigrid UIs. + +#include +#include +#include +#include + +#include "nvim/lib/kvec.h" +#include "nvim/log.h" +#include "nvim/main.h" +#include "nvim/ascii.h" +#include "nvim/vim.h" +#include "nvim/ui.h" +#include "nvim/memory.h" +#include "nvim/ui_compositor.h" +#include "nvim/ugrid.h" +#include "nvim/screen.h" +#include "nvim/syntax.h" +#include "nvim/api/private/helpers.h" +#include "nvim/os/os.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "ui_compositor.c.generated.h" +#endif + +static UI *compositor = NULL; +static int composed_uis = 0; +kvec_t(ScreenGrid *) layers = KV_INITIAL_VALUE; + +static size_t bufsize = 0; +static schar_T *linebuf; +static sattr_T *attrbuf; + +#ifndef NDEBUG +static int chk_width = 0, chk_height = 0; +#endif + +static ScreenGrid *curgrid; + +static bool valid_screen = true; +static bool msg_scroll_mode = false; +static int msg_first_invalid = 0; + +void ui_comp_init(void) +{ + if (compositor != NULL) { + return; + } + compositor = xcalloc(1, sizeof(UI)); + + compositor->rgb = true; + compositor->grid_resize = ui_comp_grid_resize; + compositor->grid_clear = ui_comp_grid_clear; + compositor->grid_scroll = ui_comp_grid_scroll; + compositor->grid_cursor_goto = ui_comp_grid_cursor_goto; + compositor->raw_line = ui_comp_raw_line; + compositor->win_scroll_over_start = ui_comp_win_scroll_over_start; + compositor->win_scroll_over_reset = ui_comp_win_scroll_over_reset; + + // Be unopinionated: will be attached together with a "real" ui anyway + compositor->width = INT_MAX; + compositor->height = INT_MAX; + for (UIExtension i = 0; (int)i < kUIExtCount; i++) { + compositor->ui_ext[i] = true; + } + + // TODO(bfredl): this will be more complicated if we implement + // hlstate per UI (i e reduce hl ids for non-hlstate UIs) + compositor->ui_ext[kUIHlState] = false; + + kv_push(layers, &default_grid); + curgrid = &default_grid; + + ui_attach_impl(compositor); +} + + +void ui_comp_attach(UI *ui) +{ + composed_uis++; + ui->composed = true; +} + +void ui_comp_detach(UI *ui) +{ + composed_uis--; + if (composed_uis == 0) { + xfree(linebuf); + xfree(attrbuf); + linebuf = NULL; + attrbuf = NULL; + bufsize = 0; + } + ui->composed = false; +} + +bool ui_comp_should_draw(void) +{ + return composed_uis != 0 && valid_screen; +} + +/// TODO(bfredl): later on the compositor should just use win_float_pos events, +/// though that will require slight event order adjustment: emit the win_pos +/// events in the beginning of update_screen(0), rather than in ui_flush() +bool ui_comp_put_grid(ScreenGrid *grid, int row, int col, int height, int width) +{ + if (grid->comp_index != 0) { + bool moved = (row != grid->comp_row) || (col != grid->comp_col); + if (ui_comp_should_draw()) { + // Redraw the area covered by the old position, and is not covered + // by the new position. Disable the grid so that compose_area() will not + // use it. + grid->comp_disabled = true; + compose_area(grid->comp_row, row, + grid->comp_col, grid->comp_col + grid->Columns); + if (grid->comp_col < col) { + compose_area(MAX(row, grid->comp_row), + MIN(row+height, grid->comp_row+grid->Rows), + grid->comp_col, col); + } + if (col+width < grid->comp_col+grid->Columns) { + compose_area(MAX(row, grid->comp_row), + MIN(row+height, grid->comp_row+grid->Rows), + col+width, grid->comp_col+grid->Columns); + } + compose_area(row+height, grid->comp_row+grid->Rows, + grid->comp_col, grid->comp_col + grid->Columns); + grid->comp_disabled = false; + } + grid->comp_row = row; + grid->comp_col = col; + return moved; + } +#ifndef NDEBUG + for (size_t i = 0; i < kv_size(layers); i++) { + if (kv_A(layers, i) == grid) { + assert(false); + } + } +#endif + // not found: new grid + kv_push(layers, grid); + grid->comp_row = row; + grid->comp_col = col; + grid->comp_index = kv_size(layers)-1; + return true; +} + +void ui_comp_remove_grid(ScreenGrid *grid) +{ + assert(grid != &default_grid); + if (grid->comp_index == 0) { + // grid wasn't present + return; + } + + if (curgrid == grid) { + curgrid = &default_grid; + } + + for (size_t i = grid->comp_index; i < kv_size(layers)-1; i++) { + kv_A(layers, i) = kv_A(layers, i+1); + kv_A(layers, i)->comp_index = i; + } + (void)kv_pop(layers); + grid->comp_index = 0; + + if (ui_comp_should_draw()) { + // inefficent: only draw up to grid->comp_index + compose_area(grid->comp_row, grid->comp_row+grid->Rows, + grid->comp_col, grid->comp_col+grid->Columns); + } +} + +bool ui_comp_set_grid(handle_T handle) +{ + if (curgrid->handle == handle) { + return true; + } + ScreenGrid *grid = NULL; + for (size_t i = 0; i < kv_size(layers); i++) { + if (kv_A(layers, i)->handle == handle) { + grid = kv_A(layers, i); + break; + } + } + if (grid != NULL) { + curgrid = grid; + return true; + } + return false; +} + +static void ui_comp_grid_cursor_goto(UI *ui, Integer grid_handle, + Integer r, Integer c) +{ + if (!ui_comp_should_draw() || !ui_comp_set_grid((int)grid_handle)) { + return; + } + int cursor_row = curgrid->comp_row+(int)r; + int cursor_col = curgrid->comp_col+(int)c; + + if (cursor_col >= default_grid.Columns || cursor_row >= default_grid.Rows) { + // TODO(bfredl): this happens with 'writedelay', refactor? + // abort(); + return; + } + ui_composed_call_grid_cursor_goto(1, cursor_row, cursor_col); +} + + +/// Baseline implementation. This is always correct, but we can sometimes +/// do something more efficient (where efficiency means smaller deltas to +/// the downstream UI.) +static void compose_line(Integer row, Integer startcol, Integer endcol, + bool wrap) +{ + int col = (int)startcol; + ScreenGrid *grid = NULL; + + while (col < endcol) { + int until = 0; + for (size_t i = 0; i < kv_size(layers); i++) { + ScreenGrid *g = kv_A(layers, i); + if (g->comp_row > row || row >= g->comp_row + g->Rows + || g->comp_disabled) { + continue; + } + if (g->comp_col <= col && col < g->comp_col+g->Columns) { + grid = g; + until = g->comp_col+g->Columns; + } else if (g->comp_col > col) { + until = MIN(until, g->comp_col); + } + } + until = MIN(until, (int)endcol); + + assert(grid != NULL); + assert(until > col); + assert(until <= default_grid.Columns); + size_t n = (size_t)(until-col); + size_t off = grid->line_offset[row-grid->comp_row] + + (size_t)(col-grid->comp_col); + memcpy(linebuf+(col-startcol), grid->chars+off, n * sizeof(*linebuf)); + memcpy(attrbuf+(col-startcol), grid->attrs+off, n * sizeof(*attrbuf)); + col = until; + } + assert(endcol <= chk_width); + assert(row < chk_height); + // TODO(bfredl): too conservative, need check + // grid->line_wraps if grid->Width == Width + wrap = wrap && grid && grid->handle == 1; + ui_composed_call_raw_line(1, row, startcol, endcol, endcol, 0, wrap, + (const schar_T *)linebuf, (const sattr_T *)attrbuf); +} + +static void compose_area(Integer startrow, Integer endrow, + Integer startcol, Integer endcol) +{ + endrow = MIN(endrow, default_grid.Rows); + endcol = MIN(endcol, default_grid.Columns); + for (int r = (int)startrow; r < endrow; r++) { + compose_line(r, startcol, endcol, false); + } +} + + +static void draw_line(ScreenGrid *grid, Integer row, Integer startcol, + Integer endcol, Integer clearcol, Integer clearattr, + bool wrap, const schar_T *chunk, const sattr_T *attrs) +{ + row += grid->comp_row; + startcol += grid->comp_col; + endcol += grid->comp_col; + clearcol += grid->comp_col; + wrap = wrap && grid->handle == 1; + assert(clearcol <= default_grid.Columns); + if (kv_size(layers) > grid->comp_index+1) { + compose_line(row, startcol, clearcol, wrap); + } else { + ui_composed_call_raw_line(1, row, startcol, endcol, clearcol, clearattr, + wrap, chunk, attrs); + } +} + +static void ui_comp_raw_line(UI *ui, Integer grid, Integer row, + Integer startcol, Integer endcol, + Integer clearcol, Integer clearattr, bool wrap, + const schar_T *chunk, const sattr_T *attrs) +{ + if (!ui_comp_should_draw() || !ui_comp_set_grid((int)grid)) { + return; + } + draw_line(curgrid, row, startcol, endcol, clearcol, clearattr, wrap, chunk, + attrs); +} + +/// The screen is invalid and will soon be cleared +/// +/// Don't redraw floats until screen is cleared +void ui_comp_invalidate_screen(void) +{ + valid_screen = false; +} + +static void ui_comp_grid_clear(UI *ui, Integer grid) +{ + // By design, only first grid uses clearing. + assert(grid == 1); + ui_composed_call_grid_clear(1); + valid_screen = true; +} + +// TODO(bfredl): These events are somewhat of a hack. multiline messages +// should later on be a separate grid, then this would just be ordinary +// ui_comp_put_grid and ui_comp_remove_grid calls. +static void ui_comp_win_scroll_over_start(UI *ui) +{ + msg_scroll_mode = true; + msg_first_invalid = ui->height; +} + +static void ui_comp_win_scroll_over_reset(UI *ui) +{ + msg_scroll_mode = false; + for (size_t i = 1; i < kv_size(layers); i++) { + ScreenGrid *grid = kv_A(layers, i); + if (grid->comp_row+grid->Rows > msg_first_invalid) { + compose_area(msg_first_invalid, grid->comp_row+grid->Rows, + grid->comp_col, grid->comp_col+grid->Columns); + } + } +} + +static void ui_comp_grid_scroll(UI *ui, Integer grid, Integer top, + Integer bot, Integer left, Integer right, + Integer rows, Integer cols) +{ + if (!ui_comp_should_draw() || !ui_comp_set_grid((int)grid)) { + return; + } + top += curgrid->comp_row; + bot += curgrid->comp_row; + left += curgrid->comp_col; + right += curgrid->comp_col; + if (!msg_scroll_mode && kv_size(layers) > curgrid->comp_index+1) { + // TODO(bfredl): + // 1. check if rectangles actually overlap + // 2. calulate subareas that can scroll. + if (rows > 0) { + bot -= rows; + } else { + top += (-rows); + } + compose_area(top, bot, left, right); + } else { + msg_first_invalid = MIN(msg_first_invalid, (int)top); + ui_composed_call_grid_scroll(1, top, bot, left, right, rows, cols); + } +} + +static void ui_comp_grid_resize(UI *ui, Integer grid, + Integer width, Integer height) +{ + if (grid == 1) { + ui_composed_call_grid_resize(1, width, height); +#ifndef NDEBUG + chk_width = (int)width; + chk_height = (int)height; +#endif + size_t new_bufsize = (size_t)width; + if (bufsize != new_bufsize) { + xfree(linebuf); + xfree(attrbuf); + linebuf = xmalloc(new_bufsize * sizeof(*linebuf)); + attrbuf = xmalloc(new_bufsize * sizeof(*attrbuf)); + bufsize = new_bufsize; + } + } +} + diff --git a/src/nvim/ui_compositor.h b/src/nvim/ui_compositor.h new file mode 100644 index 0000000000..b3780db532 --- /dev/null +++ b/src/nvim/ui_compositor.h @@ -0,0 +1,12 @@ +// Bridge for communication between a UI thread and nvim core. +// Used by the built-in TUI and libnvim-based UIs. +#ifndef NVIM_UI_COMPOSITOR_H +#define NVIM_UI_COMPOSITOR_H + +#include "nvim/ui.h" +#include "nvim/event/defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "ui_compositor.h.generated.h" +#endif +#endif // NVIM_UI_COMPOSITOR_H -- cgit From 2c01e79dc47ff83e0af118fb3fee50fa2ff7fcf2 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sun, 20 Jan 2019 12:14:35 +0100 Subject: Reduce pum redraws from edit.c by delaying undisplay of pum This makes it possible for the compositor to compare the old pum with the new position, and only clear what is necessary. --- src/nvim/edit.c | 40 +++++++++------------------------------- src/nvim/popupmnu.c | 28 +++++++++++++++++++++------- src/nvim/screen.c | 1 - 3 files changed, 30 insertions(+), 39 deletions(-) (limited to 'src') diff --git a/src/nvim/edit.c b/src/nvim/edit.c index bb16e2ae4d..b39ec58ffe 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -473,6 +473,8 @@ static void insert_enter(InsertState *s) o_lnum = curwin->w_cursor.lnum; } + pum_check_clear(); + foldUpdateAfterInsert(); // When CTRL-C was typed got_int will be set, with the result // that the autocommands won't be executed. When mapped got_int @@ -1447,6 +1449,7 @@ ins_redraw ( redrawWinline(curwin, curwin->w_cursor.lnum); } + pum_check_clear(); if (must_redraw) { update_screen(0); } else if (clear_cmdline || redraw_cmdline) { @@ -2490,20 +2493,10 @@ void set_completion(colnr_T startcol, list_T *list) static pumitem_T *compl_match_array = NULL; static int compl_match_arraysize; + /* * Update the screen and when there is any scrolling remove the popup menu. */ -static void ins_compl_upd_pum(void) -{ - int h; - - if (compl_match_array != NULL) { - h = curwin->w_cline_height; - update_screen(0); - if (h != curwin->w_cline_height) - ins_compl_del_pum(); - } -} /* * Remove any popup menu. @@ -2511,7 +2504,7 @@ static void ins_compl_upd_pum(void) static void ins_compl_del_pum(void) { if (compl_match_array != NULL) { - pum_undisplay(); + pum_undisplay(false); xfree(compl_match_array); compl_match_array = NULL; } @@ -4305,17 +4298,14 @@ ins_compl_next ( } if (!allow_get_expansion) { - /* may undisplay the popup menu first */ - ins_compl_upd_pum(); - - /* redraw to show the user what was inserted */ + // redraw to show the user what was inserted update_screen(0); - /* display the updated popup menu */ + // display the updated popup menu ins_compl_show_pum(); - /* Delete old text to be replaced, since we're still searching and - * don't want to match ourselves! */ + // Delete old text to be replaced, since we're still searching and + // don't want to match ourselves! ins_compl_delete(); } @@ -4862,8 +4852,6 @@ static int ins_complete(int c, bool enable_pum) save_w_leftcol = curwin->w_leftcol; n = ins_compl_next(true, ins_compl_key2count(c), insert_match, false); - /* may undisplay the popup menu */ - ins_compl_upd_pum(); if (n > 1) /* all matches have been found */ compl_matches = n; @@ -7939,7 +7927,6 @@ static void ins_mouse(int c) static void ins_mousescroll(int dir) { win_T *const old_curwin = curwin; - bool did_scroll = false; pos_T tpos = curwin->w_cursor; if (mouse_row >= 0 && mouse_col >= 0) { @@ -7969,7 +7956,6 @@ static void ins_mousescroll(int dir) } else { mouse_scroll_horiz(dir); } - did_scroll = true; } curwin->w_redr_status = TRUE; @@ -7977,14 +7963,6 @@ static void ins_mousescroll(int dir) curwin = old_curwin; curbuf = curwin->w_buffer; - /* The popup menu may overlay the window, need to redraw it. - * TODO: Would be more efficient to only redraw the windows that are - * overlapped by the popup menu. */ - if (pum_visible() && did_scroll) { - redraw_all_later(NOT_VALID); - ins_compl_show_pum(); - } - if (!equalpos(curwin->w_cursor, tpos)) { start_arrow(&tpos); can_cindent = true; diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c index 86cb66c6f3..78b64621e9 100644 --- a/src/nvim/popupmnu.c +++ b/src/nvim/popupmnu.c @@ -42,6 +42,7 @@ static int pum_row; // top row of pum static int pum_col; // left column of pum static bool pum_is_visible = false; +static bool pum_is_drawn = false; static bool pum_external = false; static ScreenGrid pum_grid = SCREEN_GRID_INIT; @@ -88,6 +89,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed) // Mark the pum as visible already here, // to avoid that must_redraw is set when 'cursorcolumn' is on. pum_is_visible = true; + pum_is_drawn = true; validate_cursor_col(); above_row = 0; below_row = cmdline_row; @@ -730,6 +732,8 @@ static int pum_set_selected(int n, int repeat) // Update the screen before drawing the popup menu. // Enable updating the status lines. + // TODO(bfredl): can simplify, get rid of the flag munging? + // or at least eliminate extra redraw before win_enter()? pum_is_visible = false; update_screen(0); pum_is_visible = true; @@ -759,17 +763,27 @@ static int pum_set_selected(int n, int repeat) } /// Undisplay the popup menu (later). -void pum_undisplay(void) +void pum_undisplay(bool immediate) { pum_is_visible = false; pum_array = NULL; - if (pum_external) { - ui_call_popupmenu_hide(); - } else { - ui_comp_remove_grid(&pum_grid); - // TODO(bfredl): consider the possibility of keeping float grids allocated. - grid_free(&pum_grid); + if (immediate) { + pum_check_clear(); + } +} + +void pum_check_clear(void) +{ + if (!pum_is_visible && pum_is_drawn) { + if (pum_external) { + ui_call_popupmenu_hide(); + } else { + ui_comp_remove_grid(&pum_grid); + // TODO(bfredl): consider keeping float grids allocated. + grid_free(&pum_grid); + } + pum_is_drawn = false; } } diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 309a8d5439..b50333e7fa 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -7203,7 +7203,6 @@ void screen_resize(int width, int height) } else { update_topline(); if (pum_drawn()) { - redraw_later(NOT_VALID); ins_compl_show_pum(); } update_screen(NOT_VALID); -- cgit From 2405cf82552e93eb3d5441d2f109647b97af5521 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Sat, 26 Jan 2019 16:57:09 +0100 Subject: vim-patch:8.1.0792: bad display if opening cmdline window from Insert completion --- src/nvim/edit.c | 10 ++++++++-- src/nvim/ex_getln.c | 8 +++++++- 2 files changed, 15 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/nvim/edit.c b/src/nvim/edit.c index b39ec58ffe..a63dd97ba8 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -7132,11 +7132,17 @@ static void ins_reg(void) * message for it. Only call it explicitly. */ ++no_u_sync; if (regname == '=') { - /* Sync undo when evaluating the expression calls setline() or - * append(), so that it can be undone separately. */ + pos_T curpos = curwin->w_cursor; + + // Sync undo when evaluating the expression calls setline() or + // append(), so that it can be undone separately. u_sync_once = 2; regname = get_expr_register(); + + // Cursor may be moved back a column. + curwin->w_cursor = curpos; + check_cursor(); } if (regname == NUL || !valid_yank_reg(regname, false)) { vim_beep(BO_REG); diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 786769dc7d..bd03160bcd 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -51,6 +51,7 @@ #include "nvim/option.h" #include "nvim/os_unix.h" #include "nvim/path.h" +#include "nvim/popupmnu.h" #include "nvim/regexp.h" #include "nvim/screen.h" #include "nvim/search.h" @@ -6051,7 +6052,12 @@ static int open_cmdwin(void) /* Don't execute autocommands while creating the window. */ block_autocmds(); - /* don't use a new tab page */ + + // When using completion in Insert mode with = one can open the + // command line window, but we don't want the popup menu then. + pum_undisplay(true); + + // don't use a new tab page cmdmod.tab = 0; cmdmod.noswapfile = 1; -- cgit From 69bdc4f0726ae0f3a2610dadbaf4156acfe74f49 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Thu, 24 Jan 2019 21:37:38 +0100 Subject: ui/compositor: add redraws needed due to intersected doublewidth chars. --- src/nvim/api/ui.c | 2 +- src/nvim/api/ui_events.in.h | 2 +- src/nvim/popupmnu.c | 2 +- src/nvim/screen.c | 47 ++++++++++--------------- src/nvim/tui/tui.c | 5 +-- src/nvim/ui.c | 12 +++++-- src/nvim/ui.h | 7 ++++ src/nvim/ui_bridge.c | 6 ++-- src/nvim/ui_compositor.c | 84 +++++++++++++++++++++++++++++---------------- 9 files changed, 99 insertions(+), 68 deletions(-) (limited to 'src') diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index 9d577db022..b77516d588 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -461,7 +461,7 @@ static void remote_ui_put(UI *ui, const char *cell) static void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Integer endcol, Integer clearcol, Integer clearattr, - Boolean wrap, const schar_T *chunk, + LineFlags flags, const schar_T *chunk, const sattr_T *attrs) { UIData *data = ui->data; diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h index 70f8b9d85c..ef3ff0f4c2 100644 --- a/src/nvim/api/ui_events.in.h +++ b/src/nvim/api/ui_events.in.h @@ -92,7 +92,7 @@ void grid_destroy(Integer grid) // translate this in to the public grid_line format. void raw_line(Integer grid, Integer row, Integer startcol, Integer endcol, Integer clearcol, Integer clearattr, - Boolean wrap, const schar_T *chunk, const sattr_T *attrs) + LineFlags flags, const schar_T *chunk, const sattr_T *attrs) FUNC_API_NOEXPORT FUNC_API_COMPOSITOR_IMPL; void event(char *name, Array args, bool *args_consumed) diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c index 78b64621e9..d64406846a 100644 --- a/src/nvim/popupmnu.c +++ b/src/nvim/popupmnu.c @@ -363,7 +363,7 @@ void pum_redraw(void) if (!pum_grid.chars || pum_grid.Rows != pum_height || pum_grid.Columns != grid_width) { - grid_alloc(&pum_grid, pum_height, grid_width, !moved); + grid_alloc(&pum_grid, pum_height, grid_width, !moved, false); ui_call_grid_resize(pum_grid.handle, pum_grid.Columns, pum_grid.Rows); } else if (moved) { grid_invalidate(&pum_grid); diff --git a/src/nvim/screen.c b/src/nvim/screen.c index b50333e7fa..7252289e0e 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -5316,7 +5316,7 @@ void grid_puts(ScreenGrid *grid, char_u *text, int row, int col, int attr) } static int put_dirty_row = -1; -static int put_dirty_first = -1; +static int put_dirty_first = INT_MAX; static int put_dirty_last = 0; /// Start a group of screen_puts_len calls that builds a single screen line. @@ -5347,8 +5347,6 @@ void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, int prev_c = 0; /* previous Arabic character */ int pc, nc, nc1; int pcc[MAX_MCO]; - int force_redraw_this; - int force_redraw_next = FALSE; int need_redraw; bool do_flush = false; @@ -5371,16 +5369,10 @@ void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, /* When drawing over the right halve of a double-wide char clear out the * left halve. Only needed in a terminal. */ - if (col > 0 && col < grid->Columns && grid_fix_col(grid, col, row) != col) { - schar_from_ascii(grid->chars[off - 1], ' '); - grid->attrs[off - 1] = 0; + if (grid != &default_grid && col == 0 && grid_invalid_row(grid, row)) { // redraw the previous cell, make it empty - if (put_dirty_first == -1) { - put_dirty_first = col-1; - } - put_dirty_last = col+1; - // force the cell at "col" to be redrawn - force_redraw_next = true; + put_dirty_first = -1; + put_dirty_last = MAX(put_dirty_last, 1); } max_off = grid->line_offset[row] + grid->Columns; @@ -5428,15 +5420,12 @@ void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, schar_from_cc(buf, u8c, u8cc); - force_redraw_this = force_redraw_next; - force_redraw_next = FALSE; - need_redraw = schar_cmp(grid->chars[off], buf) || (mbyte_cells == 2 && grid->chars[off + 1][0] != 0) || grid->attrs[off] != attr || exmode_active; - if (need_redraw || force_redraw_this) { + if (need_redraw) { // When at the end of the text and overwriting a two-cell // character with a one-cell character, need to clear the next // cell. Also when overwriting the left halve of a two-cell char @@ -5460,9 +5449,7 @@ void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, grid->chars[off + 1][0] = 0; grid->attrs[off + 1] = attr; } - if (put_dirty_first == -1 || col < put_dirty_first) { - put_dirty_first = col; - } + put_dirty_first = MIN(put_dirty_first, col); put_dirty_last = MAX(put_dirty_last, col+mbyte_cells); } @@ -5491,14 +5478,14 @@ void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, void grid_puts_line_flush(ScreenGrid *grid, bool set_cursor) { assert(put_dirty_row != -1); - if (put_dirty_first != -1) { + if (put_dirty_first < put_dirty_last) { if (set_cursor) { ui_grid_cursor_goto(grid->handle, put_dirty_row, MIN(put_dirty_last, grid->Columns-1)); } ui_line(grid, put_dirty_row, put_dirty_first, put_dirty_last, put_dirty_last, 0, false); - put_dirty_first = -1; + put_dirty_first = INT_MAX; put_dirty_last = 0; } put_dirty_row = -1; @@ -5870,9 +5857,7 @@ void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, if (dirty_last > dirty_first) { // TODO(bfredl): support a cleared suffix even with a batched line? if (put_dirty_row == row) { - if (put_dirty_first == -1 || dirty_first < put_dirty_first) { - put_dirty_first = dirty_first; - } + put_dirty_first = MIN(put_dirty_first, dirty_first); put_dirty_last = MAX(put_dirty_last, dirty_last); } else { int last = c2 != ' ' ? dirty_last : dirty_first + (c1 != ' '); @@ -5958,7 +5943,7 @@ void win_grid_alloc(win_T *wp) || grid->Rows != rows || grid->Columns != cols) { if (want_allocation) { - grid_alloc(grid, rows, cols, true); + grid_alloc(grid, rows, cols, true, true); } else { // Single grid mode, all rendering will be redirected to default_grid. // Only keep track of the size and offset of the window. @@ -6054,7 +6039,7 @@ retry: // Continuing with the old arrays may result in a crash, because the // size is wrong. - grid_alloc(&default_grid, Rows, Columns, !doclear); + grid_alloc(&default_grid, Rows, Columns, !doclear, true); StlClickDefinition *new_tab_page_click_defs = xcalloc( (size_t)Columns, sizeof(*new_tab_page_click_defs)); @@ -6088,7 +6073,7 @@ retry: } } -void grid_alloc(ScreenGrid *grid, int rows, int columns, bool copy) +void grid_alloc(ScreenGrid *grid, int rows, int columns, bool copy, bool valid) { int new_row; ScreenGrid new = *grid; @@ -6106,7 +6091,7 @@ void grid_alloc(ScreenGrid *grid, int rows, int columns, bool copy) new.line_offset[new_row] = new_row * new.Columns; new.line_wraps[new_row] = false; - grid_clear_line(&new, new.line_offset[new_row], columns, true); + grid_clear_line(&new, new.line_offset[new_row], columns, valid); if (copy) { // If the screen is not going to be cleared, copy as much as @@ -6234,6 +6219,12 @@ void grid_invalidate(ScreenGrid *grid) (void)memset(grid->attrs, -1, grid->Rows * grid->Columns * sizeof(sattr_T)); } +bool grid_invalid_row(ScreenGrid *grid, int row) +{ + return grid->attrs[grid->line_offset[row]] < 0; +} + + /// Copy part of a grid line for vertically split window. static void linecopy(ScreenGrid *grid, int to, int from, int col, int width) diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 2e26a75be1..a1a43c3e3a 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -1273,7 +1273,7 @@ static void tui_option_set(UI *ui, String name, Object value) static void tui_raw_line(UI *ui, Integer g, Integer linerow, Integer startcol, Integer endcol, Integer clearcol, Integer clearattr, - Boolean wrap, const schar_T *chunk, + LineFlags flags, const schar_T *chunk, const sattr_T *attrs) { TUIData *data = ui->data; @@ -1295,7 +1295,8 @@ static void tui_raw_line(UI *ui, Integer g, Integer linerow, Integer startcol, (int)clearattr); } - if (wrap && ui->width == grid->width && linerow + 1 < grid->height) { + if (flags & kLineFlagWrap && ui->width == grid->width + && linerow + 1 < grid->height) { // Only do line wrapping if the grid width is equal to the terminal // width and the line continuation is within the grid. diff --git a/src/nvim/ui.c b/src/nvim/ui.c index 3dc6ccaf52..dd4eb0f196 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -315,11 +315,17 @@ void ui_set_ext_option(UI *ui, UIExtension ext, bool active) void ui_line(ScreenGrid *grid, int row, int startcol, int endcol, int clearcol, int clearattr, bool wrap) { + LineFlags flags = wrap ? kLineFlagWrap : 0; + if (startcol == -1) { + startcol = 0; + flags |= kLineFlagInvalid; + } + size_t off = grid->line_offset[row] + (size_t)startcol; - ui_call_raw_line(grid->handle, row, startcol, endcol, - clearcol, clearattr, wrap, (const schar_T *)grid->chars + off, - (const sattr_T *)grid->attrs + off); + ui_call_raw_line(grid->handle, row, startcol, endcol, clearcol, clearattr, + flags, (const schar_T *)grid->chars + off, + (const sattr_T *)grid->attrs + off); if (p_wd) { // 'writedelay': flush & delay each time. int old_row = cursor_row, old_col = cursor_col; diff --git a/src/nvim/ui.h b/src/nvim/ui.h index 8aeb632f6d..a5a0fa8b75 100644 --- a/src/nvim/ui.h +++ b/src/nvim/ui.h @@ -34,6 +34,13 @@ EXTERN const char *ui_ext_names[] INIT(= { typedef struct ui_t UI; +enum { + kLineFlagWrap = 1, + kLineFlagInvalid = 2, +}; + +typedef int LineFlags; + struct ui_t { bool rgb; bool composed; diff --git a/src/nvim/ui_bridge.c b/src/nvim/ui_bridge.c index bd5d37be73..91cd458702 100644 --- a/src/nvim/ui_bridge.c +++ b/src/nvim/ui_bridge.c @@ -153,14 +153,14 @@ static void ui_bridge_raw_line_event(void **argv) UI *ui = UI(argv[0]); ui->raw_line(ui, PTR2INT(argv[1]), PTR2INT(argv[2]), PTR2INT(argv[3]), PTR2INT(argv[4]), PTR2INT(argv[5]), PTR2INT(argv[6]), - PTR2INT(argv[7]), argv[8], argv[9]); + (LineFlags)PTR2INT(argv[7]), argv[8], argv[9]); xfree(argv[8]); xfree(argv[9]); } static void ui_bridge_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Integer endcol, Integer clearcol, Integer clearattr, - Boolean wrap, const schar_T *chunk, + LineFlags flags, const schar_T *chunk, const sattr_T *attrs) { size_t ncol = (size_t)(endcol-startcol); @@ -168,7 +168,7 @@ static void ui_bridge_raw_line(UI *ui, Integer grid, Integer row, sattr_T *hl = xmemdup(attrs, ncol * sizeof(sattr_T)); UI_BRIDGE_CALL(ui, raw_line, 10, ui, INT2PTR(grid), INT2PTR(row), INT2PTR(startcol), INT2PTR(endcol), INT2PTR(clearcol), - INT2PTR(clearattr), INT2PTR(wrap), c, hl); + INT2PTR(clearattr), INT2PTR(flags), c, hl); } static void ui_bridge_suspend(UI *b) diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c index 2ca0d1a6eb..9e0c44f3c2 100644 --- a/src/nvim/ui_compositor.c +++ b/src/nvim/ui_compositor.c @@ -217,8 +217,16 @@ static void ui_comp_grid_cursor_goto(UI *ui, Integer grid_handle, /// do something more efficient (where efficiency means smaller deltas to /// the downstream UI.) static void compose_line(Integer row, Integer startcol, Integer endcol, - bool wrap) + LineFlags flags) { + // in case we start on the right half of a double-width char, we need to + // check the left half. But skip it in output if it wasn't doublewidth. + int skip = 0; + if (startcol > 0 && (flags & kLineFlagInvalid)) { + startcol--; + skip = 1; + } + int col = (int)startcol; ScreenGrid *grid = NULL; @@ -247,15 +255,37 @@ static void compose_line(Integer row, Integer startcol, Integer endcol, + (size_t)(col-grid->comp_col); memcpy(linebuf+(col-startcol), grid->chars+off, n * sizeof(*linebuf)); memcpy(attrbuf+(col-startcol), grid->attrs+off, n * sizeof(*attrbuf)); + + // Tricky: if overlap caused a doublewidth char to get cut-off, must + // replace the visible half with a space. + if (linebuf[col-startcol][0] == NUL) { + linebuf[col-startcol][0] = ' '; + linebuf[col-startcol][1] = NUL; + } else if (n > 1 && linebuf[col-startcol+1][0] == NUL) { + skip = 0; + } + if (grid->comp_col+grid->Columns > until + && grid->chars[off+n][0] == NUL) { + linebuf[until-1-startcol][0] = ' '; + linebuf[until-1-startcol][1] = '\0'; + if (col == startcol && n == 1) { + skip = 0; + } + } col = until; } assert(endcol <= chk_width); assert(row < chk_height); - // TODO(bfredl): too conservative, need check - // grid->line_wraps if grid->Width == Width - wrap = wrap && grid && grid->handle == 1; - ui_composed_call_raw_line(1, row, startcol, endcol, endcol, 0, wrap, - (const schar_T *)linebuf, (const sattr_T *)attrbuf); + + if (!(grid && grid == &default_grid)) { + // TODO(bfredl): too conservative, need check + // grid->line_wraps if grid->Width == Width + flags = flags & ~kLineFlagWrap; + } + + ui_composed_call_raw_line(1, row, startcol+skip, endcol, endcol, 0, flags, + (const schar_T *)linebuf+skip, + (const sattr_T *)attrbuf+skip); } static void compose_area(Integer startrow, Integer endrow, @@ -264,39 +294,35 @@ static void compose_area(Integer startrow, Integer endrow, endrow = MIN(endrow, default_grid.Rows); endcol = MIN(endcol, default_grid.Columns); for (int r = (int)startrow; r < endrow; r++) { - compose_line(r, startcol, endcol, false); + compose_line(r, startcol, endcol, kLineFlagInvalid); } } -static void draw_line(ScreenGrid *grid, Integer row, Integer startcol, - Integer endcol, Integer clearcol, Integer clearattr, - bool wrap, const schar_T *chunk, const sattr_T *attrs) -{ - row += grid->comp_row; - startcol += grid->comp_col; - endcol += grid->comp_col; - clearcol += grid->comp_col; - wrap = wrap && grid->handle == 1; - assert(clearcol <= default_grid.Columns); - if (kv_size(layers) > grid->comp_index+1) { - compose_line(row, startcol, clearcol, wrap); - } else { - ui_composed_call_raw_line(1, row, startcol, endcol, clearcol, clearattr, - wrap, chunk, attrs); - } -} - static void ui_comp_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Integer endcol, - Integer clearcol, Integer clearattr, bool wrap, - const schar_T *chunk, const sattr_T *attrs) + Integer clearcol, Integer clearattr, + LineFlags flags, const schar_T *chunk, + const sattr_T *attrs) { if (!ui_comp_should_draw() || !ui_comp_set_grid((int)grid)) { return; } - draw_line(curgrid, row, startcol, endcol, clearcol, clearattr, wrap, chunk, - attrs); + + row += curgrid->comp_row; + startcol += curgrid->comp_col; + endcol += curgrid->comp_col; + clearcol += curgrid->comp_col; + if (curgrid != &default_grid) { + flags = flags & ~kLineFlagWrap; + } + assert(clearcol <= default_grid.Columns); + if (flags & kLineFlagInvalid || kv_size(layers) > curgrid->comp_index+1) { + compose_line(row, startcol, clearcol, flags); + } else { + ui_composed_call_raw_line(1, row, startcol, endcol, clearcol, clearattr, + flags, chunk, attrs); + } } /// The screen is invalid and will soon be cleared -- cgit From 0f96a21e3fd6ba989e27a992e48c084dd02d8885 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Thu, 31 Jan 2019 13:13:34 +0100 Subject: multigrid: reset win scrolling after swap message --- src/nvim/globals.h | 15 +++++++++------ src/nvim/memline.c | 4 +++- src/nvim/message.c | 3 ++- src/nvim/screen.c | 12 ++++++++---- 4 files changed, 22 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/nvim/globals.h b/src/nvim/globals.h index ccdf8f87ab..f47697b190 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -194,12 +194,15 @@ EXTERN int compl_cont_status INIT(= 0); EXTERN int cmdmsg_rl INIT(= false); // cmdline is drawn right to left EXTERN int msg_col; EXTERN int msg_row; -EXTERN int msg_scrolled; /* Number of screen lines that windows have - * scrolled because of printing messages. */ -EXTERN int msg_scrolled_ign INIT(= FALSE); -/* when TRUE don't set need_wait_return in - msg_puts_attr() when msg_scrolled is - non-zero */ +EXTERN int msg_scrolled; // Number of screen lines that windows have + // scrolled because of printing messages. +// when true don't set need_wait_return in msg_puts_attr() +// when msg_scrolled is non-zero +EXTERN bool msg_scrolled_ign INIT(= false); +// Whether the screen is damaged due to scrolling. Sometimes msg_scrolled +// is reset before the screen is redrawn, so we need to keep track of this. +EXTERN bool msg_did_scroll INIT(= false); + EXTERN char_u *keep_msg INIT(= NULL); /* msg to be shown after redraw */ EXTERN int keep_msg_attr INIT(= 0); /* highlight attr for keep_msg */ diff --git a/src/nvim/memline.c b/src/nvim/memline.c index 4c4f7d65bd..662eda3c7c 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -3412,7 +3412,9 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, # endif xfree(name); - /* pretend screen didn't scroll, need redraw anyway */ + // pretend screen didn't scroll, need redraw anyway + // TODO(bfredl): when doing the message grid refactor, + // simplify this special case. msg_scrolled = 0; redraw_all_later(NOT_VALID); } diff --git a/src/nvim/message.c b/src/nvim/message.c index 1330460867..4552999256 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -1960,8 +1960,9 @@ int msg_scrollsize(void) */ static void msg_scroll_up(void) { - if (msg_scrolled == 0) { + if (!msg_did_scroll) { ui_call_win_scroll_over_start(); + msg_did_scroll = true; } if (dy_flags & DY_MSGSEP) { if (msg_scrolled == 0) { diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 7252289e0e..759eefaecc 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -307,11 +307,15 @@ void update_screen(int type) ++display_tick; /* let syntax code know we're in a next round of * display updating */ - /* - * if the screen was scrolled up when displaying a message, scroll it down - */ - if (msg_scrolled) { + // Tricky: vim code can reset msg_scrolled behind our back, so need + // separate bookkeeping for now. + if (msg_did_scroll) { ui_call_win_scroll_over_reset(); + msg_did_scroll = false; + } + + // if the screen was scrolled up when displaying a message, scroll it down + if (msg_scrolled) { clear_cmdline = true; if (dy_flags & DY_MSGSEP) { int valid = MAX(Rows - msg_scrollsize(), 0); -- cgit