From ef3e32d57ee0ebe3f425998216d8bf61ffdc28bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Linse?= Date: Thu, 30 May 2019 17:46:41 +0200 Subject: [PATCH] api/window: add style="minimal" flag to nvim_open_win() --- runtime/doc/api.txt | 8 +- src/nvim/api/vim.c | 18 ++++ src/nvim/api/window.c | 5 ++ src/nvim/buffer.c | 5 ++ src/nvim/buffer_defs.h | 14 ++- src/nvim/highlight.c | 21 ++--- src/nvim/option.c | 3 +- src/nvim/screen.c | 3 +- src/nvim/window.c | 48 +++++++++- test/functional/ui/float_spec.lua | 142 ++++++++++++++++++++++++++++-- 10 files changed, 237 insertions(+), 30 deletions(-) diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 065a052175..709e5885e4 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -284,14 +284,18 @@ highlighting, or |api-highlights|. By default, floats will use |hl-NormalFloat| as normal highlight, which links to |hl-Pmenu| in the builtin color scheme. The 'winhighlight' option can be used to override it. Currently, floating windows don't support any visual -decorations like a border or additional widgets like scrollbar. +decorations like a border or additional widgets like scrollbar. By default, +floats will inherit options from the current window. This is not always +useful for some options, like 'number'. Use `style='minimal'` flag to +|nvim_open_win()| to disable many UI features that are unwanted for a simple +float, like end-of-buffer region or special columns. Here is an example for creating a float with scratch buffer: > let buf = nvim_create_buf(v:false, v:true) call nvim_buf_set_lines(buf, 0, -1, v:true, ["test", "text"]) let opts = {'relative': 'cursor', 'width': 10, 'height': 2, 'col': 0, - \ 'row': 1, 'anchor': 'NW'} + \ 'row': 1, 'anchor': 'NW', 'style': 'minimal'} let win = nvim_open_win(buf, 0, opts) " optional: change highlight, otherwise Pmenu is used call nvim_win_set_option(win, 'winhl', 'Normal:MyHighlight') diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 8e5650633a..dbe3b66fd5 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1064,6 +1064,19 @@ fail: /// - `external`: GUI should display the window as an external /// top-level window. Currently accepts no other positioning /// configuration together with this. +/// - `style`: Configure the apparance of the window. Currently only takes +/// one non-empty value: +/// - "minimal" Nvim will display the window with many UI options +/// disabled. This is useful when displaing a temporary +/// float where the text should not be edited. Disables +/// 'number', 'relativenumber', 'cursorline', 'cursorcolumn', +/// 'spell' and 'list' options. 'signcolumn' is changed to +/// `auto`. The end-of-buffer region is hidden by setting +/// `eob` flag of 'fillchars' to a space char, and clearing +/// the |EndOfBuffer| region in 'winhighlight'. +/// +/// top-level window. Currently accepts no other positioning +/// configuration together with this. /// @param[out] err Error details, if any /// /// @return Window handle, or 0 on error @@ -1085,6 +1098,11 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dictionary config, if (buffer > 0) { nvim_win_set_buf(wp->handle, buffer, err); } + + if (fconfig.style == kWinStyleMinimal) { + win_set_minimal_style(wp); + didset_window_options(wp); + } return wp->handle; } diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index 9fd1818a5c..4922dd7efc 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -13,6 +13,7 @@ #include "nvim/vim.h" #include "nvim/buffer.h" #include "nvim/cursor.h" +#include "nvim/option.h" #include "nvim/window.h" #include "nvim/screen.h" #include "nvim/move.h" @@ -475,6 +476,10 @@ void nvim_win_set_config(Window window, Dictionary config, Error *err) win_config_float(win, fconfig); win->w_pos_changed = true; } + if (fconfig.style == kWinStyleMinimal) { + win_set_minimal_style(win); + didset_window_options(win); + } } /// Return window configuration. diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 5678f518f5..cd31adbaff 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -2543,6 +2543,11 @@ void get_winopts(buf_T *buf) } else copy_winopt(&curwin->w_allbuf_opt, &curwin->w_onebuf_opt); + if (curwin->w_float_config.style == kWinStyleMinimal) { + didset_window_options(curwin); + win_set_minimal_style(curwin); + } + // Set 'foldlevel' to 'foldlevelstart' if it's not negative. if (p_fdls >= 0) { curwin->w_p_fdl = p_fdls; diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index ad10f6baa2..a27672488e 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -972,7 +972,6 @@ struct matchitem { }; typedef int FloatAnchor; -typedef int FloatRelative; enum { kFloatAnchorEast = 1, @@ -985,15 +984,20 @@ enum { // SE -> kFloatAnchorSouth | kFloatAnchorEast EXTERN const char *const float_anchor_str[] INIT(= { "NW", "NE", "SW", "SE" }); -enum { +typedef enum { kFloatRelativeEditor = 0, kFloatRelativeWindow = 1, kFloatRelativeCursor = 2, -}; +} FloatRelative; EXTERN const char *const float_relative_str[] INIT(= { "editor", "window", "cursor" }); +typedef enum { + kWinStyleUnused = 0, + kWinStyleMinimal, /// Minimal UI: no number column, eob markers, etc +} WinStyle; + typedef struct { Window window; int height, width; @@ -1002,12 +1006,14 @@ typedef struct { FloatRelative relative; bool external; bool focusable; + WinStyle style; } FloatConfig; #define FLOAT_CONFIG_INIT ((FloatConfig){ .height = 0, .width = 0, \ .row = 0, .col = 0, .anchor = 0, \ .relative = 0, .external = false, \ - .focusable = true }) + .focusable = true, \ + .style = kWinStyleUnused }) // Structure to store last cursor position and topline. Used by check_lnums() // and reset_lnums(). diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c index 15c3d0eb7b..e5cbb4f944 100644 --- a/src/nvim/highlight.c +++ b/src/nvim/highlight.c @@ -141,10 +141,12 @@ int hl_get_ui_attr(int idx, int final_id, bool optional) HlAttrs attrs = HLATTRS_INIT; bool available = false; - int syn_attr = syn_id2attr(final_id); - if (syn_attr != 0) { - attrs = syn_attr2entry(syn_attr); - available = true; + if (final_id > 0) { + int syn_attr = syn_id2attr(final_id); + if (syn_attr != 0) { + attrs = syn_attr2entry(syn_attr); + available = true; + } } if (HLF_PNI <= idx && idx <= HLF_PST) { @@ -176,15 +178,14 @@ void update_window_hl(win_T *wp, bool invalid) // determine window specific background set in 'winhighlight' bool float_win = wp->w_floating && !wp->w_float_config.external; - if (wp != curwin && wp->w_hl_ids[HLF_INACTIVE] > 0) { + if (wp != curwin && wp->w_hl_ids[HLF_INACTIVE] != 0) { wp->w_hl_attr_normal = hl_get_ui_attr(HLF_INACTIVE, wp->w_hl_ids[HLF_INACTIVE], !has_blend); - } else if (float_win && wp->w_hl_ids[HLF_NFLOAT] > 0) { + } else if (float_win && wp->w_hl_ids[HLF_NFLOAT] != 0) { wp->w_hl_attr_normal = hl_get_ui_attr(HLF_NFLOAT, - // 'cursorline' wp->w_hl_ids[HLF_NFLOAT], !has_blend); - } else if (wp->w_hl_id_normal > 0) { + } else if (wp->w_hl_id_normal != 0) { wp->w_hl_attr_normal = hl_get_ui_attr(-1, wp->w_hl_id_normal, !has_blend); } else { wp->w_hl_attr_normal = float_win ? HL_ATTR(HLF_NFLOAT) : 0; @@ -199,14 +200,14 @@ void update_window_hl(win_T *wp, bool invalid) } } - if (wp != curwin) { + if (wp != curwin && wp->w_hl_ids[HLF_INACTIVE] == 0) { wp->w_hl_attr_normal = hl_combine_attr(HL_ATTR(HLF_INACTIVE), wp->w_hl_attr_normal); } for (int hlf = 0; hlf < (int)HLF_COUNT; hlf++) { int attr; - if (wp->w_hl_ids[hlf] > 0) { + if (wp->w_hl_ids[hlf] != 0) { attr = hl_get_ui_attr(hlf, wp->w_hl_ids[hlf], false); } else { attr = HL_ATTR(hlf); diff --git a/src/nvim/option.c b/src/nvim/option.c index e8e246c277..2c96dae64e 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -3762,7 +3762,8 @@ static bool parse_winhl_opt(win_T *wp) size_t nlen = (size_t)(colon-p); char *hi = colon+1; char *commap = xstrchrnul(hi, ','); - int hl_id = syn_check_group((char_u *)hi, (int)(commap-hi)); + int len = (int)(commap-hi); + int hl_id = len ? syn_check_group((char_u *)hi, len) : -1; if (strncmp("Normal", p, nlen) == 0) { w_hl_id_normal = hl_id; diff --git a/src/nvim/screen.c b/src/nvim/screen.c index acff44164f..94887e113a 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -1612,7 +1612,8 @@ static void win_draw_end(win_T *wp, int c1, int c2, bool draw_margin, int row, } } - int attr = hl_combine_attr(wp->w_hl_attr_normal, win_hl_attr(wp, hl)); + int attr = hl_combine_attr(wp->w_hl_attr_normal, + hl ? win_hl_attr(wp, hl) : 0); if (wp->w_p_rl) { grid_fill(&wp->w_grid, row, endrow, wp->w_wincol, W_ENDCOL(wp) - 1 - n, diff --git a/src/nvim/window.c b/src/nvim/window.c index a3b1efeaaa..acaff8327d 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -584,15 +584,43 @@ win_T *win_new_float(win_T *wp, FloatConfig fconfig, Error *err) wp->w_status_height = 0; wp->w_vsep_width = 0; - // TODO(bfredl): use set_option_to() after merging #9110 ? - wp->w_p_nu = false; - wp->w_allbuf_opt.wo_nu = false; win_config_float(wp, fconfig); wp->w_pos_changed = true; redraw_win_later(wp, VALID); return wp; } +void win_set_minimal_style(win_T *wp) +{ + wp->w_p_nu = false; + wp->w_p_rnu = false; + wp->w_p_cul = false; + wp->w_p_cuc = false; + wp->w_p_spell = false; + wp->w_p_list = false; + + // Hide EOB region: use " " fillchar and cleared highlighting + if (wp->w_p_fcs_chars.eob != ' ') { + char_u *old = wp->w_p_fcs; + wp->w_p_fcs = ((*old == NUL) + ? (char_u *)xstrdup("eob: ") + : concat_str(old, (char_u *)",eob: ")); + xfree(old); + } + if (wp->w_hl_ids[HLF_EOB] != -1) { + char_u *old = wp->w_p_winhl; + wp->w_p_winhl = ((*old == NUL) + ? (char_u *)xstrdup("EndOfBuffer:") + : concat_str(old, (char_u *)",EndOfBuffer:")); + xfree(old); + } + + if (wp->w_p_scl[0] != 'a') { + xfree(wp->w_p_scl); + wp->w_p_scl = (char_u *)xstrdup("auto"); + } +} + void win_config_float(win_T *wp, FloatConfig fconfig) { wp->w_width = MAX(fconfig.width, 1); @@ -821,6 +849,20 @@ bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf, "'focusable' key must be Boolean"); return false; } + } else if (!strcmp(key, "style")) { + if (val.type != kObjectTypeString) { + api_set_error(err, kErrorTypeValidation, + "'style' key must be String"); + return false; + } + if (val.data.string.data[0] == NUL) { + fconfig->style = kWinStyleUnused; + } else if (striequal(val.data.string.data, "minimal")) { + fconfig->style = kWinStyleMinimal; + } else { + api_set_error(err, kErrorTypeValidation, + "Invalid value of 'style' key"); + } } else { api_set_error(err, kErrorTypeValidation, "Invalid key '%s'", key); diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index 41ba542899..b717ec1118 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -35,6 +35,10 @@ describe('floating windows', function() [15] = {background = Screen.colors.Grey20}, [16] = {background = Screen.colors.Grey20, bold = true, foreground = Screen.colors.Blue1}, [17] = {background = Screen.colors.Yellow}, + [18] = {foreground = Screen.colors.Brown, background = Screen.colors.Grey20}, + [19] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.WebGray}, + [20] = {bold = true, foreground = Screen.colors.Brown}, + [21] = {background = Screen.colors.Gray90}, } it('behavior', function() @@ -182,7 +186,7 @@ describe('floating windows', function() end end) - it('defaults to nonumber and NormalFloat highlight', function() + it('defaults to NormalFloat highlight and inherited options', function() command('set number') command('hi NormalFloat guibg=#333333') feed('ixygg') @@ -205,18 +209,18 @@ describe('floating windows', function() {0:~ }| {0:~ }| ## grid 3 - {15:x }| - {15:y }| - {15: }| + {18: 1 }{15:x }| + {18: 2 }{15:y }| + {18: 3 }{15: }| {16:~ }| ]], float_pos={[3] = {{id = 1001}, "NW", 1, 4, 10, true}}} else screen:expect([[ {14: 1 }^x | {14: 2 }y | - {14: 3 } {15:x } | - {0:~ }{15:y }{0: }| - {0:~ }{15: }{0: }| + {14: 3 } {18: 1 }{15:x } | + {0:~ }{18: 2 }{15:y }{0: }| + {0:~ }{18: 3 }{15: }{0: }| {0:~ }{16:~ }{0: }| | ]]) @@ -242,7 +246,7 @@ describe('floating windows', function() {0:~ }| {0:~ }| ## grid 3 - {15: }| + {18: 1 }{15: }| {16:~ }| {16:~ }| {16:~ }| @@ -251,7 +255,7 @@ describe('floating windows', function() screen:expect([[ {14: 1 }^x | {14: 2 }y | - {14: 3 } {15: } | + {14: 3 } {18: 1 }{15: } | {0:~ }{16:~ }{0: }| {0:~ }{16:~ }{0: }| {0:~ }{16:~ }{0: }| @@ -260,6 +264,126 @@ describe('floating windows', function() end end) + it("can use 'minimal' style", function() + command('set number') + command('set signcolumn=yes') + command('set cursorline') + command('hi NormalFloat guibg=#333333') + feed('ixygg') + local win = meths.open_win(0, false, {relative='editor', width=20, height=4, row=4, col=10, style='minimal'}) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + | + ## grid 2 + {19: }{20: 1 }{21:^x }| + {19: }{14: 2 }y | + {19: }{14: 3 } | + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {15:x }| + {15:y }| + {15: }| + {15: }| + ]], float_pos={[3] = {{id = 1001}, "NW", 1, 4, 10, true}}} + else + screen:expect([[ + {19: }{20: 1 }{21:^x }| + {19: }{14: 2 }y | + {19: }{14: 3 } {15:x } | + {0:~ }{15:y }{0: }| + {0:~ }{15: }{0: }| + {0:~ }{15: }{0: }| + | + ]]) + end + + -- signcolumn=yes still works if there actually are signs + command('sign define piet1 text=𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄ texthl=Search') + command('sign place 1 line=1 name=piet1 buffer=1') + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + | + ## grid 2 + {17:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{20: 1 }{21:^x }| + {19: }{14: 2 }y | + {19: }{14: 3 } | + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {17:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{15:x }| + {19: }{15:y }| + {19: }{15: }| + {15: }| + ]], float_pos={[3] = {{id = 1001}, "NW", 1, 4, 10, true}}} + + else + screen:expect([[ + {17:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{20: 1 }{21:^x }| + {19: }{14: 2 }y | + {19: }{14: 3 } {17:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{15:x } | + {0:~ }{19: }{15:y }{0: }| + {0:~ }{19: }{15: }{0: }| + {0:~ }{15: }{0: }| + | + ]]) + end + command('sign unplace 1 buffer=1') + + local buf = meths.create_buf(false, true) + meths.win_set_buf(win, buf) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + | + ## grid 2 + {19: }{20: 1 }{21:^x }| + {19: }{14: 2 }y | + {19: }{14: 3 } | + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {15: }| + {15: }| + {15: }| + {15: }| + ]], float_pos={[3] = {{id = 1001}, "NW", 1, 4, 10, true}}} + else + screen:expect([[ + {19: }{20: 1 }{21:^x }| + {19: }{14: 2 }y | + {19: }{14: 3 } {15: } | + {0:~ }{15: }{0: }| + {0:~ }{15: }{0: }| + {0:~ }{15: }{0: }| + | + ]]) + end + end) + it('can have minimum size', function() insert("the background text") local buf = meths.create_buf(false, true)