diff options
-rw-r--r-- | runtime/doc/api.txt | 156 | ||||
-rw-r--r-- | runtime/doc/syntax.txt | 2 | ||||
-rw-r--r-- | runtime/doc/vim_diff.txt | 1 | ||||
-rw-r--r-- | src/nvim/api/vim.c | 12 | ||||
-rw-r--r-- | src/nvim/highlight.c | 7 | ||||
-rw-r--r-- | src/nvim/highlight_defs.h | 2 | ||||
-rw-r--r-- | src/nvim/syntax.c | 1 | ||||
-rw-r--r-- | src/nvim/window.c | 14 | ||||
-rw-r--r-- | test/functional/ui/cursor_spec.lua | 4 | ||||
-rw-r--r-- | test/functional/ui/float_spec.lua | 161 |
10 files changed, 286 insertions, 74 deletions
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index c9d526d9aa..7172091ceb 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -232,7 +232,44 @@ An example of calling the api from vimscript: > " later call nvim_buf_clear_namespace(0, src, 0, -1) + + +============================================================================== +Floating windows *api-floatwin* + +Nvim supports floating windows, windows that are displayed on top of ordinary +windows. This is useful to implement simple widgets, such as tooltips +displaying information next to cursor text. Floating windows are fully +functional buffer windows and support user editing. They support the standard +|api-window| calls and almost all window options (with some exceptions such as +'statusline' is not supported currently). + +Floating windows are created either by |nvim_open_win()| to open a new window, +or |nvim_win_config()| to reconfigure a normal window into a float. Currently +the position can either be grid coordinates relative the top-left of some +window, or a position relative to the current window cursor. The parameters +for positioning are described in detail at |nvim_open_win()| help. + +|nvim_open_win()| assumes an existing buffer to display in the window. To create +a scratch buffer for the float, |nvim_create_buffer()| can be used. The text in +the buffer can be highlighted using standard functionality, such as syntax +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. + +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', 'col':0, 'row':1, 'anchor': 'NW'} + let win = nvim_open_win(buf, 0, 10, 2, opts) + " optional: change highlight, otherwise Pmenu is used + call nvim_win_set_option(win, 'winhl', 'Normal:MyHighlight') > +To close the float, |nvim_win_close()| can be used. ============================================================================== Global Functions *api-global* @@ -576,6 +613,94 @@ nvim_set_current_win({window}) *nvim_set_current_win()* Parameters: ~ {window} Window handle +nvim_create_buf({listed}, {scratch}) *nvim_create_buf()* + Creates a new, empty, unnamed buffer. + + Parameters: ~ + {listed} Controls 'buflisted' + {scratch} Creates a "throwaway" |scratch-buffer| for + temporary work (always 'nomodified') + + Return: ~ + Buffer handle, or 0 on error + + *nvim_open_win()* +nvim_open_win({buffer}, {enter}, {width}, {height}, {options}) + Open a new window. + + Currently this is used to open floating and external windows. + Floats are windows that are drawn above the split layout, at + some anchor position in some other window. Floats can be draw + internally or by external GUI with the |ui-multigrid| + extension. External windows are only supported with multigrid + GUIs, and are displayed as separate top-level windows. + + For a general overview of floats, see |api-floatwin|. + + Exactly one of `external` and `relative` must be specified. + + Parameters: ~ + {buffer} handle of buffer to be displayed in the window + {enter} whether the window should be entered (made the + current window) + {width} width of window (in character cells) + {height} height of window (in character cells) + {options} dict of options for configuring window + positioning accepts the following keys: + + `relative`: If set, the window becomes a + floating window. The window will be placed with + row,col coordinates relative one of the + following: + "editor" the global editor grid + "win" a window. Use 'win' option below to + specify window id, or current window will + be used by default. + "cursor" the cursor position in current window. + + `anchor`: the corner of the float that the row,col + position defines + "NW" north-west (default) + "NE" north-east + "SW" south-west + "SE" south-east + + `focusable`: Whether window can be focused by wincmds and + mouse events. Defaults to true. Even if set to false, + the window can still be entered using + |nvim_set_current_win()| API call. + + `row`: row position. Screen cell height are used as unit. + Can be floating point. + + `col`: column position. Screen cell width is used as + unit. Can be floating point. + + `win`: when using relative='win', window id of the window + where the position is defined. + + `external`: GUI should display the window as an external + top-level window. Currently accepts no other + positioning options together with this. + + With editor positioning row=0, col=0 refers to the top-left + corner of the screen-grid and row=Lines-1, Columns-1 refers to + the bottom-right corner. Floating point values are allowed, + but the builtin implementation (used by TUI and GUIs without + multigrid support) will always round down to nearest integer. + + Out-of-bounds values, and configurations that make the float + not fit inside the main editor, are allowed. The builtin + implementation will truncate values so floats are completely + within the main screen grid. External GUIs could let floats + hover outside of the main window like a tooltip, but this + should not be used to specify arbitrary WM screen positions. + + Parameters: ~ + + Return: ~ + the window handle or 0 when error + nvim_list_tabpages() *nvim_list_tabpages()* Gets the current list of tabpage handles. @@ -1436,6 +1561,37 @@ nvim_win_is_valid({window}) *nvim_win_is_valid()* Return: ~ true if the window is valid, false otherwise + *nvim_win_config()* +nvim_win_config({window}, {width}, {height}, {options}) + Configure window position. Currently this is only used to + configure floating and external windows (including changing a + split window to these types). + + See documentation at |nvim_open_win()|, for the meaning of + parameters. Pass in -1 for 'witdh' and 'height' to keep + exiting size. + + When reconfiguring a floating window, absent option keys will + not be changed. The following restriction apply: `row`, `col` + and `relative` must be reconfigured together. Only changing a + subset of these is an error. + +nvim_win_close({window}, {force}) *nvim_win_close()* + Close a window. + + This is equivalent to |:close| with count except that it takes + a window id. + + Parameters: ~ + {window} Window handle + {force} Behave like `:close!` The last window of a + buffer with unwritten changes can be closed. The + buffer will become hidden, even if 'hidden' is + not set. + + Return: ~ + Window number + ============================================================================== Tabpage Functions *api-tabpage* diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt index 684830b78d..b60a952def 100644 --- a/runtime/doc/syntax.txt +++ b/runtime/doc/syntax.txt @@ -4953,6 +4953,8 @@ NonText '@' at the end of the window, characters from 'showbreak' fit at the end of the line). See also |hl-EndOfBuffer|. *hl-Normal* Normal normal text + *hl-NormalFloat* +NormalFloat Normal text in floating windows. *hl-NormalNC* NormalNC normal text in non-current windows *hl-Pmenu* diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 25ca2974bc..3b5a5cded2 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -161,6 +161,7 @@ Functions: Highlight groups: |expr-highlight| highlight groups (prefixed with "Nvim") + |hl-NormalFloat| highlights floating window |hl-NormalNC| highlights non-current windows |hl-MsgSeparator| highlights separator for scrolled messages |hl-QuickFixLine| diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index cb5ed5ecda..a773234ea0 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -15,6 +15,7 @@ #include "nvim/api/private/defs.h" #include "nvim/api/private/dispatch.h" #include "nvim/api/buffer.h" +#include "nvim/api/window.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/msgpack_rpc/helpers.h" #include "nvim/lua/executor.h" @@ -997,6 +998,8 @@ Buffer nvim_create_buf(Boolean listed, Boolean scratch, Error *err) /// GUI with the |ui-multigrid| extension. External windows are only supported /// with multigrid GUIs, and are displayed as separate top-level windows. /// +/// For a general overview of floats, see |api-floatwin|. +/// /// Exactly one of `external` and `relative` must be specified. /// /// @param buffer handle of buffer to be displayed in the window @@ -1049,7 +1052,6 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dictionary options, Error *err) FUNC_API_SINCE(6) { - win_T *old = curwin; FloatConfig config = FLOAT_CONFIG_INIT; if (!parse_float_config(options, &config, false, err)) { return 0; @@ -1058,11 +1060,11 @@ Window nvim_open_win(Buffer buffer, Boolean enter, if (!wp) { return 0; } - if (buffer > 0) { - nvim_set_current_buf(buffer, err); + if (enter) { + win_enter(wp, false); } - if (!enter) { - win_enter(old, false); + if (buffer > 0) { + nvim_win_set_buf(wp->handle, buffer, err); } return wp->handle; } diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c index 4c5fca6d39..3ba02be32d 100644 --- a/src/nvim/highlight.c +++ b/src/nvim/highlight.c @@ -160,14 +160,19 @@ void update_window_hl(win_T *wp, bool invalid) wp->w_hl_needs_update = false; // 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) { wp->w_hl_attr_normal = hl_get_ui_attr(HLF_INACTIVE, wp->w_hl_ids[HLF_INACTIVE], true); + } else if (float_win && wp->w_hl_ids[HLF_NFLOAT] > 0) { + wp->w_hl_attr_normal = hl_get_ui_attr(HLF_NFLOAT, + wp->w_hl_ids[HLF_NFLOAT], true); } else if (wp->w_hl_id_normal > 0) { wp->w_hl_attr_normal = hl_get_ui_attr(-1, wp->w_hl_id_normal, true); } else { - wp->w_hl_attr_normal = 0; + wp->w_hl_attr_normal = float_win ? HL_ATTR(HLF_NFLOAT) : 0; } + if (wp != curwin) { wp->w_hl_attr_normal = hl_combine_attr(HL_ATTR(HLF_INACTIVE), wp->w_hl_attr_normal); diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h index 1da33bfea5..746d2c2dfc 100644 --- a/src/nvim/highlight_defs.h +++ b/src/nvim/highlight_defs.h @@ -90,6 +90,7 @@ typedef enum { , HLF_0 // Whitespace , HLF_INACTIVE // NormalNC: Normal text in non-current windows , HLF_MSGSEP // message separator line + , HLF_NFLOAT // Floating window , HLF_COUNT // MUST be the last one } hlf_T; @@ -142,6 +143,7 @@ EXTERN const char *hlf_names[] INIT(= { [HLF_0] = "Whitespace", [HLF_INACTIVE] = "NormalNC", [HLF_MSGSEP] = "MsgSeparator", + [HLF_NFLOAT] = "NormalFloat", }); diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index 0104f0d834..8c3ce823d3 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -5956,6 +5956,7 @@ static const char *highlight_init_both[] = { "default link Substitute Search", "default link Whitespace NonText", "default link MsgSeparator StatusLine", + "default link NormalFloat Pmenu", NULL }; diff --git a/src/nvim/window.c b/src/nvim/window.c index 83ddf534cf..edb5b06a2e 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -541,9 +541,7 @@ static void cmd_with_count(char *cmd, char_u *bufp, size_t bufsize, win_T *win_new_float(win_T *wp, int width, int height, FloatConfig config, Error *err) { - bool new = false; if (wp == NULL) { - new = true; wp = win_alloc(lastwin_nofloating(), false); win_init(wp, curwin, 0); } else { @@ -569,12 +567,13 @@ win_T *win_new_float(win_T *wp, int width, int height, FloatConfig config, wp->w_floating = 1; 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, width, height, config); wp->w_pos_changed = true; redraw_win_later(wp, VALID); - if (new) { - win_enter(wp, false); - } return wp; } @@ -591,6 +590,7 @@ void win_config_float(win_T *wp, int width, int height, config.window = curwin->handle; } + bool change_external = config.external != wp->w_float_config.external; wp->w_float_config = config; if (!ui_has(kUIMultigrid)) { @@ -601,6 +601,10 @@ void win_config_float(win_T *wp, int width, int height, win_set_inner_size(wp); must_redraw = MAX(must_redraw, VALID); wp->w_pos_changed = true; + if (change_external) { + wp->w_hl_needs_update = true; + redraw_win_later(wp, NOT_VALID); + } } static void ui_ext_win_position(win_T *wp) diff --git a/test/functional/ui/cursor_spec.lua b/test/functional/ui/cursor_spec.lua index 3e0370db14..4dc86f1e1f 100644 --- a/test/functional/ui/cursor_spec.lua +++ b/test/functional/ui/cursor_spec.lua @@ -216,10 +216,10 @@ describe('ui/cursor', function() if m.blinkwait then m.blinkwait = 700 end end if m.hl_id then - m.hl_id = 49 + m.hl_id = 50 m.attr = {background = Screen.colors.DarkGray} end - if m.id_lm then m.id_lm = 50 end + if m.id_lm then m.id_lm = 51 end end -- Assert the new expectation. diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index 3be2182eb4..1113468d88 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -29,7 +29,10 @@ describe('floating windows', function() [10] = {background = Screen.colors.LightGrey, underline = true, bold = true, foreground = Screen.colors.Magenta}, [11] = {bold = true, foreground = Screen.colors.Magenta}, [12] = {background = Screen.colors.Red, bold = true, foreground = Screen.colors.Blue1}, - [13] = {background = Screen.colors.WebGray} + [13] = {background = Screen.colors.WebGray}, + [14] = {foreground = Screen.colors.Brown}, + [15] = {background = Screen.colors.Grey20}, + [16] = {background = Screen.colors.Grey20, bold = true, foreground = Screen.colors.Blue1}, } local function with_ext_multigrid(multigrid) @@ -43,12 +46,10 @@ describe('floating windows', function() it('can be created and reconfigured', function() local buf = meths.create_buf(false,false) local win = meths.open_win(buf, false, 20, 2, {relative='editor', row=2, col=5}) - meths.win_set_option(win , 'winhl', 'Normal:PMenu') local expected_pos = { [3]={{id=1001}, 'NW', 1, 2, 5, true}, } - if multigrid then screen:expect{grid=[[ ## grid 1 @@ -151,6 +152,83 @@ describe('floating windows', function() end end) + it('defaults to nonumber and NormalFloat highlight', function() + command('set number') + command('hi NormalFloat guibg=#333333') + feed('ix<cr>y<cr><esc>gg') + local win = meths.open_win(0, false, 20, 4, {relative='editor', row=4, col=10}) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + | + ## grid 2 + {14: 1 }^x | + {14: 2 }y | + {14: 3 } | + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {15:x }| + {15:y }| + {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: }| + {0:~ }{16:~ }{0: }| + | + ]]) + end + + 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:----------------------------------------]| + {4:[No Name] [+] }| + | + ## grid 2 + {14: 1 }^x | + {14: 2 }y | + {14: 3 } | + {0:~ }| + {0:~ }| + ## grid 3 + {15: }| + {16:~ }| + {16:~ }| + {16:~ }| + ]], float_pos={[3] = {{id = 1001}, "NW", 1, 4, 10, true}}} + else + screen:expect([[ + {14: 1 }^x | + {14: 2 }y | + {14: 3 } {15: } | + {0:~ }{16:~ }{0: }| + {0:~ }{16:~ }{0: }| + {4:[No Name] }{16:~ }{4: }| + | + ]]) + end + end) + it('API has proper error messages', function() local buf = meths.create_buf(false,false) eq({false, "Invalid options key 'bork'"}, @@ -211,7 +289,6 @@ describe('floating windows', function() local buf = meths.create_buf(false,false) -- no 'win' arg, relative default window local win = meths.open_win(buf, false, 20, 2, {relative='win', row=0, col=10}) - meths.win_set_option(win, 'winhl', 'Normal:PMenu') if multigrid then screen:expect{grid=[[ ## grid 1 @@ -467,8 +544,7 @@ describe('floating windows', function() screen2:attach(nil, session2) screen2:set_default_attr_ids(attrs) local buf = meths.create_buf(false,false) - local win = meths.open_win(buf, true, 20, 2, {relative='editor', row=2, col=5}) - meths.win_set_option(win, 'winhl', 'Normal:PMenu') + meths.open_win(buf, true, 20, 2, {relative='editor', row=2, col=5}) local expected_pos = { [2]={{id=1001}, 'NW', 1, 2, 5} } @@ -502,7 +578,6 @@ describe('floating windows', function() local buf = meths.create_buf(false,false) meths.buf_set_lines(buf, 0, -1, true, {'such', 'very', 'float'}) local win = meths.open_win(buf, false, 15, 4, {relative='editor', row=2, col=10}) - meths.win_set_option(win , 'winhl', 'Normal:PMenu') local expected_pos = { [4]={{id=1002}, 'NW', 1, 2, 10, true}, } @@ -1417,7 +1492,6 @@ describe('floating windows', function() local buf = meths.create_buf(false,false) win = meths.open_win(buf, false, 20, 2, {relative='editor', row=2, col=5}) meths.buf_set_lines(buf,0,-1,true,{"y"}) - meths.win_set_option(win , 'winhl', 'Normal:PMenu') expected_pos = { [3]={{id=1001}, 'NW', 1, 2, 5, true} } @@ -2074,39 +2148,6 @@ describe('floating windows', function() {1:y }| {2:~ }| ## grid 4 - {1:^y }| - {2:~ }| - ]], float_pos=expected_pos} - else - screen:expect([[ - {1:^y }| - {2:~ }| - {4:[No N}{1:y }{4: }| - x {2:~ } | - {0:~ }| - {5:[No Name] [+] }| - | - ]]) - end - - feed(":set winhighlight=<cr><c-l>") - if multigrid then - screen:expect{grid=[[ - ## grid 1 - [4:----------------------------------------]| - [4:----------------------------------------]| - {4:[No Name] [+] }| - [2:----------------------------------------]| - [2:----------------------------------------]| - {5:[No Name] [+] }| - | - ## grid 2 - x | - {0:~ }| - ## grid 3 - {1:y }| - {2:~ }| - ## grid 4 ^y | {0:~ }| ]], float_pos=expected_pos} @@ -2122,7 +2163,6 @@ describe('floating windows', function() ]]) end - feed("<c-w>j") if multigrid then screen:expect{grid=[[ @@ -2659,16 +2699,16 @@ describe('floating windows', function() x | {0:~ }| ## grid 3 - {1:^y }| - {2:~ }| + ^y | + {0:~ }| ]]} else screen:expect([[ x | {0:~ }| {5:[No Name] [+] }| - {1:^y }| - {2:~ }| + ^y | + {0:~ }| {4:[No Name] [+] }| | ]]) @@ -2693,8 +2733,8 @@ describe('floating windows', function() {0:~ }| {0:~ }| ## grid 3 - {1:^y }| - {2:~ }| + ^y | + {0:~ }| ]], float_pos=expected_pos} else eq({false, "UI doesn't support external windows"}, @@ -2717,11 +2757,10 @@ describe('floating windows', function() x | {0:~ }| ## grid 3 - {1:^y }| - {2:~ }| + ^y | + {0:~ }| ]]) end - end) it('movements with nested split layout', function() @@ -2786,8 +2825,8 @@ describe('floating windows', function() 4 | {0:~ }| ## grid 3 - ^5 | - {0:~ }| + {1:^5 }| + {2:~ }| ## grid 4 2 | {0:~ }| @@ -2802,8 +2841,8 @@ describe('floating windows', function() screen:expect([[ 1 {5:│}2 | {0:~ }{5:│}{0:~ }| - {5:[No N}^5 {5:ame] [+] }| - 3 {0:~ } | + {5:[No N}{1:^5 }{5:ame] [+] }| + 3 {2:~ } | {0:~ }{5:│}{0:~ }| {5:[No Name] [+] [No Name] [+] }| :enew | @@ -2985,8 +3024,8 @@ describe('floating windows', function() {0:~ }| {0:~ }| ## grid 3 - {1:y }| - {2:~ }| + y | + {0:~ }| ## grid 4 ^ | {0:~ }| @@ -3016,8 +3055,8 @@ describe('floating windows', function() {0:~ }| {0:~ }| ## grid 3 - {1:y }| - {2:~ }| + y | + {0:~ }| ## grid 4 | {0:~ }| @@ -3044,8 +3083,8 @@ describe('floating windows', function() {0:~ }| {0:~ }| ## grid 3 - {1:y }| - {2:~ }| + y | + {0:~ }| ## grid 4 ^ | {0:~ }| |