diff options
author | Björn Linse <bjorn.linse@gmail.com> | 2021-02-22 16:08:24 +0100 |
---|---|---|
committer | Björn Linse <bjorn.linse@gmail.com> | 2021-03-22 23:18:40 +0100 |
commit | 243820ebd0d9df7664311c8bf79d879bf23eb742 (patch) | |
tree | 73bd78832f061f3e091ccb1468b8110c91f23835 /src | |
parent | e5cfc7f3a0257682410cbb6bb433688bccdfd54f (diff) | |
download | rneovim-243820ebd0d9df7664311c8bf79d879bf23eb742.tar.gz rneovim-243820ebd0d9df7664311c8bf79d879bf23eb742.tar.bz2 rneovim-243820ebd0d9df7664311c8bf79d879bf23eb742.zip |
floats: add borders (MS-DOS MODE)
Diffstat (limited to 'src')
-rw-r--r-- | src/nvim/api/private/helpers.c | 101 | ||||
-rw-r--r-- | src/nvim/api/vim.c | 19 | ||||
-rw-r--r-- | src/nvim/buffer_defs.h | 12 | ||||
-rw-r--r-- | src/nvim/eval.c | 2 | ||||
-rw-r--r-- | src/nvim/grid_defs.h | 2 | ||||
-rw-r--r-- | src/nvim/highlight.c | 11 | ||||
-rw-r--r-- | src/nvim/highlight_defs.h | 2 | ||||
-rw-r--r-- | src/nvim/mbyte.c | 3 | ||||
-rw-r--r-- | src/nvim/mouse.c | 8 | ||||
-rw-r--r-- | src/nvim/option.c | 4 | ||||
-rw-r--r-- | src/nvim/popupmnu.c | 1 | ||||
-rw-r--r-- | src/nvim/screen.c | 84 | ||||
-rw-r--r-- | src/nvim/syntax.c | 1 | ||||
-rw-r--r-- | src/nvim/window.c | 26 |
14 files changed, 250 insertions, 26 deletions
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 2e8491215f..d2b787a6f5 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -1645,6 +1645,20 @@ bool api_object_to_bool(Object obj, const char *what, } } +int object_to_hl_id(Object obj, const char *what, Error *err) +{ + if (obj.type == kObjectTypeString) { + String str = obj.data.string; + return str.size ? syn_check_group((char_u *)str.data, (int)str.size) : 0; + } else if (obj.type == kObjectTypeInteger) { + return (int)obj.data.integer; + } else { + api_set_error(err, kErrorTypeValidation, + "%s is not a valid highlight", what); + return 0; + } +} + HlMessage parse_hl_msg(Array chunks, Error *err) { HlMessage hl_msg = KV_INITIAL_VALUE; @@ -1768,6 +1782,91 @@ static bool parse_float_bufpos(Array bufpos, lpos_T *out) return true; } +static void parse_border_style(Object style, FloatConfig *fconfig, Error *err) +{ + struct { + const char *name; + schar_T chars[8]; + } defaults[] = { + { "double", { "╔", "═", "╗", "║", "╝", "═", "╚", "║" } }, + { "single", { "┌", "─", "┐", "│", "┘", "─", "└", "│" } }, + { NULL, { { NUL } } }, + }; + + schar_T *chars = fconfig->border_chars; + int *hl_ids = fconfig->border_hl_ids; + + fconfig->border = true; + + if (style.type == kObjectTypeArray) { + Array arr = style.data.array; + size_t size = arr.size; + if (!size || size > 8 || (size & (size-1))) { + api_set_error(err, kErrorTypeValidation, + "invalid number of border chars"); + return; + } + for (size_t i = 0; i < size; i++) { + Object iytem = arr.items[i]; + String string = NULL_STRING; + int hl_id = 0; + if (iytem.type == kObjectTypeArray) { + Array iarr = iytem.data.array; + if (!iarr.size || iarr.size > 2) { + api_set_error(err, kErrorTypeValidation, "invalid border char"); + return; + } + if (iarr.items[0].type != kObjectTypeString) { + api_set_error(err, kErrorTypeValidation, "invalid border char"); + return; + } + string = iarr.items[0].data.string; + if (iarr.size == 2) { + hl_id = object_to_hl_id(iarr.items[1], "border char highlight", err); + if (ERROR_SET(err)) { + return; + } + } + + } else if (iytem.type == kObjectTypeString) { + string = iytem.data.string; + } else { + api_set_error(err, kErrorTypeValidation, "invalid border char"); + return; + } + if (!string.size + || mb_string2cells_len((char_u *)string.data, string.size) != 1) { + api_set_error(err, kErrorTypeValidation, + "border chars must be one cell"); + } + size_t len = MIN(string.size, sizeof(*chars)-1); + memcpy(chars[i], string.data, len); + chars[i][len] = NUL; + hl_ids[i] = hl_id; + } + while (size < 8) { + memcpy(chars+size, chars, sizeof(*chars) * size); + memcpy(hl_ids+size, hl_ids, sizeof(*hl_ids) * size); + size <<= 1; + } + } else if (style.type == kObjectTypeString) { + String str = style.data.string; + if (str.size == 0 || strequal(str.data, "none")) { + fconfig->border = false; + return; + } + for (size_t i = 0; defaults[i].name; i++) { + if (strequal(str.data, defaults[i].name)) { + memcpy(chars, defaults[i].chars, sizeof(defaults[i].chars)); + memset(hl_ids, 0, 8 * sizeof(*hl_ids)); + return; + } + } + api_set_error(err, kErrorTypeValidation, + "invalid border style \"%s\"", str.data); + } +} + bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf, Error *err) { @@ -1890,7 +1989,7 @@ bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf, return false; } } else if (!strcmp(key, "border")) { - fconfig->border = api_object_to_bool(val, "border", false, err); + parse_border_style(val, fconfig, err); if (ERROR_SET(err)) { return false; } diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 7b5ed79032..9dde62f0ee 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1416,6 +1416,25 @@ void nvim_chan_send(Integer chan, String data, Error *err) /// end-of-buffer region is hidden by setting `eob` flag of /// 'fillchars' to a space char, and clearing the /// |EndOfBuffer| region in 'winhighlight'. +/// - `border`: style of (optional) window border. This can either be a string +/// or an array. the string values are: +/// - "none" No border. This is the default +/// - "single" a single line box +/// - "double" a double line box +/// If it is an array it should be an array of eight items or any divisor of +/// eight. The array will specifify the eight chars building up the border +/// in a clockwise fashion starting with the top-left corner. As, an +/// example, the double box style could be specified as: +/// [ "╔", "═" ,"╗", "║", "╝", "═", "╚", "║" ] +/// if the number of chars are less than eight, they will be repeated. Thus +/// an ASCII border could be specified as: +/// [ "/", "-", "\\", "|" ] +/// or all chars the same as: +/// [ "x" ] +/// By default `FloatBorder` highlight is used which links to `VertSplit` +/// when not defined. It could also be specified by character: +/// [ {"+", "MyCorner"}, {"x", "MyBorder"} ] +/// /// @param[out] err Error details, if any /// /// @return Window handle, or 0 on error diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index ae7b9739a6..e8038e7281 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -1079,6 +1079,10 @@ typedef struct { bool external; bool focusable; WinStyle style; + bool border; + schar_T border_chars[8]; + int border_hl_ids[8]; + int border_attr[8]; } FloatConfig; #define FLOAT_CONFIG_INIT ((FloatConfig){ .height = 0, .width = 0, \ @@ -1256,6 +1260,11 @@ struct window_S { int w_height_request; int w_width_request; + int w_border_adj; + // outer size of window grid, including border + int w_height_outer; + int w_width_outer; + /* * === start of cached values ==== */ @@ -1331,7 +1340,8 @@ struct window_S { // w_redr_type is REDRAW_TOP linenr_T w_redraw_top; // when != 0: first line needing redraw linenr_T w_redraw_bot; // when != 0: last line needing redraw - int w_redr_status; // if TRUE status line must be redrawn + bool w_redr_status; // if true status line must be redrawn + bool w_redr_border; // if true border must be redrawn // remember what is shown in the ruler for this window (if 'ruler' set) pos_T w_ru_cursor; // cursor position shown in ruler diff --git a/src/nvim/eval.c b/src/nvim/eval.c index f190ef14c4..e1fcbdce25 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -8501,7 +8501,7 @@ static bool tv_is_luafunc(typval_T *tv) int check_luafunc_name(const char *str, bool paren) { const char *p = str; - while (ASCII_ISALNUM(*p) || *p == '_' || *p == '.') { + while (ASCII_ISALNUM(*p) || *p == '_' || *p == '.' || *p == '\'') { p++; } if (*p != (paren ? '(' : NUL)) { diff --git a/src/nvim/grid_defs.h b/src/nvim/grid_defs.h index 8489872465..3b34af46e4 100644 --- a/src/nvim/grid_defs.h +++ b/src/nvim/grid_defs.h @@ -7,7 +7,7 @@ #include "nvim/types.h" -#define MAX_MCO 6 // maximum value for 'maxcombine' +#define MAX_MCO 6 // fixed value for 'maxcombine' // The characters and attributes drawn on grids. typedef char_u schar_T[(MAX_MCO+1) * 4 + 1]; diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c index 5163752e1f..f03382bea7 100644 --- a/src/nvim/highlight.c +++ b/src/nvim/highlight.c @@ -341,6 +341,17 @@ void update_window_hl(win_T *wp, bool invalid) } wp->w_hl_attrs[hlf] = attr; } + + if (wp->w_floating && wp->w_float_config.border) { + for (int i = 0; i < 8; i++) { + int attr = wp->w_hl_attrs[HLF_BORDER]; + if (wp->w_float_config.border_hl_ids[i]) { + attr = hl_get_ui_attr(HLF_BORDER, wp->w_float_config.border_hl_ids[i], + false); + } + wp->w_float_config.border_attr[i] = attr; + } + } } /// Gets HL_UNDERLINE highlight. diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h index 2bda094d8e..ed4aefb577 100644 --- a/src/nvim/highlight_defs.h +++ b/src/nvim/highlight_defs.h @@ -101,6 +101,7 @@ typedef enum { , HLF_MSGSEP // message separator line , HLF_NFLOAT // Floating window , HLF_MSG // Message area + , HLF_BORDER // Floating window border , HLF_COUNT // MUST be the last one } hlf_T; @@ -155,6 +156,7 @@ EXTERN const char *hlf_names[] INIT(= { [HLF_MSGSEP] = "MsgSeparator", [HLF_NFLOAT] = "NormalFloat", [HLF_MSG] = "MsgArea", + [HLF_BORDER] = "FloatBorder", }); diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index ec4f4cbc21..73e3ba53a5 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -571,11 +571,12 @@ size_t mb_string2cells(const char_u *str) /// @param size maximum length of string. It will terminate on earlier NUL. /// @return The number of cells occupied by string `str` size_t mb_string2cells_len(const char_u *str, size_t size) + FUNC_ATTR_NONNULL_ARG(1) { size_t clen = 0; for (const char_u *p = str; *p != NUL && p < str+size; - p += utf_ptr2len_len(p, size+(p-str))) { + p += utfc_ptr2len_len(p, size+(p-str))) { clen += utf_ptr2cells(p); } diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index e96757e471..fa9787a3ac 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -472,8 +472,8 @@ static win_T *mouse_find_grid_win(int *gridp, int *rowp, int *colp) win_T *wp = get_win_by_grid_handle(*gridp); if (wp && wp->w_grid_alloc.chars && !(wp->w_floating && !wp->w_float_config.focusable)) { - *rowp = MIN(*rowp, wp->w_grid.Rows-1); - *colp = MIN(*colp, wp->w_grid.Columns-1); + *rowp = MIN(*rowp-wp->w_grid.row_offset, wp->w_grid.Rows-1); + *colp = MIN(*colp-wp->w_grid.col_offset, wp->w_grid.Columns-1); return wp; } } else if (*gridp == 0) { @@ -483,8 +483,8 @@ static win_T *mouse_find_grid_win(int *gridp, int *rowp, int *colp) continue; } *gridp = grid->handle; - *rowp -= grid->comp_row; - *colp -= grid->comp_col; + *rowp -= grid->comp_row+wp->w_grid.row_offset; + *colp -= grid->comp_col+wp->w_grid.col_offset; return wp; } diff --git a/src/nvim/option.c b/src/nvim/option.c index dbd8ceb55c..d04329e104 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -4287,7 +4287,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, // 'floatblend' curwin->w_p_winbl = MAX(MIN(curwin->w_p_winbl, 100), 0); curwin->w_hl_needs_update = true; - curwin->w_grid.blending = curwin->w_p_winbl > 0; + curwin->w_grid_alloc.blending = curwin->w_p_winbl > 0; } @@ -5800,7 +5800,7 @@ void didset_window_options(win_T *wp) set_chars_option(wp, &wp->w_p_fcs, true); set_chars_option(wp, &wp->w_p_lcs, true); parse_winhl_opt(wp); // sets w_hl_needs_update also for w_p_winbl - wp->w_grid.blending = wp->w_p_winbl > 0; + wp->w_grid_alloc.blending = wp->w_p_winbl > 0; } diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c index 43c018bb86..69c614fff9 100644 --- a/src/nvim/popupmnu.c +++ b/src/nvim/popupmnu.c @@ -141,6 +141,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, pum_anchor_grid = (int)curwin->w_grid.target->handle; if (!ui_has(kUIMultigrid)) { + pum_anchor_grid = (int)default_grid.handle; pum_win_row += curwin->w_winrow; cursor_col += curwin->w_wincol; } diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 306db28923..d50520f49e 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -587,6 +587,13 @@ int update_screen(int type) wp->w_redr_type = NOT_VALID; } + // reallocate grid if needed. + win_grid_alloc(wp); + + if (wp->w_redr_border || wp->w_redr_type >= NOT_VALID) { + win_redr_border(wp); + } + if (wp->w_redr_type != 0) { if (!did_one) { did_one = TRUE; @@ -774,8 +781,6 @@ static void win_update(win_T *wp, Providers *providers) type = wp->w_redr_type; - win_grid_alloc(wp); - if (type >= NOT_VALID) { wp->w_redr_status = true; wp->w_lines_valid = 0; @@ -5411,6 +5416,46 @@ theend: entered = FALSE; } +static void win_redr_border(win_T *wp) +{ + wp->w_redr_border = false; + if (!(wp->w_floating && wp->w_float_config.border)) { + return; + } + + ScreenGrid *grid = &wp->w_grid_alloc; + + schar_T *chars = wp->w_float_config.border_chars; + int *attrs = wp->w_float_config.border_attr; + + int endrow = grid->Rows-1, endcol = grid->Columns-1; + + grid_puts_line_start(grid, 0); + grid_put_schar(grid, 0, 0, chars[0], attrs[0]); + for (int i = 1; i < endcol; i++) { + grid_put_schar(grid, 0, i, chars[1], attrs[1]); + } + grid_put_schar(grid, 0, endcol, chars[2], attrs[2]); + grid_puts_line_flush(false); + + for (int i = 1; i < endrow; i++) { + grid_puts_line_start(grid, i); + grid_put_schar(grid, i, 0, chars[7], attrs[7]); + grid_puts_line_flush(false); + grid_puts_line_start(grid, i); + grid_put_schar(grid, i, endcol, chars[3], attrs[3]); + grid_puts_line_flush(false); + } + + grid_puts_line_start(grid, endrow); + grid_put_schar(grid, endrow, 0, chars[6], attrs[6]); + for (int i = 1; i < endcol; i++) { + grid_put_schar(grid, endrow, i, chars[5], attrs[5]); + } + grid_put_schar(grid, endrow, endcol, chars[4], attrs[4]); + grid_puts_line_flush(false); +} + // Low-level functions to manipulate invidual character cells on the // screen grid. @@ -5548,6 +5593,20 @@ void grid_puts_line_start(ScreenGrid *grid, int row) put_dirty_grid = grid; } +void grid_put_schar(ScreenGrid *grid, int row, int col, char_u *schar, int attr) +{ + assert(put_dirty_row == row); + unsigned int off = grid->line_offset[row] + col; + if (grid->attrs[off] != attr || schar_cmp(grid->chars[off], schar)) { + schar_copy(grid->chars[off], schar); + grid->attrs[off] = attr; + + put_dirty_first = MIN(put_dirty_first, col); + // TODO(bfredl): Y U NO DOUBLEWIDTH? + put_dirty_last = MAX(put_dirty_last, col+1); + } +} + /// like grid_puts(), but output "text[len]". When "len" is -1 output up to /// a NUL. void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, @@ -6143,6 +6202,8 @@ void win_grid_alloc(win_T *wp) int rows = wp->w_height_inner; int cols = wp->w_width_inner; + int total_rows = wp->w_height_outer; + int total_cols = wp->w_width_outer; bool want_allocation = ui_has(kUIMultigrid) || wp->w_floating; bool has_allocation = (grid_allocated->chars != NULL); @@ -6153,14 +6214,16 @@ void win_grid_alloc(win_T *wp) wp->w_lines = xcalloc(rows+1, sizeof(wline_T)); } - int total_rows = rows, total_cols = cols; - int was_resized = false; if (want_allocation && (!has_allocation - || grid_allocated->Rows != total_rows - || grid_allocated->Columns != total_cols)) { - grid_alloc(grid_allocated, total_rows, total_cols, wp->w_grid_alloc.valid, false); + || grid_allocated->Rows != total_rows + || grid_allocated->Columns != total_cols)) { + grid_alloc(grid_allocated, total_rows, total_cols, + wp->w_grid_alloc.valid, false); grid_allocated->valid = true; + if (wp->w_border_adj) { + wp->w_redr_border = true; + } was_resized = true; } else if (!want_allocation && has_allocation) { // Single grid mode, all rendering will be redirected to default_grid. @@ -6178,8 +6241,8 @@ void win_grid_alloc(win_T *wp) if (want_allocation) { grid->target = grid_allocated; - grid->row_offset = 0; - grid->col_offset = 0; + grid->row_offset = wp->w_border_adj; + grid->col_offset = wp->w_border_adj; } else { grid->target = &default_grid; grid->row_offset = wp->w_winrow; @@ -6191,7 +6254,8 @@ void win_grid_alloc(win_T *wp) // - screen_resize was called and all grid sizes must be sent // - the UI wants multigrid event (necessary) if ((send_grid_resize || was_resized) && want_allocation) { - ui_call_grid_resize(grid_allocated->handle, grid_allocated->Columns, grid_allocated->Rows); + ui_call_grid_resize(grid_allocated->handle, + grid_allocated->Columns, grid_allocated->Rows); } } diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index d204968c0f..f1eb7879b0 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -6046,6 +6046,7 @@ static const char *highlight_init_both[] = { "default link Whitespace NonText", "default link MsgSeparator StatusLine", "default link NormalFloat Pmenu", + "default link FloatBorder VertSplit", "RedrawDebugNormal cterm=reverse gui=reverse", "RedrawDebugClear ctermbg=Yellow guibg=Yellow", "RedrawDebugComposed ctermbg=Green guibg=Green", diff --git a/src/nvim/window.c b/src/nvim/window.c index 2511c5c95b..fd7af108b7 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -605,6 +605,7 @@ win_T *win_new_float(win_T *wp, FloatConfig fconfig, Error *err) wp->w_vsep_width = 0; win_config_float(wp, fconfig); + win_set_inner_size(wp); wp->w_pos_changed = true; redraw_later(wp, VALID); return wp; @@ -667,6 +668,8 @@ void win_config_float(win_T *wp, FloatConfig fconfig) } bool change_external = fconfig.external != wp->w_float_config.external; + bool change_border = fconfig.border != wp->w_float_config.border; + wp->w_float_config = fconfig; if (!ui_has(kUIMultigrid)) { @@ -676,11 +679,18 @@ void win_config_float(win_T *wp, FloatConfig fconfig) win_set_inner_size(wp); must_redraw = MAX(must_redraw, VALID); + wp->w_pos_changed = true; - if (change_external) { + if (change_external || change_border) { wp->w_hl_needs_update = true; redraw_later(wp, NOT_VALID); } + + // changing border style while keeping border only requires redrawing border + if (fconfig.border) { + wp->w_redr_border = true; + redraw_later(wp, VALID); + } } void win_check_anchored_floats(win_T *win) @@ -743,8 +753,8 @@ void ui_ext_win_position(win_T *wp) } if (ui_has(kUIMultigrid)) { String anchor = cstr_to_string(float_anchor_str[c.anchor]); - ui_call_win_float_pos(wp->w_grid_alloc.handle, wp->handle, anchor, grid->handle, - row, col, c.focusable); + ui_call_win_float_pos(wp->w_grid_alloc.handle, wp->handle, anchor, + grid->handle, row, col, c.focusable); } else { // TODO(bfredl): ideally, compositor should work like any multigrid UI // and use standard win_pos events. @@ -759,8 +769,8 @@ void ui_ext_win_position(win_T *wp) wp->w_wincol = comp_col; bool valid = (wp->w_redr_type == 0); bool on_top = (curwin == wp) || !curwin->w_floating; - ui_comp_put_grid(&wp->w_grid_alloc, comp_row, comp_col, wp->w_height, - wp->w_width, valid, on_top); + ui_comp_put_grid(&wp->w_grid_alloc, comp_row, comp_col, + wp->w_height_outer, wp->w_width_outer, valid, on_top); ui_check_cursor_grid(wp->w_grid_alloc.handle); wp->w_grid_alloc.focusable = wp->w_float_config.focusable; if (!valid) { @@ -3515,9 +3525,11 @@ void win_init_size(void) { firstwin->w_height = ROWS_AVAIL; firstwin->w_height_inner = firstwin->w_height; + firstwin->w_height_outer = firstwin->w_height; topframe->fr_height = ROWS_AVAIL; firstwin->w_width = Columns; firstwin->w_width_inner = firstwin->w_width; + firstwin->w_width_outer = firstwin->w_width; topframe->fr_width = Columns; } @@ -5714,6 +5726,10 @@ void win_set_inner_size(win_T *wp) if (wp->w_buffer->terminal) { terminal_check_size(wp->w_buffer->terminal); } + + wp->w_border_adj = wp->w_floating && wp->w_float_config.border ? 1 : 0; + wp->w_height_outer = wp->w_height_inner + 2 * wp->w_border_adj; + wp->w_width_outer = wp->w_width_inner + 2 * wp->w_border_adj; } /// Set the width of a window. |