diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/nvim/api/ui.c | 3 | ||||
-rw-r--r-- | src/nvim/api/ui_events.in.h | 4 | ||||
-rw-r--r-- | src/nvim/buffer.c | 1 | ||||
-rw-r--r-- | src/nvim/eval.c | 33 | ||||
-rw-r--r-- | src/nvim/ex_getln.c | 33 | ||||
-rw-r--r-- | src/nvim/grid_defs.h | 13 | ||||
-rw-r--r-- | src/nvim/highlight.c | 3 | ||||
-rw-r--r-- | src/nvim/highlight_defs.h | 2 | ||||
-rw-r--r-- | src/nvim/memline.c | 6 | ||||
-rw-r--r-- | src/nvim/message.c | 278 | ||||
-rw-r--r-- | src/nvim/message.h | 8 | ||||
-rw-r--r-- | src/nvim/mouse.c | 10 | ||||
-rw-r--r-- | src/nvim/normal.c | 10 | ||||
-rw-r--r-- | src/nvim/option.c | 1 | ||||
-rw-r--r-- | src/nvim/option_defs.h | 1 | ||||
-rw-r--r-- | src/nvim/screen.c | 148 | ||||
-rw-r--r-- | src/nvim/syntax.c | 3 | ||||
-rw-r--r-- | src/nvim/ui.c | 2 | ||||
-rw-r--r-- | src/nvim/ui_compositor.c | 122 | ||||
-rw-r--r-- | src/nvim/window.c | 4 |
20 files changed, 514 insertions, 171 deletions
diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index 20ed77afad..7e45abb897 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -133,8 +133,7 @@ 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->msg_set_pos = remote_ui_msg_set_pos; 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 9f58257e53..6677e248cf 100644 --- a/src/nvim/api/ui_events.in.h +++ b/src/nvim/api/ui_events.in.h @@ -112,9 +112,7 @@ void win_hide(Integer grid) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; void win_close(Integer grid) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; -void win_scroll_over_start(void) - FUNC_API_SINCE(6) FUNC_API_BRIDGE_IMPL FUNC_API_COMPOSITOR_IMPL; -void win_scroll_over_reset(void) +void msg_set_pos(Integer grid, Integer row, Boolean scrolled, String sep_char) FUNC_API_SINCE(6) FUNC_API_BRIDGE_IMPL FUNC_API_COMPOSITOR_IMPL; void popupmenu_show(Array items, Integer selected, diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 2cc9643b7e..382b4c45c1 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -2656,7 +2656,6 @@ void buflist_list(exarg_T *eap) msg_outtrans(IObuff); line_breakcheck(); } - ui_flush(); } /* diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 12c53fa804..488790970e 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -14648,6 +14648,21 @@ static void f_rpcstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } +static void screenchar_adjust_grid(ScreenGrid **grid, int *row, int *col) +{ + // TODO(bfredl): this is a hack for legacy tests which use screenchar() + // to check printed messages on the screen (but not floats etc + // as these are not legacy features). If the compositor is refactored to + // have its own buffer, this should just read from it instead. + msg_scroll_flush(); + if (msg_grid.chars && msg_grid.comp_index > 0 && *row >= msg_grid.comp_row + && *row < (msg_grid.Rows + msg_grid.comp_row) + && *col < msg_grid.Columns) { + *grid = &msg_grid; + *row -= msg_grid.comp_row; + } +} + /* * "screenattr()" function */ @@ -14655,13 +14670,15 @@ static void f_screenattr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int c; - const int row = (int)tv_get_number_chk(&argvars[0], NULL) - 1; - const int col = (int)tv_get_number_chk(&argvars[1], NULL) - 1; + int row = (int)tv_get_number_chk(&argvars[0], NULL) - 1; + int col = (int)tv_get_number_chk(&argvars[1], NULL) - 1; if (row < 0 || row >= default_grid.Rows || col < 0 || col >= default_grid.Columns) { c = -1; } else { - c = default_grid.attrs[default_grid.line_offset[row] + col]; + ScreenGrid *grid = &default_grid; + screenchar_adjust_grid(&grid, &row, &col); + c = grid->attrs[grid->line_offset[row] + col]; } rettv->vval.v_number = c; } @@ -14671,17 +14688,17 @@ static void f_screenattr(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_screenchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - int off; int c; - const int row = tv_get_number_chk(&argvars[0], NULL) - 1; - const int col = tv_get_number_chk(&argvars[1], NULL) - 1; + int row = tv_get_number_chk(&argvars[0], NULL) - 1; + int col = tv_get_number_chk(&argvars[1], NULL) - 1; if (row < 0 || row >= default_grid.Rows || col < 0 || col >= default_grid.Columns) { c = -1; } else { - off = default_grid.line_offset[row] + col; - c = utf_ptr2char(default_grid.chars[off]); + ScreenGrid *grid = &default_grid; + screenchar_adjust_grid(&grid, &row, &col); + c = utf_ptr2char(grid->chars[grid->line_offset[row] + col]); } rettv->vval.v_number = c; } diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 4e43e95c2e..4f35555098 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -310,6 +310,8 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) cmdmsg_rl = false; } + msg_grid_validate(); + redir_off = true; // don't redirect the typed command if (!cmd_silent) { gotocmdline(true); @@ -908,7 +910,7 @@ static int command_line_execute(VimState *state, int key) if (!cmd_silent) { if (!ui_has(kUICmdline)) { - ui_cursor_goto(msg_row, 0); + cmd_cursor_goto(msg_row, 0); } ui_flush(); } @@ -2323,7 +2325,7 @@ redraw: } } msg_clr_eos(); - ui_cursor_goto(msg_row, msg_col); + cmd_cursor_goto(msg_row, msg_col); continue; } @@ -2391,7 +2393,7 @@ redraw: line_ga.ga_len += len; escaped = FALSE; - ui_cursor_goto(msg_row, msg_col); + cmd_cursor_goto(msg_row, msg_col); pend = (char_u *)(line_ga.ga_data) + line_ga.ga_len; /* We are done when a NL is entered, but not when it comes after an @@ -3436,7 +3438,7 @@ void redrawcmd(void) /* when 'incsearch' is set there may be no command line while redrawing */ if (ccline.cmdbuff == NULL) { - ui_cursor_goto(cmdline_row, 0); + cmd_cursor_goto(cmdline_row, 0); msg_clr_eos(); return; } @@ -3510,7 +3512,14 @@ static void cursorcmd(void) } } - ui_cursor_goto(msg_row, msg_col); + cmd_cursor_goto(msg_row, msg_col); +} + +static void cmd_cursor_goto(int row, int col) +{ + ScreenGrid *grid = &msg_grid_adj; + screen_adjust_grid(&grid, &row, &col); + ui_grid_cursor_goto(grid->handle, row, col); } void gotocmdline(int clr) @@ -3519,13 +3528,15 @@ void gotocmdline(int clr) return; } msg_start(); - if (cmdmsg_rl) + if (cmdmsg_rl) { msg_col = Columns - 1; - else - msg_col = 0; /* always start in column 0 */ - if (clr) /* clear the bottom line(s) */ - msg_clr_eos(); /* will reset clear_cmdline */ - ui_cursor_goto(cmdline_row, 0); + } else { + msg_col = 0; // always start in column 0 + } + if (clr) { // clear the bottom line(s) + msg_clr_eos(); // will reset clear_cmdline + } + cmd_cursor_goto(cmdline_row, 0); } /* diff --git a/src/nvim/grid_defs.h b/src/nvim/grid_defs.h index e4021c033b..d9be53468f 100644 --- a/src/nvim/grid_defs.h +++ b/src/nvim/grid_defs.h @@ -43,6 +43,10 @@ typedef struct { unsigned *line_offset; char_u *line_wraps; + // last column that was drawn (not cleared with the default background). + // only used when "throttled" is set. Not allocated by grid_alloc! + int *dirty_col; + // the size of the allocated grid. int Rows; int Columns; @@ -50,12 +54,17 @@ typedef struct { // The state of the grid is valid. Otherwise it needs to be redrawn. bool valid; + // only draw internally and don't send updates yet to the compositor or + // external UI. + bool throttled; + // offsets for the grid relative to the global screen int row_offset; int col_offset; // whether the compositor should blend the grid with the background grid bool blending; + bool focusable; // state owned by the compositor. int comp_row; @@ -64,7 +73,7 @@ typedef struct { bool comp_disabled; } ScreenGrid; -#define SCREEN_GRID_INIT { 0, NULL, NULL, NULL, NULL, 0, 0, false, 0, 0, \ - false, 0, 0, 0, false } +#define SCREEN_GRID_INIT { 0, NULL, NULL, NULL, NULL, NULL, 0, 0, false, \ + false, 0, 0, false, true, 0, 0, 0, false } #endif // NVIM_GRID_DEFS_H diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c index 3a61409dfe..f44cd5cdab 100644 --- a/src/nvim/highlight.c +++ b/src/nvim/highlight.c @@ -7,6 +7,7 @@ #include "nvim/highlight.h" #include "nvim/highlight_defs.h" #include "nvim/map.h" +#include "nvim/message.h" #include "nvim/popupmnu.h" #include "nvim/screen.h" #include "nvim/syntax.h" @@ -161,6 +162,8 @@ int hl_get_ui_attr(int idx, int final_id, bool optional) if (pum_drawn()) { must_redraw_pum = true; } + } else if (idx == HLF_MSG) { + msg_grid.blending = attrs.hl_blend > -1; } if (optional && !available) { diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h index afccf9e6f6..512d87fa34 100644 --- a/src/nvim/highlight_defs.h +++ b/src/nvim/highlight_defs.h @@ -93,6 +93,7 @@ typedef enum { , HLF_INACTIVE // NormalNC: Normal text in non-current windows , HLF_MSGSEP // message separator line , HLF_NFLOAT // Floating window + , HLF_MSG // Message area , HLF_COUNT // MUST be the last one } hlf_T; @@ -146,6 +147,7 @@ EXTERN const char *hlf_names[] INIT(= { [HLF_INACTIVE] = "NormalNC", [HLF_MSGSEP] = "MsgSeparator", [HLF_NFLOAT] = "NormalFloat", + [HLF_MSG] = "MsgArea", }); diff --git a/src/nvim/memline.c b/src/nvim/memline.c index 34774055c1..bb125df968 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -3522,11 +3522,9 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, } xfree(name); + // 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); + msg_reset_scroll(); } if (choice > 0) { diff --git a/src/nvim/message.c b/src/nvim/message.c index c8deaa590c..9e5c35d58b 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -35,7 +35,9 @@ #include "nvim/screen.h" #include "nvim/strings.h" #include "nvim/syntax.h" +#include "nvim/highlight.h" #include "nvim/ui.h" +#include "nvim/ui_compositor.h" #include "nvim/mouse.h" #include "nvim/os/os.h" #include "nvim/os/input.h" @@ -124,6 +126,75 @@ static int msg_ext_visible = 0; ///< number of messages currently visible /// Shouldn't clear message after leaving cmdline static bool msg_ext_keep_after_cmdline = false; +static int msg_grid_pos_at_flush = 0; +static int msg_grid_scroll_discount = 0; + +static void ui_ext_msg_set_pos(int row, bool scrolled) +{ + char buf[MAX_MCO]; + size_t size = utf_char2bytes(curwin->w_p_fcs_chars.msgsep, (char_u *)buf); + buf[size] = '\0'; + ui_call_msg_set_pos(msg_grid.handle, row, scrolled, + (String){ .data = buf, .size = size }); +} + +void msg_grid_set_pos(int row, bool scrolled) +{ + if (!msg_grid.throttled) { + ui_ext_msg_set_pos(row, scrolled); + msg_grid_pos_at_flush = row; + } + msg_grid_pos = row; + if (msg_grid.chars) { + msg_grid_adj.row_offset = -row; + } +} + +void msg_grid_validate(void) +{ + grid_assign_handle(&msg_grid); + bool should_alloc = msg_dothrottle(); + if (msg_grid.Rows != Rows || msg_grid.Columns != Columns + || (should_alloc && !msg_grid.chars)) { + // TODO(bfredl): eventually should be set to "invalid". I e all callers + // will use the grid including clear to EOS if necessary. + grid_alloc(&msg_grid, Rows, Columns, false, true); + + xfree(msg_grid.dirty_col); + msg_grid.dirty_col = xcalloc(Rows, sizeof(*msg_grid.dirty_col)); + + // Tricky: allow resize while pager is active + int pos = msg_scrolled ? msg_grid_pos : Rows - p_ch; + ui_comp_put_grid(&msg_grid, pos, 0, msg_grid.Rows, msg_grid.Columns, + false, true); + ui_call_grid_resize(msg_grid.handle, msg_grid.Columns, msg_grid.Rows); + + msg_grid.throttled = false; // don't throttle in 'cmdheight' area + msg_scroll_at_flush = msg_scrolled; + msg_grid.focusable = false; + if (!msg_scrolled) { + msg_grid_set_pos(Rows - p_ch, false); + } + } else if (!should_alloc && msg_grid.chars) { + ui_comp_remove_grid(&msg_grid); + grid_free(&msg_grid); + XFREE_CLEAR(msg_grid.dirty_col); + ui_call_grid_destroy(msg_grid.handle); + msg_grid.throttled = false; + msg_grid_adj.row_offset = 0; + redraw_cmdline = true; + } else if (msg_grid.chars && !msg_scrolled && msg_grid_pos != Rows - p_ch) { + msg_grid_set_pos(Rows - p_ch, false); + } + + if (msg_grid.chars && cmdline_row < msg_grid_pos) { + // TODO(bfredl): this should already be the case, but fails in some + // "batched" executions where compute_cmdrow() use stale positions or + // something. + cmdline_row = msg_grid_pos; + } +} + /* * msg(s) - displays the string 's' on the status line * When terminal not initialized (yet) mch_errmsg(..) is used. @@ -1701,6 +1772,7 @@ void msg_prt_line(char_u *s, int list) static char_u *screen_puts_mbyte(char_u *s, int l, int attr) { int cw; + attr = hl_combine_attr(HL_ATTR(HLF_MSG), attr); msg_didout = true; // remember that line is not empty cw = utf_ptr2cells(s); @@ -1711,7 +1783,7 @@ static char_u *screen_puts_mbyte(char_u *s, int l, int attr) return s; } - grid_puts_len(&default_grid, s, l, msg_row, msg_col, attr); + grid_puts_len(&msg_grid_adj, s, l, msg_row, msg_col, attr); if (cmdmsg_rl) { msg_col -= cw; if (msg_col == 0) { @@ -1900,6 +1972,8 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, return; } + msg_grid_validate(); + cmdline_was_last_drawn = redrawing_cmdline; while ((maxlen < 0 || (int)(s - str) < maxlen) && *s != NUL) { @@ -1929,15 +2003,16 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, if (msg_no_more && lines_left == 0) break; - /* Scroll the screen up one line. */ - msg_scroll_up(); + // Scroll the screen up one line. + bool has_last_char = (*s >= ' ' && !cmdmsg_rl); + msg_scroll_up(!has_last_char); msg_row = Rows - 2; if (msg_col >= Columns) /* can happen after screen resize */ msg_col = Columns - 1; // Display char in last column before showing more-prompt. - if (*s >= ' ' && !cmdmsg_rl) { + if (has_last_char) { if (maxlen >= 0) { // Avoid including composing chars after the end. l = utfc_ptr2len_len(s, (int)((str + maxlen) - s)); @@ -1950,6 +2025,15 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, did_last_char = false; } + // Tricky: if last cell will be written, delay the throttle until + // after the first scroll. Otherwise we would need to keep track of it. + if (has_last_char && msg_dothrottle()) { + if (!msg_grid.throttled) { + msg_grid_scroll_discount++; + } + msg_grid.throttled = true; + } + if (p_more) { // Store text for scrolling back. store_sb_text((char_u **)&sb_str, (char_u *)s, attr, &sb_col, true); @@ -2074,29 +2158,106 @@ int msg_scrollsize(void) return msg_scrolled + p_ch + 1; } +bool msg_dothrottle(void) +{ + return default_grid.chars && msg_use_msgsep() + && !ui_has(kUIMessages); +} + +bool msg_use_msgsep(void) +{ + // the full-screen scroll behavior doesn't really make sense with + // 'ext_multigrid' + return ((dy_flags & DY_MSGSEP) || ui_has(kUIMultigrid)); +} + /* * Scroll the screen up one line for displaying the next message line. */ -void msg_scroll_up(void) +void msg_scroll_up(bool may_throttle) +{ + if (may_throttle && msg_dothrottle()) { + msg_grid.throttled = true; + } + msg_did_scroll = true; + if (msg_use_msgsep()) { + if (msg_grid_pos > 0) { + msg_grid_set_pos(msg_grid_pos-1, true); + } else { + grid_del_lines(&msg_grid, 0, 1, msg_grid.Rows, 0, msg_grid.Columns); + memmove(msg_grid.dirty_col, msg_grid.dirty_col+1, + (msg_grid.Rows-1) * sizeof(*msg_grid.dirty_col)); + msg_grid.dirty_col[msg_grid.Rows-1] = 0; + } + } else { + grid_del_lines(&msg_grid_adj, 0, 1, Rows, 0, Columns); + } + + grid_fill(&msg_grid_adj, Rows-1, Rows, 0, Columns, ' ', ' ', + HL_ATTR(HLF_MSG)); +} + +void msg_scroll_flush(void) +{ + if (!msg_grid.throttled) { + return; + } + msg_grid.throttled = false; + int pos_delta = msg_grid_pos_at_flush - msg_grid_pos; + assert(pos_delta >= 0); + int delta = MIN(msg_scrolled - msg_scroll_at_flush, msg_grid.Rows); + + if (pos_delta > 0) { + ui_ext_msg_set_pos(msg_grid_pos, true); + msg_grid_pos_at_flush = msg_grid_pos; + } + + int to_scroll = delta-pos_delta-msg_grid_scroll_discount; + assert(to_scroll >= 0); + + // TODO(bfredl): msg_grid_pos should be 0 already when starting scrolling + // but this sometimes fails in "headless" message printing. + if (to_scroll > 0 && msg_grid_pos == 0) { + ui_call_grid_scroll(msg_grid.handle, 0, Rows, 0, Columns, to_scroll, 0); + } + + for (int i = MAX(Rows-MAX(delta, 1), 0); i < Rows; i++) { + int row = i-msg_grid_pos; + assert(row >= 0); + ui_line(&msg_grid, row, 0, msg_grid.dirty_col[row], msg_grid.Columns, + HL_ATTR(HLF_MSG), false); + msg_grid.dirty_col[row] = 0; + } + msg_scroll_at_flush = msg_scrolled; + msg_grid_scroll_discount = 0; +} + +void msg_reset_scroll(void) { - if (!msg_did_scroll) { - ui_call_win_scroll_over_start(); - msg_did_scroll = true; + if (ui_has(kUIMessages)) { + msg_ext_clear(true); + return; } - if (dy_flags & DY_MSGSEP) { - if (msg_scrolled == 0) { - grid_fill(&default_grid, Rows-p_ch-1, Rows-p_ch, 0, (int)Columns, - curwin->w_p_fcs_chars.msgsep, curwin->w_p_fcs_chars.msgsep, - HL_ATTR(HLF_MSGSEP)); + // TODO(bfredl): some duplicate logic with update_screen(). Later on + // we should properly disentangle message clear with full screen redraw. + if (msg_dothrottle()) { + msg_grid.throttled = false; + // TODO(bfredl): risk for extra flicker i e with + // "nvim -o has_swap also_has_swap" + msg_grid_set_pos(Rows - p_ch, false); + clear_cmdline = true; + if (msg_grid.chars) { + // non-displayed part of msg_grid is considered invalid. + for (int i = 0; i < MIN(msg_scrollsize(), msg_grid.Rows); i++) { + grid_clear_line(&msg_grid, msg_grid.line_offset[i], + (int)msg_grid.Columns, false); + } } - int nscroll = MIN(msg_scrollsize()+1, Rows); - grid_del_lines(&default_grid, Rows-nscroll, 1, Rows, 0, Columns); } else { - grid_del_lines(&default_grid, 0, 1, (int)Rows, 0, Columns); + redraw_all_later(NOT_VALID); } - // TODO(bfredl): when msgsep display is properly batched, this fill should be - // eliminated. - grid_fill(&default_grid, Rows-1, Rows, 0, (int)Columns, ' ', ' ', 0); + msg_scrolled = 0; + msg_scroll_at_flush = 0; } /* @@ -2285,6 +2446,11 @@ static msgchunk_T *disp_sb_line(int row, msgchunk_T *smp) break; mp = mp->sb_next; } + + if (msg_col < Columns) { + grid_fill(&msg_grid_adj, row, row+1, msg_col, Columns, ' ', ' ', + HL_ATTR(HLF_MSG)); + } return mp->sb_next; } @@ -2293,9 +2459,10 @@ static msgchunk_T *disp_sb_line(int row, msgchunk_T *smp) */ static void t_puts(int *t_col, const char_u *t_s, const char_u *s, int attr) { + attr = hl_combine_attr(HL_ATTR(HLF_MSG), attr); // Output postponed text. msg_didout = true; // Remember that line is not empty. - grid_puts_len(&default_grid, (char_u *)t_s, (int)(s - t_s), msg_row, msg_col, + grid_puts_len(&msg_grid_adj, (char_u *)t_s, (int)(s - t_s), msg_row, msg_col, attr); msg_col += *t_col; *t_col = 0; @@ -2514,14 +2681,14 @@ static int do_more_prompt(int typed_char) } if (toscroll == -1) { - grid_ins_lines(&default_grid, 0, 1, (int)Rows, 0, (int)Columns); - grid_fill(&default_grid, 0, 1, 0, (int)Columns, ' ', ' ', 0); + grid_ins_lines(&msg_grid_adj, 0, 1, Rows, 0, Columns); // display line at top (void)disp_sb_line(0, mp); } else { - /* redisplay all lines */ - screenclear(); - for (i = 0; mp != NULL && i < Rows - 1; ++i) { + // redisplay all lines + // TODO(bfredl): this case is not optimized (though only concerns + // event fragmentization, not unnecessary scroll events). + for (i = 0; mp != NULL && i < Rows - 1; i++) { mp = disp_sb_line(i, mp); ++msg_scrolled; } @@ -2531,20 +2698,24 @@ static int do_more_prompt(int typed_char) } else { /* First display any text that we scrolled back. */ while (toscroll > 0 && mp_last != NULL) { - /* scroll up, display line at bottom */ - msg_scroll_up(); + if (msg_dothrottle() && !msg_grid.throttled) { + // Tricky: we redraw at one line higher than usual. Therefore + // the non-flushed area is one line larger. + msg_scroll_at_flush--; + msg_grid_scroll_discount++; + } + // scroll up, display line at bottom + msg_scroll_up(true); inc_msg_scrolled(); - grid_fill(&default_grid, (int)Rows - 2, (int)Rows - 1, 0, - (int)Columns, ' ', ' ', 0); - mp_last = disp_sb_line((int)Rows - 2, mp_last); - --toscroll; + mp_last = disp_sb_line(Rows - 2, mp_last); + toscroll--; } } if (toscroll <= 0) { // displayed the requested text, more prompt again - grid_fill(&default_grid, (int)Rows - 1, (int)Rows, 0, - (int)Columns, ' ', ' ', 0); + grid_fill(&msg_grid_adj, Rows - 1, Rows, 0, Columns, ' ', ' ', + HL_ATTR(HLF_MSG)); msg_moremsg(false); continue; } @@ -2557,8 +2728,11 @@ static int do_more_prompt(int typed_char) } // clear the --more-- message - grid_fill(&default_grid, (int)Rows - 1, (int)Rows, 0, (int)Columns, ' ', ' ', - 0); + grid_fill(&msg_grid_adj, Rows - 1, Rows, 0, Columns, ' ', ' ', 0); + redraw_cmdline = true; + clear_cmdline = false; + mode_displayed = false; + State = oldState; setmouse(); if (quit_more) { @@ -2607,8 +2781,9 @@ void mch_msg(char *str) */ static void msg_screen_putchar(int c, int attr) { + attr = hl_combine_attr(HL_ATTR(HLF_MSG), attr); msg_didout = true; // remember that line is not empty - grid_putchar(&default_grid, c, msg_row, msg_col, attr); + grid_putchar(&msg_grid_adj, c, msg_row, msg_col, attr); if (cmdmsg_rl) { if (--msg_col == 0) { msg_col = Columns; @@ -2628,11 +2803,11 @@ void msg_moremsg(int full) char_u *s = (char_u *)_("-- More --"); attr = HL_ATTR(HLF_M); - grid_puts(&default_grid, s, (int)Rows - 1, 0, attr); + grid_puts(&msg_grid_adj, s, Rows - 1, 0, attr); if (full) { - grid_puts(&default_grid, (char_u *) + grid_puts(&msg_grid_adj, (char_u *) _(" SPACE/d/j: screen/page/line down, b/u/k: up, q: quit "), - (int)Rows - 1, vim_strsize(s), attr); + Rows - 1, vim_strsize(s), attr); } } @@ -2685,12 +2860,24 @@ void msg_clr_eos_force(void) return; } int msg_startcol = (cmdmsg_rl) ? 0 : msg_col; - int msg_endcol = (cmdmsg_rl) ? msg_col + 1 : (int)Columns; + int msg_endcol = (cmdmsg_rl) ? msg_col + 1 : Columns; + + if (msg_grid.chars && msg_row < msg_grid_pos) { + // TODO(bfredl): ugly, this state should already been validated at this + // point. But msg_clr_eos() is called in a lot of places. + msg_row = msg_grid_pos; + } + + grid_fill(&msg_grid_adj, msg_row, msg_row + 1, msg_startcol, msg_endcol, ' ', + ' ', HL_ATTR(HLF_MSG)); + grid_fill(&msg_grid_adj, msg_row + 1, Rows, 0, Columns, ' ', ' ', + HL_ATTR(HLF_MSG)); - grid_fill(&default_grid, msg_row, msg_row + 1, msg_startcol, msg_endcol, ' ', - ' ', 0); - grid_fill(&default_grid, msg_row + 1, (int)Rows, 0, (int)Columns, ' ', ' ', - 0); + redraw_cmdline = true; // overwritten the command line + if (msg_row < Rows-1 || msg_col == (cmdmsg_rl ? Columns : 0)) { + clear_cmdline = false; // command line has been cleared + mode_displayed = false; // mode cleared or overwritten + } } /* @@ -2724,7 +2911,8 @@ int msg_end(void) // @TODO(bfredl): calling flush here inhibits substantial performance // improvements. Caller should call ui_flush before waiting on user input or // CPU busywork. - ui_flush(); // calls msg_ext_ui_flush + // ui_flush(); // calls msg_ext_ui_flush + msg_ext_ui_flush(); return true; } diff --git a/src/nvim/message.h b/src/nvim/message.h index 914c10af43..1703384bb5 100644 --- a/src/nvim/message.h +++ b/src/nvim/message.h @@ -7,6 +7,7 @@ #include "nvim/macros.h" #include "nvim/types.h" +#include "nvim/grid_defs.h" /* * Types of dialogs passed to do_dialog(). @@ -90,6 +91,13 @@ extern MessageHistoryEntry *last_msg_hist; EXTERN bool msg_ext_need_clear INIT(= false); +EXTERN ScreenGrid msg_grid INIT(= SCREEN_GRID_INIT); +EXTERN ScreenGrid msg_grid_adj INIT(= SCREEN_GRID_INIT); + +EXTERN int msg_scroll_at_flush INIT(= 0); + +EXTERN int msg_grid_pos INIT(= 0); + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "message.h.generated.h" #endif diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index 6d1a517ce8..d0aa0653cb 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -440,8 +440,11 @@ win_T *mouse_find_win(int *gridp, int *rowp, int *colp) win_T *wp_grid = mouse_find_grid_win(gridp, rowp, colp); if (wp_grid) { return wp_grid; + } else if (*gridp > 1) { + return NULL; } + frame_T *fp; fp = topframe; @@ -475,7 +478,10 @@ win_T *mouse_find_win(int *gridp, int *rowp, int *colp) static win_T *mouse_find_grid_win(int *gridp, int *rowp, int *colp) { - if (*gridp > 1) { + if (*gridp == msg_grid.handle) { + rowp += msg_grid_pos; + *gridp = DEFAULT_GRID_HANDLE; + } else if (*gridp > 1) { win_T *wp = get_win_by_grid_handle(*gridp); if (wp && wp->w_grid.chars && !(wp->w_floating && !wp->w_float_config.focusable)) { @@ -486,7 +492,7 @@ static win_T *mouse_find_grid_win(int *gridp, int *rowp, int *colp) } else if (*gridp == 0) { ScreenGrid *grid = ui_comp_mouse_focus(*rowp, *colp); FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (&wp->w_grid != grid || !wp->w_float_config.focusable) { + if (&wp->w_grid != grid) { continue; } *gridp = grid->handle; diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 819ca83d27..e6a4c38c59 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -3455,16 +3455,18 @@ static void display_showcmd(void) return; } + msg_grid_validate(); int showcmd_row = Rows - 1; - grid_puts_line_start(&default_grid, showcmd_row); + grid_puts_line_start(&msg_grid_adj, showcmd_row); if (!showcmd_is_clear) { - grid_puts(&default_grid, showcmd_buf, showcmd_row, sc_col, 0); + grid_puts(&msg_grid_adj, showcmd_buf, showcmd_row, sc_col, + HL_ATTR(HLF_MSG)); } // clear the rest of an old message by outputting up to SHOWCMD_COLS spaces - grid_puts(&default_grid, (char_u *)" " + len, showcmd_row, - sc_col + len, 0); + grid_puts(&msg_grid_adj, (char_u *)" " + len, showcmd_row, + sc_col + len, HL_ATTR(HLF_MSG)); grid_puts_line_flush(false); } diff --git a/src/nvim/option.c b/src/nvim/option.c index fae4507f1e..04349414a2 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -2989,6 +2989,7 @@ ambw_end: errmsg = e_invarg; } else { (void)init_chartab(); + msg_grid_validate(); } } else if (varp == &p_ead) { // 'eadirection' if (check_opt_strings(p_ead, p_ead_values, false) != OK) { diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index fa6ebc70e5..f9cddf5550 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -399,6 +399,7 @@ static char *(p_dy_values[]) = { "lastline", "truncate", "uhex", "msgsep", #define DY_LASTLINE 0x001 #define DY_TRUNCATE 0x002 #define DY_UHEX 0x004 +// code should use msg_use_msgsep() to check if msgsep is active #define DY_MSGSEP 0x008 EXTERN int p_ed; // 'edcompatible' EXTERN int p_emoji; // 'emoji' diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 1b2143c419..e182c62c71 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -152,6 +152,7 @@ static bool send_grid_resize = false; static bool conceal_cursor_used = false; static bool redraw_popupmenu = false; +static bool msg_grid_invalid = false; static bool resizing = false; @@ -318,27 +319,37 @@ int update_screen(int type) // 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; + msg_scroll_at_flush = 0; + } + + if (type >= CLEAR || !default_grid.valid) { + ui_comp_set_screen_valid(false); } // if the screen was scrolled up when displaying a message, scroll it down - if (msg_scrolled) { + if (msg_scrolled || msg_grid_invalid) { clear_cmdline = true; - if (dy_flags & DY_MSGSEP) { - int valid = MAX(Rows - msg_scrollsize(), 0); - if (valid == 0) { - redraw_tabline = true; - } - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (W_ENDROW(wp) > valid) { - wp->w_redr_type = NOT_VALID; - wp->w_lines_valid = 0; - } - if (W_ENDROW(wp) + wp->w_status_height > valid) { - wp->w_redr_status = true; + int valid = MAX(Rows - msg_scrollsize(), 0); + if (msg_grid.chars) { + // non-displayed part of msg_grid is considered invalid. + for (int i = 0; i < MIN(msg_scrollsize(), msg_grid.Rows); i++) { + grid_clear_line(&msg_grid, msg_grid.line_offset[i], + (int)msg_grid.Columns, false); + } + } + if (msg_use_msgsep()) { + msg_grid.throttled = false; + // CLEAR is already handled + if (type == NOT_VALID && !ui_has(kUIMultigrid) && msg_scrolled) { + ui_comp_set_screen_valid(false); + for (int i = valid; i < Rows-p_ch; i++) { + grid_clear_line(&default_grid, default_grid.line_offset[i], + Columns, false); } } + msg_grid_set_pos(Rows-p_ch, false); + msg_grid_invalid = false; } else if (msg_scrolled > Rows - 5) { // clearing is faster type = CLEAR; } else if (type != CLEAR) { @@ -368,12 +379,10 @@ int update_screen(int type) redraw_tabline = TRUE; } msg_scrolled = 0; - need_wait_return = FALSE; + msg_scroll_at_flush = 0; + need_wait_return = false; } - if (type >= CLEAR || !default_grid.valid) { - ui_comp_set_screen_valid(false); - } win_ui_flush_positions(); msg_ext_check_clear(); @@ -394,6 +403,11 @@ int update_screen(int type) grid_invalidate(&default_grid); default_grid.valid = true; } + + if (type == NOT_VALID && msg_dothrottle()) { + grid_fill(&default_grid, Rows-p_ch, Rows, 0, Columns, ' ', ' ', 0); + } + ui_comp_set_screen_valid(true); if (clear_cmdline) /* going to clear cmdline (done below) */ @@ -4310,9 +4324,13 @@ win_line ( void screen_adjust_grid(ScreenGrid **grid, int *row_off, int *col_off) { if (!(*grid)->chars && *grid != &default_grid) { - *row_off += (*grid)->row_offset; - *col_off += (*grid)->col_offset; - *grid = &default_grid; + *row_off += (*grid)->row_offset; + *col_off += (*grid)->col_offset; + if (*grid == &msg_grid_adj && msg_grid.chars) { + *grid = &msg_grid; + } else { + *grid = &default_grid; + } } } @@ -4799,7 +4817,7 @@ win_redr_status_matches ( /* Put the wildmenu just above the command line. If there is * no room, scroll the screen one line up. */ if (cmdline_row == Rows - 1) { - msg_scroll_up(); + msg_scroll_up(false); msg_scrolled++; } else { cmdline_row++; @@ -4821,13 +4839,18 @@ win_redr_status_matches ( } } - grid_puts(&default_grid, buf, row, 0, attr); + // Tricky: wildmenu can be drawn either over a status line, or at empty + // scrolled space in the message output + ScreenGrid *grid = (wild_menu_showing == WM_SCROLLED) + ? &msg_grid_adj : &default_grid; + + grid_puts(grid, buf, row, 0, attr); if (selstart != NULL && highlight) { *selend = NUL; - grid_puts(&default_grid, selstart, row, selstart_col, HL_ATTR(HLF_WM)); + grid_puts(grid, selstart, row, selstart_col, HL_ATTR(HLF_WM)); } - grid_fill(&default_grid, row, row + 1, clen, Columns, + grid_fill(grid, row, row + 1, clen, Columns, fillchar, fillchar, attr); } @@ -5350,6 +5373,8 @@ static int put_dirty_last = 0; /// another line. void grid_puts_line_start(ScreenGrid *grid, int row) { + int col = 0; // unused + screen_adjust_grid(&grid, &row, &col); assert(put_dirty_row == -1); put_dirty_row = row; put_dirty_grid = grid; @@ -5379,7 +5404,7 @@ void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, screen_adjust_grid(&grid, &row, &col); // Safety check. The check for negative row and column is to fix issue - // vim/vim#4102. TODO: find out why row/col could be negative. + // vim/vim#4102. TODO(neovim): find out why row/col could be negative. if (grid->chars == NULL || row >= grid->Rows || row < 0 || col >= grid->Columns || col < 0) { @@ -5511,8 +5536,14 @@ void grid_puts_line_flush(bool set_cursor) ui_grid_cursor_goto(put_dirty_grid->handle, put_dirty_row, MIN(put_dirty_last, put_dirty_grid->Columns-1)); } - ui_line(put_dirty_grid, put_dirty_row, put_dirty_first, put_dirty_last, - put_dirty_last, 0, false); + if (!put_dirty_grid->throttled) { + ui_line(put_dirty_grid, put_dirty_row, put_dirty_first, put_dirty_last, + put_dirty_last, 0, false); + } else if (put_dirty_grid->dirty_col) { + if (put_dirty_last > put_dirty_grid->dirty_col[put_dirty_row]) { + put_dirty_grid->dirty_col[put_dirty_row] = put_dirty_last; + } + } put_dirty_first = INT_MAX; put_dirty_last = 0; } @@ -5886,6 +5917,18 @@ void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, if (put_dirty_row == row) { put_dirty_first = MIN(put_dirty_first, dirty_first); put_dirty_last = MAX(put_dirty_last, dirty_last); + } else if (grid->throttled) { + // Note: assumes msg_grid is the only throttled grid + assert(grid == &msg_grid); + int dirty = 0; + if (attr != HL_ATTR(HLF_MSG) || c2 != ' ') { + dirty = dirty_last; + } else if (c1 != ' ') { + dirty = dirty_first + 1; + } + if (grid->dirty_col && dirty > grid->dirty_col[row]) { + grid->dirty_col[row] = dirty; + } } else { int last = c2 != ' ' ? dirty_last : dirty_first + (c1 != ' '); ui_line(grid, row, dirty_first, last, dirty_last, attr, false); @@ -5895,19 +5938,6 @@ void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, if (end_col == grid->Columns) { grid->line_wraps[row] = false; } - - // TODO(bfredl): The relevant caller should do this - if (row == Rows - 1 && !ui_has(kUIMessages)) { - // overwritten the command line - redraw_cmdline = true; - if (start_col == 0 && end_col == Columns - && c1 == ' ' && c2 == ' ' && attr == 0) { - clear_cmdline = false; // command line has been cleared - } - if (start_col == 0) { - mode_displayed = false; // mode cleared or overwritten - } - } } } @@ -6039,6 +6069,9 @@ retry: // win_new_shellsize will recompute floats position, but tell the // compositor to not redraw them yet ui_comp_set_screen_valid(false); + if (msg_grid.chars) { + msg_grid_invalid = true; + } win_new_shellsize(); /* fit the windows in the new sized shell */ @@ -6217,12 +6250,17 @@ void screenclear(void) msg_scrolled = 0; // can't scroll back msg_didany = false; msg_didout = false; + if (HL_ATTR(HLF_MSG) > 0 && msg_dothrottle() && msg_grid.chars) { + grid_invalidate(&msg_grid); + msg_grid_validate(); + msg_grid_invalid = false; + clear_cmdline = true; + } } /// clear a line in the grid starting at "off" until "width" characters /// are cleared. -static void grid_clear_line(ScreenGrid *grid, unsigned off, int width, - bool valid) +void grid_clear_line(ScreenGrid *grid, unsigned off, int width, bool valid) { for (int col = 0; col < width; col++) { schar_from_ascii(grid->chars[off + col], ' '); @@ -6361,7 +6399,9 @@ void grid_ins_lines(ScreenGrid *grid, int row, int line_count, int end, int col, } } - ui_call_grid_scroll(grid->handle, row, end, col, col+width, -line_count, 0); + if (!grid->throttled) { + ui_call_grid_scroll(grid->handle, row, end, col, col+width, -line_count, 0); + } return; } @@ -6412,7 +6452,9 @@ void grid_del_lines(ScreenGrid *grid, int row, int line_count, int end, int col, } } - ui_call_grid_scroll(grid->handle, row, end, col, col+width, line_count, 0); + if (!grid->throttled) { + ui_call_grid_scroll(grid->handle, row, end, col, col+width, line_count, 0); + } return; } @@ -6440,6 +6482,8 @@ int showmode(void) // don't make non-flushed message part of the showmode msg_ext_ui_flush(); + msg_grid_validate(); + do_mode = ((p_smd && msg_silent == 0) && ((State & TERM_FOCUS) || (State & INSERT) @@ -7094,13 +7138,11 @@ static void win_redr_ruler(win_T *wp, int always) } } - grid_puts(&default_grid, buffer, row, this_ru_col + off, attr); - i = redraw_cmdline; - grid_fill(&default_grid, row, row + 1, + ScreenGrid *grid = part_of_status ? &default_grid : &msg_grid_adj; + grid_puts(grid, buffer, row, this_ru_col + off, attr); + grid_fill(grid, row, row + 1, this_ru_col + off + (int)STRLEN(buffer), off + width, fillchar, fillchar, attr); - // don't redraw the cmdline because of showing the ruler - redraw_cmdline = i; } wp->w_ru_cursor = wp->w_cursor; @@ -7214,6 +7256,12 @@ void screen_resize(int width, int height) if (State == ASKMORE || State == EXTERNCMD || State == CONFIRM || exmode_active) { screenalloc(); + if (msg_grid.chars) { + msg_grid_validate(); + } + // TODO(bfredl): sometimes messes up the output. Implement clear+redraw + // also for the pager? (or: what if the pager was just a modal window?) + ui_comp_set_screen_valid(true); repeat_message(); } else { if (curwin->w_p_scb) diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index 02b72be0ae..4b9e84745a 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -7533,6 +7533,9 @@ void highlight_changed(void) hlf == (int)HLF_INACTIVE); if (highlight_attr[hlf] != highlight_attr_last[hlf]) { + if (hlf == HLF_MSG) { + clear_cmdline = true; + } ui_call_hl_group_set(cstr_as_string((char *)hlf_names[hlf]), highlight_attr[hlf]); highlight_attr_last[hlf] = highlight_attr[hlf]; diff --git a/src/nvim/ui.c b/src/nvim/ui.c index 5d191314ba..94fae0a774 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -333,6 +333,7 @@ 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) { + assert(0 <= row && row < grid->Rows); LineFlags flags = wrap ? kLineFlagWrap : 0; if (startcol == -1) { startcol = 0; @@ -404,6 +405,7 @@ void ui_flush(void) cmdline_ui_flush(); win_ui_flush_positions(); msg_ext_ui_flush(); + msg_scroll_flush(); if (pending_cursor_update) { ui_call_grid_cursor_goto(cursor_grid_handle, cursor_row, cursor_col); diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c index 9517b362af..163eadbc95 100644 --- a/src/nvim/ui_compositor.c +++ b/src/nvim/ui_compositor.c @@ -19,6 +19,7 @@ #include "nvim/ui.h" #include "nvim/highlight.h" #include "nvim/memory.h" +#include "nvim/message.h" #include "nvim/popupmnu.h" #include "nvim/ui_compositor.h" #include "nvim/ugrid.h" @@ -46,8 +47,11 @@ static int chk_width = 0, chk_height = 0; static ScreenGrid *curgrid; static bool valid_screen = true; -static bool msg_scroll_mode = false; -static int msg_first_invalid = 0; +static int msg_current_row = INT_MAX; +static bool msg_was_scrolled = false; + +static int msg_sep_row = -1; +static schar_T msg_sep_char = { ' ', NUL }; static int dbghl_normal, dbghl_clear, dbghl_composed, dbghl_recompose; @@ -63,8 +67,7 @@ void ui_comp_init(void) 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; + compositor->msg_set_pos = ui_comp_msg_set_pos; // Be unopinionated: will be attached together with a "real" ui anyway compositor->width = INT_MAX; @@ -158,8 +161,19 @@ bool ui_comp_put_grid(ScreenGrid *grid, int row, int col, int height, int width, } #endif + // TODO(bfredl): this is pretty ad-hoc, add a proper z-order/priority + // scheme. For now: + // - msg_grid is always on top. + // - pum_grid is on top of all windows but not msg_grid. Except for when + // wildoptions=pum, and completing the cmdline with scrolled messages, + // then the pum has to be drawn over the scrolled messages. size_t insert_at = kv_size(layers); - if (kv_A(layers, insert_at-1) == &pum_grid) { + bool cmd_completion = (grid == &pum_grid && (State & CMDLINE) + && (wop_flags & WOP_PUM)); + if (kv_A(layers, insert_at-1) == &msg_grid && !cmd_completion) { + insert_at--; + } + if (kv_A(layers, insert_at-1) == &pum_grid && (grid != &msg_grid)) { insert_at--; } if (insert_at > 1 && !on_top) { @@ -280,10 +294,10 @@ static void ui_comp_grid_cursor_goto(UI *ui, Integer grid_handle, ScreenGrid *ui_comp_mouse_focus(int row, int col) { - // TODO(bfredl): click "through" unfocusable grids? for (ssize_t i = (ssize_t)kv_size(layers)-1; i > 0; i--) { ScreenGrid *grid = kv_A(layers, i); - if (row >= grid->comp_row && row < grid->comp_row+grid->Rows + if (grid->focusable + && row >= grid->comp_row && row < grid->comp_row+grid->Rows && col >= grid->comp_col && col < grid->comp_col+grid->Columns) { return grid; } @@ -337,10 +351,28 @@ static void compose_line(Integer row, Integer startcol, Integer endcol, 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)); + + if (row == msg_sep_row && grid->comp_index <= msg_grid.comp_index) { + grid = &msg_grid; + sattr_T msg_sep_attr = (sattr_T)HL_ATTR(HLF_MSGSEP); + for (int i = col; i < until; i++) { + memcpy(linebuf[i-startcol], msg_sep_char, sizeof(*linebuf)); + attrbuf[i-startcol] = msg_sep_attr; + } + } else { + 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)); + 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) { + skipstart = 0; + } + } + } // 'pumblend' and 'winblend' if (grid->blending) { @@ -375,14 +407,6 @@ static void compose_line(Integer row, Integer startcol, Integer endcol, } else if (n > 1 && linebuf[col-startcol+1][0] == NUL) { skipstart = 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) { - skipstart = 0; - } - } col = until; } @@ -500,9 +524,12 @@ static void ui_comp_raw_line(UI *ui, Integer grid, Integer row, endcol = MIN(endcol, clearcol); } - if (flags & kLineFlagInvalid - || kv_size(layers) > curgrid->comp_index+1 - || curgrid->blending) { + bool above_msg = (kv_A(layers, kv_size(layers)-1) == &msg_grid + && row < msg_current_row-(msg_was_scrolled?1:0)); + bool covered = kv_size(layers)-(above_msg?1:0) > curgrid->comp_index+1; + // TODO(bfredl): eventually should just fix compose_line to respect clearing + // and optimize it for uncovered lines. + if (flags & kLineFlagInvalid || covered || curgrid->blending) { compose_debug(row, row+1, startcol, clearcol, dbghl_composed, true); compose_line(row, startcol, clearcol, flags); } else { @@ -519,27 +546,44 @@ static void ui_comp_raw_line(UI *ui, Integer grid, Integer row, void ui_comp_set_screen_valid(bool valid) { valid_screen = valid; + if (!valid) { + msg_sep_row = -1; + } } -// 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) +static void ui_comp_msg_set_pos(UI *ui, Integer grid, Integer row, + Boolean scrolled, String sep_char) { - msg_scroll_mode = true; - msg_first_invalid = ui->height; -} + msg_grid.comp_row = (int)row; + if (scrolled && row > 0) { + msg_sep_row = (int)row-1; + if (sep_char.data) { + STRLCPY(msg_sep_char, sep_char.data, sizeof(msg_sep_char)); + } + } else { + msg_sep_row = -1; + } -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); + if (row > msg_current_row && ui_comp_should_draw()) { + compose_area(MAX(msg_current_row-1, 0), row, 0, default_grid.Columns); + } else if (row < msg_current_row && ui_comp_should_draw() + && msg_current_row < Rows) { + int delta = msg_current_row - (int)row; + if (msg_grid.blending) { + int first_row = MAX((int)row-(scrolled?1:0), 0); + compose_area(first_row, Rows-delta, 0, Columns); + } else { + // scroll separator togheter with message text + int first_row = MAX((int)row-(msg_was_scrolled?1:0), 0); + ui_composed_call_grid_scroll(1, first_row, Rows, 0, Columns, delta, 0); + if (scrolled && !msg_was_scrolled && row > 0) { + compose_area(row-1, row, 0, Columns); + } } } + + msg_current_row = (int)row; + msg_was_scrolled = scrolled; } static void ui_comp_grid_scroll(UI *ui, Integer grid, Integer top, @@ -554,7 +598,8 @@ static void ui_comp_grid_scroll(UI *ui, Integer grid, Integer top, left += curgrid->comp_col; right += curgrid->comp_col; bool covered = kv_size(layers) > curgrid->comp_index+1 || curgrid->blending; - if (!msg_scroll_mode && covered) { + + if (covered) { // TODO(bfredl): // 1. check if rectangles actually overlap // 2. calulate subareas that can scroll. @@ -565,7 +610,6 @@ static void ui_comp_grid_scroll(UI *ui, Integer grid, Integer top, } 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); if (rdb_flags & RDB_COMPOSITOR) { debug_delay(2); diff --git a/src/nvim/window.c b/src/nvim/window.c index b5d34bbbdb..0e8cfcedf5 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -697,6 +697,7 @@ static void ui_ext_win_position(win_T *wp) ui_comp_put_grid(&wp->w_grid, comp_row, comp_col, wp->w_height, wp->w_width, valid, on_top); ui_check_cursor_grid(wp->w_grid.handle); + wp->w_grid.focusable = wp->w_float_config.focusable; if (!valid) { wp->w_grid.valid = false; redraw_win_later(wp, NOT_VALID); @@ -5359,6 +5360,9 @@ void win_drag_status_line(win_T *dragwin, int offset) } row = win_comp_pos(); grid_fill(&default_grid, row, cmdline_row, 0, Columns, ' ', ' ', 0); + if (msg_grid.chars) { + clear_cmdline = true; + } cmdline_row = row; p_ch = Rows - cmdline_row; if (p_ch < 1) |