Merge pull request #10090 from bfredl/floatpopup

api/window: add style="minimal" flag to disable unwanted UI features for simple floats
This commit is contained in:
Björn Linse 2019-07-07 21:35:55 +02:00 committed by GitHub
commit 524fe6205d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 237 additions and 30 deletions

View File

@ -284,14 +284,18 @@ highlighting, or |api-highlights|.
By default, floats will use |hl-NormalFloat| as normal highlight, which By default, floats will use |hl-NormalFloat| as normal highlight, which
links to |hl-Pmenu| in the builtin color scheme. The 'winhighlight' option can 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 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: > Here is an example for creating a float with scratch buffer: >
let buf = nvim_create_buf(v:false, v:true) let buf = nvim_create_buf(v:false, v:true)
call nvim_buf_set_lines(buf, 0, -1, v:true, ["test", "text"]) call nvim_buf_set_lines(buf, 0, -1, v:true, ["test", "text"])
let opts = {'relative': 'cursor', 'width': 10, 'height': 2, 'col': 0, 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) let win = nvim_open_win(buf, 0, opts)
" optional: change highlight, otherwise Pmenu is used " optional: change highlight, otherwise Pmenu is used
call nvim_win_set_option(win, 'winhl', 'Normal:MyHighlight') call nvim_win_set_option(win, 'winhl', 'Normal:MyHighlight')

View File

@ -1064,6 +1064,19 @@ fail:
/// - `external`: GUI should display the window as an external /// - `external`: GUI should display the window as an external
/// top-level window. Currently accepts no other positioning /// top-level window. Currently accepts no other positioning
/// configuration together with this. /// 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 /// @param[out] err Error details, if any
/// ///
/// @return Window handle, or 0 on error /// @return Window handle, or 0 on error
@ -1085,6 +1098,11 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dictionary config,
if (buffer > 0) { if (buffer > 0) {
nvim_win_set_buf(wp->handle, buffer, err); nvim_win_set_buf(wp->handle, buffer, err);
} }
if (fconfig.style == kWinStyleMinimal) {
win_set_minimal_style(wp);
didset_window_options(wp);
}
return wp->handle; return wp->handle;
} }

View File

@ -13,6 +13,7 @@
#include "nvim/vim.h" #include "nvim/vim.h"
#include "nvim/buffer.h" #include "nvim/buffer.h"
#include "nvim/cursor.h" #include "nvim/cursor.h"
#include "nvim/option.h"
#include "nvim/window.h" #include "nvim/window.h"
#include "nvim/screen.h" #include "nvim/screen.h"
#include "nvim/move.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_config_float(win, fconfig);
win->w_pos_changed = true; win->w_pos_changed = true;
} }
if (fconfig.style == kWinStyleMinimal) {
win_set_minimal_style(win);
didset_window_options(win);
}
} }
/// Return window configuration. /// Return window configuration.

View File

@ -2543,6 +2543,11 @@ void get_winopts(buf_T *buf)
} else } else
copy_winopt(&curwin->w_allbuf_opt, &curwin->w_onebuf_opt); 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. // Set 'foldlevel' to 'foldlevelstart' if it's not negative.
if (p_fdls >= 0) { if (p_fdls >= 0) {
curwin->w_p_fdl = p_fdls; curwin->w_p_fdl = p_fdls;

View File

@ -972,7 +972,6 @@ struct matchitem {
}; };
typedef int FloatAnchor; typedef int FloatAnchor;
typedef int FloatRelative;
enum { enum {
kFloatAnchorEast = 1, kFloatAnchorEast = 1,
@ -985,15 +984,20 @@ enum {
// SE -> kFloatAnchorSouth | kFloatAnchorEast // SE -> kFloatAnchorSouth | kFloatAnchorEast
EXTERN const char *const float_anchor_str[] INIT(= { "NW", "NE", "SW", "SE" }); EXTERN const char *const float_anchor_str[] INIT(= { "NW", "NE", "SW", "SE" });
enum { typedef enum {
kFloatRelativeEditor = 0, kFloatRelativeEditor = 0,
kFloatRelativeWindow = 1, kFloatRelativeWindow = 1,
kFloatRelativeCursor = 2, kFloatRelativeCursor = 2,
}; } FloatRelative;
EXTERN const char *const float_relative_str[] INIT(= { "editor", "window", EXTERN const char *const float_relative_str[] INIT(= { "editor", "window",
"cursor" }); "cursor" });
typedef enum {
kWinStyleUnused = 0,
kWinStyleMinimal, /// Minimal UI: no number column, eob markers, etc
} WinStyle;
typedef struct { typedef struct {
Window window; Window window;
int height, width; int height, width;
@ -1002,12 +1006,14 @@ typedef struct {
FloatRelative relative; FloatRelative relative;
bool external; bool external;
bool focusable; bool focusable;
WinStyle style;
} FloatConfig; } FloatConfig;
#define FLOAT_CONFIG_INIT ((FloatConfig){ .height = 0, .width = 0, \ #define FLOAT_CONFIG_INIT ((FloatConfig){ .height = 0, .width = 0, \
.row = 0, .col = 0, .anchor = 0, \ .row = 0, .col = 0, .anchor = 0, \
.relative = 0, .external = false, \ .relative = 0, .external = false, \
.focusable = true }) .focusable = true, \
.style = kWinStyleUnused })
// Structure to store last cursor position and topline. Used by check_lnums() // Structure to store last cursor position and topline. Used by check_lnums()
// and reset_lnums(). // and reset_lnums().

View File

@ -141,10 +141,12 @@ int hl_get_ui_attr(int idx, int final_id, bool optional)
HlAttrs attrs = HLATTRS_INIT; HlAttrs attrs = HLATTRS_INIT;
bool available = false; bool available = false;
int syn_attr = syn_id2attr(final_id); if (final_id > 0) {
if (syn_attr != 0) { int syn_attr = syn_id2attr(final_id);
attrs = syn_attr2entry(syn_attr); if (syn_attr != 0) {
available = true; attrs = syn_attr2entry(syn_attr);
available = true;
}
} }
if (HLF_PNI <= idx && idx <= HLF_PST) { 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' // determine window specific background set in 'winhighlight'
bool float_win = wp->w_floating && !wp->w_float_config.external; 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_attr_normal = hl_get_ui_attr(HLF_INACTIVE,
wp->w_hl_ids[HLF_INACTIVE], wp->w_hl_ids[HLF_INACTIVE],
!has_blend); !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, wp->w_hl_attr_normal = hl_get_ui_attr(HLF_NFLOAT,
// 'cursorline'
wp->w_hl_ids[HLF_NFLOAT], !has_blend); 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); wp->w_hl_attr_normal = hl_get_ui_attr(-1, wp->w_hl_id_normal, !has_blend);
} else { } else {
wp->w_hl_attr_normal = float_win ? HL_ATTR(HLF_NFLOAT) : 0; 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 = hl_combine_attr(HL_ATTR(HLF_INACTIVE),
wp->w_hl_attr_normal); wp->w_hl_attr_normal);
} }
for (int hlf = 0; hlf < (int)HLF_COUNT; hlf++) { for (int hlf = 0; hlf < (int)HLF_COUNT; hlf++) {
int attr; 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); attr = hl_get_ui_attr(hlf, wp->w_hl_ids[hlf], false);
} else { } else {
attr = HL_ATTR(hlf); attr = HL_ATTR(hlf);

View File

@ -3762,7 +3762,8 @@ static bool parse_winhl_opt(win_T *wp)
size_t nlen = (size_t)(colon-p); size_t nlen = (size_t)(colon-p);
char *hi = colon+1; char *hi = colon+1;
char *commap = xstrchrnul(hi, ','); 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) { if (strncmp("Normal", p, nlen) == 0) {
w_hl_id_normal = hl_id; w_hl_id_normal = hl_id;

View File

@ -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) { if (wp->w_p_rl) {
grid_fill(&wp->w_grid, row, endrow, wp->w_wincol, W_ENDCOL(wp) - 1 - n, grid_fill(&wp->w_grid, row, endrow, wp->w_wincol, W_ENDCOL(wp) - 1 - n,

View File

@ -584,15 +584,43 @@ win_T *win_new_float(win_T *wp, FloatConfig fconfig, Error *err)
wp->w_status_height = 0; wp->w_status_height = 0;
wp->w_vsep_width = 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); win_config_float(wp, fconfig);
wp->w_pos_changed = true; wp->w_pos_changed = true;
redraw_win_later(wp, VALID); redraw_win_later(wp, VALID);
return wp; 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) void win_config_float(win_T *wp, FloatConfig fconfig)
{ {
wp->w_width = MAX(fconfig.width, 1); 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"); "'focusable' key must be Boolean");
return false; 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 { } else {
api_set_error(err, kErrorTypeValidation, api_set_error(err, kErrorTypeValidation,
"Invalid key '%s'", key); "Invalid key '%s'", key);

View File

@ -35,6 +35,10 @@ describe('floating windows', function()
[15] = {background = Screen.colors.Grey20}, [15] = {background = Screen.colors.Grey20},
[16] = {background = Screen.colors.Grey20, bold = true, foreground = Screen.colors.Blue1}, [16] = {background = Screen.colors.Grey20, bold = true, foreground = Screen.colors.Blue1},
[17] = {background = Screen.colors.Yellow}, [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() it('behavior', function()
@ -182,7 +186,7 @@ describe('floating windows', function()
end end
end) end)
it('defaults to nonumber and NormalFloat highlight', function() it('defaults to NormalFloat highlight and inherited options', function()
command('set number') command('set number')
command('hi NormalFloat guibg=#333333') command('hi NormalFloat guibg=#333333')
feed('ix<cr>y<cr><esc>gg') feed('ix<cr>y<cr><esc>gg')
@ -205,18 +209,18 @@ describe('floating windows', function()
{0:~ }| {0:~ }|
{0:~ }| {0:~ }|
## grid 3 ## grid 3
{15:x }| {18: 1 }{15:x }|
{15:y }| {18: 2 }{15:y }|
{15: }| {18: 3 }{15: }|
{16:~ }| {16:~ }|
]], float_pos={[3] = {{id = 1001}, "NW", 1, 4, 10, true}}} ]], float_pos={[3] = {{id = 1001}, "NW", 1, 4, 10, true}}}
else else
screen:expect([[ screen:expect([[
{14: 1 }^x | {14: 1 }^x |
{14: 2 }y | {14: 2 }y |
{14: 3 } {15:x } | {14: 3 } {18: 1 }{15:x } |
{0:~ }{15:y }{0: }| {0:~ }{18: 2 }{15:y }{0: }|
{0:~ }{15: }{0: }| {0:~ }{18: 3 }{15: }{0: }|
{0:~ }{16:~ }{0: }| {0:~ }{16:~ }{0: }|
| |
]]) ]])
@ -242,7 +246,7 @@ describe('floating windows', function()
{0:~ }| {0:~ }|
{0:~ }| {0:~ }|
## grid 3 ## grid 3
{15: }| {18: 1 }{15: }|
{16:~ }| {16:~ }|
{16:~ }| {16:~ }|
{16:~ }| {16:~ }|
@ -251,7 +255,7 @@ describe('floating windows', function()
screen:expect([[ screen:expect([[
{14: 1 }^x | {14: 1 }^x |
{14: 2 }y | {14: 2 }y |
{14: 3 } {15: } | {14: 3 } {18: 1 }{15: } |
{0:~ }{16:~ }{0: }| {0:~ }{16:~ }{0: }|
{0:~ }{16:~ }{0: }| {0:~ }{16:~ }{0: }|
{0:~ }{16:~ }{0: }| {0:~ }{16:~ }{0: }|
@ -260,6 +264,126 @@ describe('floating windows', function()
end end
end) end)
it("can use 'minimal' style", function()
command('set number')
command('set signcolumn=yes')
command('set cursorline')
command('hi NormalFloat guibg=#333333')
feed('ix<cr>y<cr><esc>gg')
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() it('can have minimum size', function()
insert("the background text") insert("the background text")
local buf = meths.create_buf(false, true) local buf = meths.create_buf(false, true)