windows: float config changes

- Allow floating windows of width 1. #9846
- For a new floating window the size must be specified. Later on we
  might try to calculate a reasonable size by buffer contents
- Remember the configured size of a window, just like its position.
- Make get_config and set_config more consistent. Handle relative='' properly in set_config.
  get_config doesn't return keys that don't make sense for a non-floating window.
- Don't use width=0 for non-changed width, just omit the key.
This commit is contained in:
Björn Linse
2019-04-13 14:25:15 +02:00
parent d08692a824
commit c8acbe3b62
5 changed files with 198 additions and 61 deletions

View File

@@ -1003,7 +1003,8 @@ Buffer nvim_create_buf(Boolean listed, Boolean scratch, Error *err)
/// ///
/// For a general overview of floats, see |api-floatwin|. /// For a general overview of floats, see |api-floatwin|.
/// ///
/// Exactly one of `external` and `relative` must be specified. /// Exactly one of `external` and `relative` must be specified. The `width` and
/// `height` of the new window must be specified.
/// ///
/// With editor positioning row=0, col=0 refers to the top-left corner of the /// 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. /// screen-grid and row=Lines-1, Columns-1 refers to the bottom-right corner.
@@ -1035,7 +1036,7 @@ Buffer nvim_create_buf(Boolean listed, Boolean scratch, Error *err)
/// - "SW" south-west /// - "SW" south-west
/// - "SE" south-east /// - "SE" south-east
/// - `height`: window height (in character cells). Minimum of 1. /// - `height`: window height (in character cells). Minimum of 1.
/// - `width`: window width (in character cells). Minimum of 2. /// - `width`: window width (in character cells). Minimum of 1.
/// - `row`: row position. Screen cell height are used as unit. Can be /// - `row`: row position. Screen cell height are used as unit. Can be
/// floating point. /// floating point.
/// - `col`: column position. Screen cell width is used as unit. Can be /// - `col`: column position. Screen cell width is used as unit. Can be

View File

@@ -466,8 +466,6 @@ void nvim_win_set_config(Window window, Dictionary config, Error *err)
if (!parse_float_config(config, &fconfig, !new_float, err)) { if (!parse_float_config(config, &fconfig, !new_float, err)) {
return; return;
} }
fconfig.height = fconfig.height > 0 ? fconfig.height : win->w_height;
fconfig.width = fconfig.width > 0 ? fconfig.width : win->w_width;
if (new_float) { if (new_float) {
if (!win_new_float(win, fconfig, err)) { if (!win_new_float(win, fconfig, err)) {
return; return;
@@ -499,26 +497,25 @@ Dictionary nvim_win_get_config(Window window, Error *err)
return rv; return rv;
} }
PUT(rv, "width", INTEGER_OBJ(wp->w_float_config.width));
PUT(rv, "height", INTEGER_OBJ(wp->w_float_config.height));
PUT(rv, "focusable", BOOLEAN_OBJ(wp->w_float_config.focusable)); PUT(rv, "focusable", BOOLEAN_OBJ(wp->w_float_config.focusable));
PUT(rv, "external", BOOLEAN_OBJ(wp->w_float_config.external)); PUT(rv, "external", BOOLEAN_OBJ(wp->w_float_config.external));
PUT(rv, "anchor", STRING_OBJ(cstr_to_string(
float_anchor_str[wp->w_float_config.anchor])));
if (wp->w_float_config.relative == kFloatRelativeWindow) { if (wp->w_floating) {
PUT(rv, "win", INTEGER_OBJ(wp->w_float_config.window)); PUT(rv, "width", INTEGER_OBJ(wp->w_float_config.width));
PUT(rv, "height", INTEGER_OBJ(wp->w_float_config.height));
if (!wp->w_float_config.external) {
if (wp->w_float_config.relative == kFloatRelativeWindow) {
PUT(rv, "win", INTEGER_OBJ(wp->w_float_config.window));
}
PUT(rv, "anchor", STRING_OBJ(cstr_to_string(
float_anchor_str[wp->w_float_config.anchor])));
PUT(rv, "row", FLOAT_OBJ(wp->w_float_config.row));
PUT(rv, "col", FLOAT_OBJ(wp->w_float_config.col));
}
} }
if (wp->w_float_config.external) { const char *rel = (wp->w_floating && !wp->w_float_config.external
return rv; ? float_relative_str[wp->w_float_config.relative] : "");
}
PUT(rv, "row", FLOAT_OBJ(wp->w_float_config.row));
PUT(rv, "col", FLOAT_OBJ(wp->w_float_config.col));
const char *rel =
wp->w_floating ? float_relative_str[wp->w_float_config.relative] : "";
PUT(rv, "relative", STRING_OBJ(cstr_to_string(rel))); PUT(rv, "relative", STRING_OBJ(cstr_to_string(rel)));
return rv; return rv;

View File

@@ -363,7 +363,7 @@ void update_screen(int type)
need_wait_return = FALSE; need_wait_return = FALSE;
} }
if (type >= NOT_VALID) { if (type >= CLEAR || !default_grid.valid) {
ui_comp_set_screen_valid(false); ui_comp_set_screen_valid(false);
} }
win_ui_flush_positions(); win_ui_flush_positions();

View File

@@ -579,7 +579,7 @@ win_T *win_new_float(win_T *wp, FloatConfig fconfig, Error *err)
void win_config_float(win_T *wp, FloatConfig fconfig) void win_config_float(win_T *wp, FloatConfig fconfig)
{ {
wp->w_width = MAX(fconfig.width, 2); wp->w_width = MAX(fconfig.width, 1);
wp->w_height = MAX(fconfig.height, 1); wp->w_height = MAX(fconfig.height, 1);
if (fconfig.relative == kFloatRelativeCursor) { if (fconfig.relative == kFloatRelativeCursor) {
@@ -617,13 +617,16 @@ static void ui_ext_win_position(win_T *wp)
FloatConfig c = wp->w_float_config; FloatConfig c = wp->w_float_config;
if (!c.external) { if (!c.external) {
ScreenGrid *grid = &default_grid; ScreenGrid *grid = &default_grid;
int row = c.row, col = c.col; float row = c.row, col = c.col;
if (c.relative == kFloatRelativeWindow) { if (c.relative == kFloatRelativeWindow) {
Error dummy = ERROR_INIT; Error dummy = ERROR_INIT;
win_T *win = find_window_by_handle(c.window, &dummy); win_T *win = find_window_by_handle(c.window, &dummy);
if (win) { if (win) {
grid = &win->w_grid; grid = &win->w_grid;
screen_adjust_grid(&grid, &row, &col); int row_off = 0, col_off = 0;
screen_adjust_grid(&grid, &row_off, &col_off);
row += row_off;
col += col_off;
} }
api_clear_error(&dummy); api_clear_error(&dummy);
} }
@@ -637,16 +640,16 @@ static void ui_ext_win_position(win_T *wp)
bool east = c.anchor & kFloatAnchorEast; bool east = c.anchor & kFloatAnchorEast;
bool south = c.anchor & kFloatAnchorSouth; bool south = c.anchor & kFloatAnchorSouth;
row -= (south ? wp->w_height : 0); int comp_row = (int)row - (south ? wp->w_height : 0);
col -= (east ? wp->w_width : 0); int comp_col = (int)col - (east ? wp->w_width : 0);
row = MAX(MIN(row, Rows-wp->w_height-1), 0); comp_row = MAX(MIN(comp_row, Rows-wp->w_height-1), 0);
col = MAX(MIN(col, Columns-wp->w_width), 0); comp_col = MAX(MIN(comp_col, Columns-wp->w_width), 0);
wp->w_winrow = row; wp->w_winrow = comp_row;
wp->w_wincol = col; wp->w_wincol = comp_col;
bool valid = (wp->w_redr_type == 0); bool valid = (wp->w_redr_type == 0);
bool on_top = (curwin == wp) || !curwin->w_floating; bool on_top = (curwin == wp) || !curwin->w_floating;
ui_comp_put_grid(&wp->w_grid, row, col, wp->w_height, wp->w_width, ui_comp_put_grid(&wp->w_grid, comp_row, comp_col, wp->w_height,
valid, on_top); wp->w_width, valid, on_top);
if (!valid) { if (!valid) {
wp->w_grid.valid = false; wp->w_grid.valid = false;
redraw_win_later(wp, NOT_VALID); redraw_win_later(wp, NOT_VALID);
@@ -681,9 +684,6 @@ static bool parse_float_anchor(String anchor, FloatAnchor *out)
static bool parse_float_relative(String relative, FloatRelative *out) static bool parse_float_relative(String relative, FloatRelative *out)
{ {
if (relative.size == 0) {
*out = (FloatRelative)0;
}
char *str = relative.data; char *str = relative.data;
if (striequal(str, "editor")) { if (striequal(str, "editor")) {
*out = kFloatRelativeEditor; *out = kFloatRelativeEditor;
@@ -700,8 +700,11 @@ static bool parse_float_relative(String relative, FloatRelative *out)
bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf, bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf,
Error *err) Error *err)
{ {
// TODO(bfredl): use a get/has_key interface instead and get rid of extra
// flags
bool has_row = false, has_col = false, has_relative = false; bool has_row = false, has_col = false, has_relative = false;
bool has_external = false, has_window = false; bool has_external = false, has_window = false;
bool has_width = false, has_height = false;
for (size_t i = 0; i < config.size; i++) { for (size_t i = 0; i < config.size; i++) {
char *key = config.items[i].key.data; char *key = config.items[i].key.data;
@@ -729,7 +732,8 @@ bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf,
return false; return false;
} }
} else if (strequal(key, "width")) { } else if (strequal(key, "width")) {
if (val.type == kObjectTypeInteger && val.data.integer >= 0) { has_width = true;
if (val.type == kObjectTypeInteger && val.data.integer > 0) {
fconfig->width = val.data.integer; fconfig->width = val.data.integer;
} else { } else {
api_set_error(err, kErrorTypeValidation, api_set_error(err, kErrorTypeValidation,
@@ -737,7 +741,8 @@ bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf,
return false; return false;
} }
} else if (strequal(key, "height")) { } else if (strequal(key, "height")) {
if (val.type == kObjectTypeInteger && val.data.integer >= 0) { has_height = true;
if (val.type == kObjectTypeInteger && val.data.integer > 0) {
fconfig->height= val.data.integer; fconfig->height= val.data.integer;
} else { } else {
api_set_error(err, kErrorTypeValidation, api_set_error(err, kErrorTypeValidation,
@@ -756,16 +761,19 @@ bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf,
return false; return false;
} }
} else if (!strcmp(key, "relative")) { } else if (!strcmp(key, "relative")) {
has_relative = true;
if (val.type != kObjectTypeString) { if (val.type != kObjectTypeString) {
api_set_error(err, kErrorTypeValidation, api_set_error(err, kErrorTypeValidation,
"'relative' key must be String"); "'relative' key must be String");
return false; return false;
} }
if (!parse_float_relative(val.data.string, &fconfig->relative)) { // ignore empty string, to match nvim_win_get_config
api_set_error(err, kErrorTypeValidation, if (val.data.string.size > 0) {
"Invalid value of 'relative' key"); has_relative = true;
return false; if (!parse_float_relative(val.data.string, &fconfig->relative)) {
api_set_error(err, kErrorTypeValidation,
"Invalid value of 'relative' key");
return false;
}
} }
} else if (!strcmp(key, "win")) { } else if (!strcmp(key, "win")) {
has_window = true; has_window = true;
@@ -828,6 +836,12 @@ bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf,
fconfig->external = false; fconfig->external = false;
} }
if (!reconf && !(has_height && has_width)) {
api_set_error(err, kErrorTypeValidation,
"Must specify 'width' and 'height'");
return false;
}
if (fconfig->external && !ui_has(kUIMultigrid)) { if (fconfig->external && !ui_has(kUIMultigrid)) {
api_set_error(err, kErrorTypeValidation, api_set_error(err, kErrorTypeValidation,
"UI doesn't support external windows"); "UI doesn't support external windows");
@@ -1135,6 +1149,8 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
} else if (wp->w_floating) { } else if (wp->w_floating) {
new_frame(wp); new_frame(wp);
wp->w_floating = false; wp->w_floating = false;
// non-floating window doesn't store float config.
wp->w_float_config = FLOAT_CONFIG_INIT;
} }
/* /*
@@ -4421,6 +4437,7 @@ static win_T *win_alloc(win_T *after, int hidden)
new_wp->w_cursor.lnum = 1; new_wp->w_cursor.lnum = 1;
new_wp->w_scbind_pos = 1; new_wp->w_scbind_pos = 1;
new_wp->w_floating = 0; new_wp->w_floating = 0;
new_wp->w_float_config = FLOAT_CONFIG_INIT;
/* We won't calculate w_fraction until resizing the window */ /* We won't calculate w_fraction until resizing the window */
new_wp->w_fraction = 0; new_wp->w_fraction = 0;
@@ -4717,8 +4734,6 @@ int win_comp_pos(void)
// Too often, but when we support anchoring floats to split windows, // Too often, but when we support anchoring floats to split windows,
// this will be needed // this will be needed
for (win_T *wp = lastwin; wp && wp->w_floating; wp = wp->w_prev) { for (win_T *wp = lastwin; wp && wp->w_floating; wp = wp->w_prev) {
wp->w_float_config.width = wp->w_width;
wp->w_float_config.height = wp->w_height;
win_config_float(wp, wp->w_float_config); win_config_float(wp, wp->w_float_config);
} }

View File

@@ -173,6 +173,13 @@ describe('floating windows', function()
local win = meths.open_win(buf, false, {relative='editor', width=20, height=2, row=3, col=5}) local win = meths.open_win(buf, false, {relative='editor', width=20, height=2, row=3, col=5})
local expected = {anchor='NW', col=5, external=false, focusable=true, height=2, relative='editor', row=3, width=20} local expected = {anchor='NW', col=5, external=false, focusable=true, height=2, relative='editor', row=3, width=20}
eq(expected, meths.win_get_config(win)) eq(expected, meths.win_get_config(win))
eq({relative='', external=false, focusable=true}, meths.win_get_config(0))
if multigrid then
meths.win_set_config(win, {external=true, width=10, height=1})
eq({external=true,focusable=true,width=10,height=1,relative=''}, meths.win_get_config(win))
end
end) end)
it('defaults to nonumber and NormalFloat highlight', function() it('defaults to nonumber and NormalFloat highlight', function()
@@ -253,6 +260,112 @@ describe('floating windows', function()
end end
end) end)
it('can have minimum size', function()
insert("the background text")
local buf = meths.create_buf(false, true)
meths.buf_set_lines(buf, 0, -1, true, {'x'})
local win = meths.open_win(buf, false, {relative='win', width=1, height=1, row=0, col=4, focusable=false})
if multigrid then
screen:expect{grid=[[
## grid 1
[2:----------------------------------------]|
[2:----------------------------------------]|
[2:----------------------------------------]|
[2:----------------------------------------]|
[2:----------------------------------------]|
[2:----------------------------------------]|
|
## grid 2
the background tex^t |
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
## grid 4
{1:x}|
]], float_pos={
[4] = {{id = 1002}, "NW", 2, 0, 4, false}
}}
else
screen:expect([[
the {1:x}ackground tex^t |
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
|
]])
end
meths.win_set_config(win, {relative='win', row=0, col=15})
if multigrid then
screen:expect{grid=[[
## grid 1
[2:----------------------------------------]|
[2:----------------------------------------]|
[2:----------------------------------------]|
[2:----------------------------------------]|
[2:----------------------------------------]|
[2:----------------------------------------]|
|
## grid 2
the background tex^t |
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
## grid 4
{1:x}|
]], float_pos={
[4] = {{id = 1002}, "NW", 2, 0, 15, false}
}}
else
screen:expect([[
the background {1:x}ex^t |
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
|
]])
end
meths.win_close(win,false)
if multigrid then
screen:expect([[
## grid 1
[2:----------------------------------------]|
[2:----------------------------------------]|
[2:----------------------------------------]|
[2:----------------------------------------]|
[2:----------------------------------------]|
[2:----------------------------------------]|
|
## grid 2
the background tex^t |
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
]])
else
screen:expect([[
the background tex^t |
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
|
]])
end
end)
it('API has proper error messages', function() it('API has proper error messages', function()
local buf = meths.create_buf(false,false) local buf = meths.create_buf(false,false)
eq({false, "Invalid key 'bork'"}, eq({false, "Invalid key 'bork'"},
@@ -271,6 +384,10 @@ describe('floating windows', function()
meth_pcall(meths.open_win,buf, false, {width=-1,height=2,relative='editor'})) meth_pcall(meths.open_win,buf, false, {width=-1,height=2,relative='editor'}))
eq({false, "'height' key must be a positive Integer"}, eq({false, "'height' key must be a positive Integer"},
meth_pcall(meths.open_win,buf, false, {width=20,height=-1,relative='editor'})) meth_pcall(meths.open_win,buf, false, {width=20,height=-1,relative='editor'}))
eq({false, "'height' key must be a positive Integer"},
meth_pcall(meths.open_win,buf, false, {width=20,height=0,relative='editor'}))
eq({false, "Must specify 'width' and 'height'"},
meth_pcall(meths.open_win,buf, false, {relative='editor'}))
end) end)
it('can be placed relative window or cursor', function() it('can be placed relative window or cursor', function()
@@ -836,15 +953,15 @@ describe('floating windows', function()
screen:expect([[ screen:expect([[
| |
{0:~ }| {0:~ }|
{0:~ }{1:such }{0: }|
{0:~ }{1:very }{0: }| {0:~ }{1:very }{0: }|
{0:~ }{1:^float }{0: }| {0:~ }{1:^float }{0: }|
{0:~ }| {0:~ }{2:~ }{0: }|
{0:~ }|
| |
]]) ]])
end end
meths.win_set_config(win, {width=0, height=3}) meths.win_set_config(win, {height=3})
feed('gg') feed('gg')
if multigrid then if multigrid then
screen:expect{grid=[[ screen:expect{grid=[[
@@ -1173,9 +1290,9 @@ describe('floating windows', function()
screen:expect([[ screen:expect([[
| |
{0:~ }| {0:~ }|
{0:~ }{1:^such }{0: }| {0:~ }{1:^such }{0: }|
{0:~ }| {0:~ }{1:very }{0: }|
{0:~ }| {0:~ }{1:float }{0: }|
{0:~ }| {0:~ }|
| |
]]) ]])
@@ -3256,7 +3373,7 @@ describe('floating windows', function()
end end
if multigrid then if multigrid then
meths.win_set_config(0, {external=true}) meths.win_set_config(0, {external=true, width=30, height=2})
expected_pos = {[3]={external=true}} expected_pos = {[3]={external=true}}
screen:expect{grid=[[ screen:expect{grid=[[
## grid 1 ## grid 1
@@ -3274,12 +3391,12 @@ describe('floating windows', function()
{0:~ }| {0:~ }|
{0:~ }| {0:~ }|
## grid 3 ## grid 3
^y | ^y |
{0:~ }| {0:~ }|
]], float_pos=expected_pos} ]], float_pos=expected_pos}
else else
eq({false, "UI doesn't support external windows"}, eq({false, "UI doesn't support external windows"},
meth_pcall(meths.win_set_config, 0, {external=true})) meth_pcall(meths.win_set_config, 0, {external=true, width=30, height=2}))
return return
end end
@@ -3547,7 +3664,8 @@ describe('floating windows', function()
it(":tabnew and :tabnext (external)", function() it(":tabnew and :tabnext (external)", function()
if multigrid then if multigrid then
meths.win_set_config(win, {external=true}) -- also test external window wider than main screen
meths.win_set_config(win, {external=true, width=65, height=4})
expected_pos = {[3]={external=true}} expected_pos = {[3]={external=true}}
feed(":tabnew<cr>") feed(":tabnew<cr>")
screen:expect{grid=[[ screen:expect{grid=[[
@@ -3567,8 +3685,10 @@ describe('floating windows', function()
{0:~ }| {0:~ }|
{0:~ }| {0:~ }|
## grid 3 ## grid 3
y | y |
{0:~ }| {0:~ }|
{0:~ }|
{0:~ }|
## grid 4 ## grid 4
^ | ^ |
{0:~ }| {0:~ }|
@@ -3578,7 +3698,7 @@ describe('floating windows', function()
]], float_pos=expected_pos} ]], float_pos=expected_pos}
else else
eq({false, "UI doesn't support external windows"}, eq({false, "UI doesn't support external windows"},
meth_pcall(meths.win_set_config, 0, {external=true})) meth_pcall(meths.win_set_config, 0, {external=true, width=65, height=4}))
end end
feed(":tabnext<cr>") feed(":tabnext<cr>")
@@ -3599,8 +3719,10 @@ describe('floating windows', function()
{0:~ }| {0:~ }|
{0:~ }| {0:~ }|
## grid 3 ## grid 3
y | y |
{0:~ }| {0:~ }|
{0:~ }|
{0:~ }|
## grid 4 ## grid 4
| |
{0:~ }| {0:~ }|
@@ -3628,8 +3750,10 @@ describe('floating windows', function()
{0:~ }| {0:~ }|
{0:~ }| {0:~ }|
## grid 3 ## grid 3
y | y |
{0:~ }| {0:~ }|
{0:~ }|
{0:~ }|
## grid 4 ## grid 4
^ | ^ |
{0:~ }| {0:~ }|