floats: implement floating windows

Co-Author: Dongdong Zhou <dzhou121@gmail.com>
This commit is contained in:
Björn Linse 2017-04-26 15:28:10 +02:00
parent 018e0d5a19
commit 9a1675b065
30 changed files with 4068 additions and 219 deletions

View File

@ -520,19 +520,33 @@ tabs.
size). If the window was previously hidden, it should now be shown size). If the window was previously hidden, it should now be shown
again. again.
["win_float_pos", grid, win, anchor, anchor_grid, anchor_row, anchor_col, focusable]
Display or reconfigure floating window `win`. The window should be
displayed above another grid `anchor_grid` at the specified position
`anchor_row` and `anchor_col`. For the meaning of `anchor` and more
details of positioning, see |nvim_open_win()|.
["win_external_pos", grid, win]
Display or reconfigure external window `win`. The window should be
displayed as a separate top-level window in the desktop envirionment,
or something similar.
["win_hide", grid] ["win_hide", grid]
Stop displaying the window. Stop displaying the window. The window can be shown again later.
["win_scroll_over_start"] ["win_scroll_over_start"]
Hint that following `grid_scroll` on the default grid should Hint that following `grid_scroll` on the default grid should
scroll over windows. This is a temporary workaround to allow scroll over windows. This is a temporary workaround to allow
UIs to use the builtin message drawing. Later on, messages will be UIs to use the builtin message drawing. Later on, messages will be
drawn on a dedicated grid. drawn on a dedicated grid. Using |ui-messages| also avoids this issue.
["win_scroll_over_reset"] ["win_scroll_over_reset"]
Hint that scrolled over windows should be redrawn again, and not be Hint that scrolled over windows should be redrawn again, and not be
overdrawn by default grid scrolling anymore. overdrawn by default grid scrolling anymore.
["win_close", grid]
Close the window.
See |ui-linegrid| for grid events. See |ui-linegrid| for grid events.
See |nvim_ui_try_resize_grid| in |api-ui| to request changing the grid size. See |nvim_ui_try_resize_grid| in |api-ui| to request changing the grid size.
See |nvim_input_mouse| for sending mouse events to Nvim. See |nvim_input_mouse| for sending mouse events to Nvim.

View File

@ -197,6 +197,10 @@ CTRL-W ^ Does ":split #", split window in two and edit alternate file.
When a count is given, it becomes ":split #N", split window When a count is given, it becomes ":split #N", split window
and edit buffer N. and edit buffer N.
CTRL-W ge *CTRL-W_ge*
Detach the current window as an external window.
Only available when using an UI with |ui-multigrid| support.
Note that the 'splitbelow' and 'splitright' options influence where a new Note that the 'splitbelow' and 'splitright' options influence where a new
window will appear. window will appear.

View File

@ -1004,7 +1004,9 @@ static void init_ui_event_metadata(Dictionary *metadata)
Array ui_options = ARRAY_DICT_INIT; Array ui_options = ARRAY_DICT_INIT;
ADD(ui_options, STRING_OBJ(cstr_to_string("rgb"))); ADD(ui_options, STRING_OBJ(cstr_to_string("rgb")));
for (UIExtension i = 0; i < kUIExtCount; i++) { for (UIExtension i = 0; i < kUIExtCount; i++) {
ADD(ui_options, STRING_OBJ(cstr_to_string(ui_ext_names[i]))); if (ui_ext_names[i][0] != '_') {
ADD(ui_options, STRING_OBJ(cstr_to_string(ui_ext_names[i])));
}
} }
PUT(*metadata, "ui_options", ARRAY_OBJ(ui_options)); PUT(*metadata, "ui_options", ARRAY_OBJ(ui_options));
} }

View File

@ -17,6 +17,8 @@
#include "nvim/popupmnu.h" #include "nvim/popupmnu.h"
#include "nvim/cursor_shape.h" #include "nvim/cursor_shape.h"
#include "nvim/highlight.h" #include "nvim/highlight.h"
#include "nvim/screen.h"
#include "nvim/window.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS #ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/ui.c.generated.h" # include "api/ui.c.generated.h"

View File

@ -76,7 +76,7 @@ void hl_attr_define(Integer id, HlAttrs rgb_attrs, HlAttrs cterm_attrs,
void grid_resize(Integer grid, Integer width, Integer height) void grid_resize(Integer grid, Integer width, Integer height)
FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL; FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL;
void grid_clear(Integer grid) void grid_clear(Integer grid)
FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL; FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL;
void grid_cursor_goto(Integer grid, Integer row, Integer col) void grid_cursor_goto(Integer grid, Integer row, Integer col)
FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL; FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL;
void grid_line(Integer grid, Integer row, Integer col_start, Array data) void grid_line(Integer grid, Integer row, Integer col_start, Array data)
@ -101,8 +101,15 @@ void event(char *name, Array args, bool *args_consumed)
void win_pos(Integer grid, Integer win, Integer startrow, void win_pos(Integer grid, Integer win, Integer startrow,
Integer startcol, Integer width, Integer height) Integer startcol, Integer width, Integer height)
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY;
void win_float_pos(Integer grid, Window win, String anchor, Integer anchor_grid,
Float anchor_row, Float anchor_col, Boolean focusable)
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY;
void win_external_pos(Integer grid, Window win)
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY;
void win_hide(Integer grid) void win_hide(Integer grid)
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY;
void win_close(Integer grid)
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY;
void win_scroll_over_start(void) void win_scroll_over_start(void)
FUNC_API_SINCE(6) FUNC_API_BRIDGE_IMPL FUNC_API_COMPOSITOR_IMPL; FUNC_API_SINCE(6) FUNC_API_BRIDGE_IMPL FUNC_API_COMPOSITOR_IMPL;
void win_scroll_over_reset(void) void win_scroll_over_reset(void)

View File

@ -987,6 +987,84 @@ Buffer nvim_create_buf(Boolean listed, Boolean scratch, Error *err)
return buf->b_fnum; return buf->b_fnum;
} }
/// 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.
///
/// Exactly one of `external` and `relative` must be specified.
///
/// @param buffer handle of buffer to be displayed in the window
/// @param enter whether the window should be entered (made the current window)
/// @param width width of window (in character cells)
/// @param height height of window (in character cells)
/// @param 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.
///
/// @param[out] err Error details, if any
/// @return the window handle or 0 when error
Window nvim_open_win(Buffer buffer, Boolean enter,
Integer width, Integer height,
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;
}
win_T *wp = win_new_float(NULL, (int)width, (int)height, config, err);
if (!wp) {
return 0;
}
if (buffer > 0) {
nvim_set_current_buf(buffer, err);
}
if (!enter) {
win_enter(old, false);
}
return wp->handle;
}
/// Gets the current list of tabpage handles. /// Gets the current list of tabpage handles.
/// ///
/// @return List of tabpage handles /// @return List of tabpage handles

View File

@ -432,3 +432,41 @@ Boolean nvim_win_is_valid(Window window)
return ret; return ret;
} }
/// 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.
void nvim_win_config(Window window, Integer width, Integer height,
Dictionary options, Error *err)
FUNC_API_SINCE(6)
{
win_T *win = find_window_by_handle(window, err);
if (!win) {
return;
}
bool new_float = !win->w_floating;
width = width > 0 ? width: win->w_width;
height = height > 0 ? height : win->w_height;
// reuse old values, if not overriden
FloatConfig config = new_float ? FLOAT_CONFIG_INIT : win->w_float_config;
if (!parse_float_config(options, &config, !new_float, err)) {
return;
}
if (new_float) {
if (!win_new_float(win, (int)width, (int)height, config, err)) {
return;
}
redraw_later(NOT_VALID);
} else {
win_config_float(win, (int)width, (int)height, config);
win->w_pos_changed = true;
}
}

View File

@ -474,8 +474,8 @@ void close_buffer(win_T *win, buf_T *buf, int action, int abort_if_last)
return; return;
} }
buf->b_locked--; buf->b_locked--;
if (abort_if_last && one_window()) { if (abort_if_last && last_nonfloat(win)) {
/* Autocommands made this the only window. */ // Autocommands made this the only window.
EMSG(_(e_auabort)); EMSG(_(e_auabort));
return; return;
} }
@ -491,8 +491,8 @@ void close_buffer(win_T *win, buf_T *buf, int action, int abort_if_last)
return; return;
} }
buf->b_locked--; buf->b_locked--;
if (abort_if_last && one_window()) { if (abort_if_last && last_nonfloat(win)) {
/* Autocommands made this the only window. */ // Autocommands made this the only window.
EMSG(_(e_auabort)); EMSG(_(e_auabort));
return; return;
} }

View File

@ -958,6 +958,35 @@ struct matchitem {
int conceal_char; ///< cchar for Conceal highlighting int conceal_char; ///< cchar for Conceal highlighting
}; };
typedef enum {
kFloatAnchorEast = 1,
kFloatAnchorSouth = 2,
kFloatAnchorNW = 0,
kFloatAnchorNE = 1,
kFloatAnchorSW = 2,
kFloatAnchorSE = 3,
} FloatAnchor;
typedef enum {
kFloatRelativeEditor = 0,
kFloatRelativeWindow = 1,
kFloatRelativeCursor = 2,
} FloatRelative;
typedef struct {
Window window;
double row, col;
FloatAnchor anchor;
FloatRelative relative;
bool external;
bool focusable;
} FloatConfig;
#define FLOAT_CONFIG_INIT ((FloatConfig){ .row = 0, .col = 0, .anchor = 0, \
.relative = 0, .external = false, \
.focusable = true })
/* /*
* Structure which contains all information that belongs to a window * Structure which contains all information that belongs to a window
* *
@ -1221,6 +1250,8 @@ struct window_S {
ScreenGrid w_grid; // the grid specific to the window ScreenGrid w_grid; // the grid specific to the window
bool w_pos_changed; // true if window position changed bool w_pos_changed; // true if window position changed
bool w_floating; ///< whether the window is floating
FloatConfig w_float_config;
/* /*
* w_fraction is the fractional row of the cursor within the window, from * w_fraction is the fractional row of the cursor within the window, from

View File

@ -6285,6 +6285,9 @@ void tabpage_close(int forceit)
{ {
// First close all the windows but the current one. If that worked then // First close all the windows but the current one. If that worked then
// close the last window in this tab, that will close it. // close the last window in this tab, that will close it.
while (curwin->w_floating) {
ex_win_close(forceit, curwin, NULL);
}
if (!ONE_WINDOW) { if (!ONE_WINDOW) {
close_others(true, forceit); close_others(true, forceit);
} }
@ -6309,8 +6312,8 @@ void tabpage_close_other(tabpage_T *tp, int forceit)
/* Limit to 1000 windows, autocommands may add a window while we close /* Limit to 1000 windows, autocommands may add a window while we close
* one. OK, so I'm paranoid... */ * one. OK, so I'm paranoid... */
while (++done < 1000) { while (++done < 1000) {
sprintf((char *)prev_idx, "%i", tabpage_index(tp)); snprintf((char *)prev_idx, sizeof(prev_idx), "%i", tabpage_index(tp));
wp = tp->tp_firstwin; wp = tp->tp_lastwin;
ex_win_close(forceit, wp, tp); ex_win_close(forceit, wp, tp);
/* Autocommands may delete the tab page under our fingers and we may /* Autocommands may delete the tab page under our fingers and we may
@ -6331,6 +6334,7 @@ static void ex_only(exarg_T *eap)
{ {
win_T *wp; win_T *wp;
int wnr; int wnr;
if (eap->addr_count > 0) { if (eap->addr_count > 0) {
wnr = eap->line2; wnr = eap->line2;
for (wp = firstwin; --wnr > 0;) { for (wp = firstwin; --wnr > 0;) {
@ -6339,6 +6343,10 @@ static void ex_only(exarg_T *eap)
else else
wp = wp->w_next; wp = wp->w_next;
} }
} else {
wp = curwin;
}
if (wp != curwin) {
win_goto(wp); win_goto(wp);
} }
close_others(TRUE, eap->forceit); close_others(TRUE, eap->forceit);

View File

@ -3470,11 +3470,13 @@ void redrawcmd(void)
void compute_cmdrow(void) void compute_cmdrow(void)
{ {
if (exmode_active || msg_scrolled != 0) if (exmode_active || msg_scrolled != 0) {
cmdline_row = Rows - 1; cmdline_row = Rows - 1;
else } else {
cmdline_row = lastwin->w_winrow + lastwin->w_height win_T *wp = lastwin_nofloating();
+ lastwin->w_status_height; cmdline_row = wp->w_winrow + wp->w_height
+ wp->w_status_height;
}
} }
static void cursorcmd(void) static void cursorcmd(void)

View File

@ -1049,6 +1049,10 @@ EXTERN char_u e_cmdmap_repeated[] INIT(=N_(
"E5521: <Cmd> mapping must end with <CR> before second <Cmd>")); "E5521: <Cmd> mapping must end with <CR> before second <Cmd>"));
EXTERN char_u e_cmdmap_key[] INIT(=N_( EXTERN char_u e_cmdmap_key[] INIT(=N_(
"E5522: <Cmd> mapping must not include %s key")); "E5522: <Cmd> mapping must not include %s key"));
EXTERN char_u e_floatonly[] INIT(=N_(
"E5601: Cannot close window, only floating window would remain"));
EXTERN char_u e_floatexchange[] INIT(=N_(
"E5602: Cannot exchange or rotate float"));
EXTERN char top_bot_msg[] INIT(= N_("search hit TOP, continuing at BOTTOM")); EXTERN char top_bot_msg[] INIT(= N_("search hit TOP, continuing at BOTTOM"));

View File

@ -47,6 +47,9 @@ typedef struct {
int Rows; int Rows;
int Columns; int Columns;
// The state of the grid is valid. Otherwise it needs to be redrawn.
bool valid;
// offsets for the grid relative to the global screen // offsets for the grid relative to the global screen
int row_offset; int row_offset;
int col_offset; int col_offset;
@ -58,7 +61,7 @@ typedef struct {
bool comp_disabled; bool comp_disabled;
} ScreenGrid; } ScreenGrid;
#define SCREEN_GRID_INIT { 0, NULL, NULL, NULL, NULL, 0, 0, 0, 0, 0, 0, 0, \ #define SCREEN_GRID_INIT { 0, NULL, NULL, NULL, NULL, 0, 0, false, 0, 0, 0, \
false } 0, 0, false }
#endif // NVIM_GRID_DEFS_H #endif // NVIM_GRID_DEFS_H

View File

@ -46,6 +46,7 @@
#include "nvim/os/os_defs.h" #include "nvim/os/os_defs.h"
#include "nvim/path.h" #include "nvim/path.h"
#include "nvim/profile.h" #include "nvim/profile.h"
#include "nvim/popupmnu.h"
#include "nvim/quickfix.h" #include "nvim/quickfix.h"
#include "nvim/screen.h" #include "nvim/screen.h"
#include "nvim/state.h" #include "nvim/state.h"

View File

@ -572,8 +572,13 @@ void free_all_mem(void)
p_ea = false; p_ea = false;
if (first_tabpage->tp_next != NULL) if (first_tabpage->tp_next != NULL)
do_cmdline_cmd("tabonly!"); do_cmdline_cmd("tabonly!");
if (!ONE_WINDOW)
if (!ONE_WINDOW) {
// to keep things simple, don't perform this
// ritual inside a float
curwin = firstwin;
do_cmdline_cmd("only!"); do_cmdline_cmd("only!");
}
/* Free all spell info. */ /* Free all spell info. */
spell_free_all(); spell_free_all();

View File

@ -2777,9 +2777,11 @@ void msg_ext_flush_showmode(void)
{ {
// Showmode messages doesn't interrupt normal message flow, so we use // Showmode messages doesn't interrupt normal message flow, so we use
// separate event. Still reuse the same chunking logic, for simplicity. // separate event. Still reuse the same chunking logic, for simplicity.
msg_ext_emit_chunk(); if (ui_has(kUIMessages)) {
ui_call_msg_showmode(msg_ext_chunks); msg_ext_emit_chunk();
msg_ext_chunks = (Array)ARRAY_DICT_INIT; ui_call_msg_showmode(msg_ext_chunks);
msg_ext_chunks = (Array)ARRAY_DICT_INIT;
}
} }
void msg_ext_clear(bool force) void msg_ext_clear(bool force)

View File

@ -12,6 +12,7 @@
#include "nvim/screen.h" #include "nvim/screen.h"
#include "nvim/syntax.h" #include "nvim/syntax.h"
#include "nvim/ui.h" #include "nvim/ui.h"
#include "nvim/ui_compositor.h"
#include "nvim/os_unix.h" #include "nvim/os_unix.h"
#include "nvim/fold.h" #include "nvim/fold.h"
#include "nvim/diff.h" #include "nvim/diff.h"
@ -441,12 +442,6 @@ win_T *mouse_find_win(int *gridp, int *rowp, int *colp)
return wp_grid; return wp_grid;
} }
// TODO(bfredl): grid zero will have floats displayed on it, and will
// be adjusted to float grids.
if (*gridp == 0) {
*gridp = DEFAULT_GRID_HANDLE;
}
frame_T *fp; frame_T *fp;
fp = topframe; fp = topframe;
@ -478,15 +473,31 @@ win_T *mouse_find_win(int *gridp, int *rowp, int *colp)
return NULL; return NULL;
} }
static win_T *mouse_find_grid_win(int *grid, int *rowp, int *colp) static win_T *mouse_find_grid_win(int *gridp, int *rowp, int *colp)
{ {
if (*grid > 1) { if (*gridp > 1) {
win_T *wp = get_win_by_grid_handle(*grid); win_T *wp = get_win_by_grid_handle(*gridp);
if (wp && wp->w_grid.chars) { if (wp && wp->w_grid.chars
&& !(wp->w_floating && !wp->w_float_config.focusable)) {
*rowp = MIN(*rowp, wp->w_grid.Rows-1); *rowp = MIN(*rowp, wp->w_grid.Rows-1);
*colp = MIN(*colp, wp->w_grid.Columns-1); *colp = MIN(*colp, wp->w_grid.Columns-1);
return wp; return wp;
} }
} else if (*gridp == 0) {
ScreenGrid *grid = ui_comp_mouse_focus(*rowp, *colp);
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (&wp->w_grid != grid || !wp->w_float_config.focusable) {
continue;
}
*gridp = grid->handle;
*rowp -= grid->comp_row;
*colp -= grid->comp_col;
return wp;
}
// no float found, click on the default grid
// TODO(bfredl): grid can be &pum_grid, allow select pum items by mouse?
*gridp = DEFAULT_GRID_HANDLE;
} }
return NULL; return NULL;
} }

View File

@ -2200,6 +2200,7 @@ do_mouse (
* one. Speeds up dragging the status line. */ * one. Speeds up dragging the status line. */
if (vpeekc() != NUL) { if (vpeekc() != NUL) {
int nc; int nc;
int save_mouse_grid = mouse_grid;
int save_mouse_row = mouse_row; int save_mouse_row = mouse_row;
int save_mouse_col = mouse_col; int save_mouse_col = mouse_col;
@ -2209,6 +2210,7 @@ do_mouse (
if (c == nc) if (c == nc)
continue; continue;
vungetc(nc); vungetc(nc);
mouse_grid = save_mouse_grid;
mouse_row = save_mouse_row; mouse_row = save_mouse_row;
mouse_col = save_mouse_col; mouse_col = save_mouse_col;
} }

View File

@ -331,7 +331,6 @@ static unsigned int handle_mouse_event(char **ptr, uint8_t *buf,
uint8_t modifiers = check_multiclick(mouse_code, mouse_grid, uint8_t modifiers = check_multiclick(mouse_code, mouse_grid,
mouse_row, mouse_col); mouse_row, mouse_col);
if (modifiers) { if (modifiers) {
if (buf[1] != KS_MODIFIER) { if (buf[1] != KS_MODIFIER) {
// no modifiers in the buffer yet, shift the bytes 3 positions // no modifiers in the buffer yet, shift the bytes 3 positions

View File

@ -38,16 +38,16 @@ static int pum_base_width; // width of pum items base
static int pum_kind_width; // width of pum items kind column static int pum_kind_width; // width of pum items kind column
static int pum_scrollbar; // TRUE when scrollbar present static int pum_scrollbar; // TRUE when scrollbar present
static int pum_anchor_grid; // grid where position is defined
static int pum_row; // top row of pum static int pum_row; // top row of pum
static int pum_col; // left column of pum static int pum_col; // left column of pum
static bool pum_above; // pum is drawn above cursor line
static bool pum_is_visible = false; static bool pum_is_visible = false;
static bool pum_is_drawn = false; static bool pum_is_drawn = false;
static bool pum_external = false; static bool pum_external = false;
static bool pum_invalid = false; // the screen was just cleared static bool pum_invalid = false; // the screen was just cleared
static ScreenGrid pum_grid = SCREEN_GRID_INIT;
#ifdef INCLUDE_GENERATED_DECLARATIONS #ifdef INCLUDE_GENERATED_DECLARATIONS
# include "popupmnu.c.generated.h" # include "popupmnu.c.generated.h"
#endif #endif
@ -103,9 +103,9 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed)
col = curwin->w_wcol; col = curwin->w_wcol;
} }
int grid = (int)curwin->w_grid.handle; pum_anchor_grid = (int)curwin->w_grid.handle;
if (!ui_has(kUIMultigrid)) { if (!ui_has(kUIMultigrid)) {
grid = (int)default_grid.handle; pum_anchor_grid = (int)default_grid.handle;
row += curwin->w_winrow; row += curwin->w_winrow;
col += curwin->w_wincol; col += curwin->w_wincol;
} }
@ -121,7 +121,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed)
ADD(item, STRING_OBJ(cstr_to_string((char *)array[i].pum_info))); ADD(item, STRING_OBJ(cstr_to_string((char *)array[i].pum_info)));
ADD(arr, ARRAY_OBJ(item)); ADD(arr, ARRAY_OBJ(item));
} }
ui_call_popupmenu_show(arr, selected, row, col, grid); ui_call_popupmenu_show(arr, selected, row, col, pum_anchor_grid);
} else { } else {
ui_call_popupmenu_select(selected); ui_call_popupmenu_select(selected);
} }
@ -165,6 +165,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed)
if (row + 2 >= below_row - pum_height if (row + 2 >= below_row - pum_height
&& row - above_row > (below_row - above_row) / 2) { && row - above_row > (below_row - above_row) / 2) {
// pum above "row" // pum above "row"
pum_above = true;
// Leave two lines of context if possible // Leave two lines of context if possible
if (curwin->w_wrow - curwin->w_cline_row >= 2) { if (curwin->w_wrow - curwin->w_cline_row >= 2) {
@ -187,6 +188,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed)
} }
} else { } else {
// pum below "row" // pum below "row"
pum_above = false;
// Leave two lines of context if possible // Leave two lines of context if possible
if (curwin->w_cline_row + curwin->w_cline_height - curwin->w_wrow >= 3) { if (curwin->w_cline_row + curwin->w_cline_height - curwin->w_wrow >= 3) {
@ -360,7 +362,7 @@ void pum_redraw(void)
grid_assign_handle(&pum_grid); grid_assign_handle(&pum_grid);
bool moved = ui_comp_put_grid(&pum_grid, pum_row, pum_col-col_off, bool moved = ui_comp_put_grid(&pum_grid, pum_row, pum_col-col_off,
pum_height, grid_width); pum_height, grid_width, false, true);
bool invalid_grid = moved || pum_invalid; bool invalid_grid = moved || pum_invalid;
pum_invalid = false; pum_invalid = false;
@ -371,6 +373,13 @@ void pum_redraw(void)
} else if (invalid_grid) { } else if (invalid_grid) {
grid_invalidate(&pum_grid); grid_invalidate(&pum_grid);
} }
if (ui_has(kUIMultigrid)) {
const char *anchor = pum_above ? "SW" : "NW";
int row_off = pum_above ? pum_height : 0;
ui_call_win_float_pos(pum_grid.handle, -1, cstr_to_string(anchor),
pum_anchor_grid, pum_row-row_off, pum_col-col_off,
false);
}
// Never display more than we have // Never display more than we have
@ -783,6 +792,10 @@ void pum_check_clear(void)
ui_call_popupmenu_hide(); ui_call_popupmenu_hide();
} else { } else {
ui_comp_remove_grid(&pum_grid); ui_comp_remove_grid(&pum_grid);
if (ui_has(kUIMultigrid)) {
ui_call_win_close(pum_grid.handle);
ui_call_grid_destroy(pum_grid.handle);
}
// TODO(bfredl): consider keeping float grids allocated. // TODO(bfredl): consider keeping float grids allocated.
grid_free(&pum_grid); grid_free(&pum_grid);
} }

View File

@ -1,6 +1,8 @@
#ifndef NVIM_POPUPMNU_H #ifndef NVIM_POPUPMNU_H
#define NVIM_POPUPMNU_H #define NVIM_POPUPMNU_H
#include "nvim/macros.h"
#include "nvim/grid_defs.h"
#include "nvim/types.h" #include "nvim/types.h"
/// Used for popup menu items. /// Used for popup menu items.
@ -11,6 +13,7 @@ typedef struct {
char_u *pum_info; // extra info char_u *pum_info; // extra info
} pumitem_T; } pumitem_T;
EXTERN ScreenGrid pum_grid INIT(= SCREEN_GRID_INIT);
#ifdef INCLUDE_GENERATED_DECLARATIONS #ifdef INCLUDE_GENERATED_DECLARATIONS
# include "popupmnu.h.generated.h" # include "popupmnu.h.generated.h"

View File

@ -148,9 +148,6 @@ typedef struct {
/// Whether to call "ui_call_grid_resize" in win_grid_alloc /// Whether to call "ui_call_grid_resize" in win_grid_alloc
static bool send_grid_resize = false; static bool send_grid_resize = false;
/// Highlight ids are no longer valid. Force retransmission
static bool highlights_invalid = false;
static bool conceal_cursor_used = false; static bool conceal_cursor_used = false;
static bool redraw_popupmenu = false; static bool redraw_popupmenu = false;
@ -197,8 +194,10 @@ void redraw_all_later(int type)
void screen_invalidate_highlights(void) void screen_invalidate_highlights(void)
{ {
redraw_all_later(NOT_VALID); FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
highlights_invalid = true; redraw_win_later(wp, NOT_VALID);
wp->w_grid.valid = false;
}
} }
/* /*
@ -338,6 +337,9 @@ void update_screen(int type)
grid_ins_lines(&default_grid, 0, msg_scrolled, (int)Rows, grid_ins_lines(&default_grid, 0, msg_scrolled, (int)Rows,
0, (int)Columns); 0, (int)Columns);
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (wp->w_floating) {
continue;
}
if (wp->w_winrow < msg_scrolled) { if (wp->w_winrow < msg_scrolled) {
if (W_ENDROW(wp) > msg_scrolled if (W_ENDROW(wp) > msg_scrolled
&& wp->w_redr_type < REDRAW_TOP && wp->w_redr_type < REDRAW_TOP
@ -361,6 +363,10 @@ void update_screen(int type)
need_wait_return = FALSE; need_wait_return = FALSE;
} }
if (type >= NOT_VALID) {
ui_comp_set_screen_valid(false);
}
win_ui_flush_positions();
msg_ext_check_prompt(); msg_ext_check_prompt();
/* reset cmdline_row now (may have been changed temporarily) */ /* reset cmdline_row now (may have been changed temporarily) */
@ -376,9 +382,11 @@ void update_screen(int type)
type = NOT_VALID; type = NOT_VALID;
// must_redraw may be set indirectly, avoid another redraw later // must_redraw may be set indirectly, avoid another redraw later
must_redraw = 0; must_redraw = 0;
} else if (highlights_invalid) { } else if (!default_grid.valid) {
grid_invalidate(&default_grid); grid_invalidate(&default_grid);
default_grid.valid = true;
} }
ui_comp_set_screen_valid(true);
if (clear_cmdline) /* going to clear cmdline (done below) */ if (clear_cmdline) /* going to clear cmdline (done below) */
check_for_delay(FALSE); check_for_delay(FALSE);
@ -449,7 +457,14 @@ void update_screen(int type)
*/ */
did_one = FALSE; did_one = FALSE;
search_hl.rm.regprog = NULL; search_hl.rm.regprog = NULL;
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (wp->w_redr_type == CLEAR && wp->w_floating && wp->w_grid.chars) {
grid_invalidate(&wp->w_grid);
wp->w_redr_type = NOT_VALID;
}
if (wp->w_redr_type != 0) { if (wp->w_redr_type != 0) {
if (!did_one) { if (!did_one) {
did_one = TRUE; did_one = TRUE;
@ -471,7 +486,6 @@ void update_screen(int type)
} }
send_grid_resize = false; send_grid_resize = false;
highlights_invalid = false;
redraw_popupmenu = false; redraw_popupmenu = false;
/* Reset b_mod_set flags. Going through all windows is probably faster /* Reset b_mod_set flags. Going through all windows is probably faster
@ -4287,9 +4301,12 @@ win_line (
/// If UI did not request multigrid support, draw all windows on the /// If UI did not request multigrid support, draw all windows on the
/// default_grid. /// default_grid.
/// ///
/// NB: this function can only been used with window grids in a context where
/// win_grid_alloc already has been called!
///
/// If the default_grid is used, adjust window relative positions to global /// If the default_grid is used, adjust window relative positions to global
/// screen positions. /// screen positions.
static void screen_adjust_grid(ScreenGrid **grid, int *row_off, int *col_off) void screen_adjust_grid(ScreenGrid **grid, int *row_off, int *col_off)
{ {
if (!(*grid)->chars && *grid != &default_grid) { if (!(*grid)->chars && *grid != &default_grid) {
*row_off += (*grid)->row_offset; *row_off += (*grid)->row_offset;
@ -5292,7 +5309,6 @@ void grid_getbytes(ScreenGrid *grid, int row, int col, char_u *bytes,
screen_adjust_grid(&grid, &row, &col); screen_adjust_grid(&grid, &row, &col);
// safety check // safety check
if (grid->chars != NULL && row < grid->Rows && col < grid->Columns) { if (grid->chars != NULL && row < grid->Rows && col < grid->Columns) {
off = grid->line_offset[row] + col; off = grid->line_offset[row] + col;
@ -5909,14 +5925,9 @@ void win_grid_alloc(win_T *wp)
int rows = wp->w_height_inner; int rows = wp->w_height_inner;
int cols = wp->w_width_inner; int cols = wp->w_width_inner;
// TODO(bfredl): floating windows should force this to true bool want_allocation = ui_has(kUIMultigrid) || wp->w_floating;
bool want_allocation = ui_has(kUIMultigrid);
bool has_allocation = (grid->chars != NULL); bool has_allocation = (grid->chars != NULL);
if (want_allocation && has_allocation && highlights_invalid) {
grid_invalidate(grid);
}
if (grid->Rows != rows) { if (grid->Rows != rows) {
wp->w_lines_valid = 0; wp->w_lines_valid = 0;
xfree(wp->w_lines); xfree(wp->w_lines);
@ -5928,15 +5939,20 @@ void win_grid_alloc(win_T *wp)
|| grid->Rows != rows || grid->Rows != rows
|| grid->Columns != cols) { || grid->Columns != cols) {
if (want_allocation) { if (want_allocation) {
grid_alloc(grid, rows, cols, true, true); grid_alloc(grid, rows, cols, wp->w_grid.valid, wp->w_grid.valid);
grid->valid = true;
} else { } else {
// Single grid mode, all rendering will be redirected to default_grid. // Single grid mode, all rendering will be redirected to default_grid.
// Only keep track of the size and offset of the window. // Only keep track of the size and offset of the window.
grid_free(grid); grid_free(grid);
grid->Rows = rows; grid->Rows = rows;
grid->Columns = cols; grid->Columns = cols;
grid->valid = false;
} }
was_resized = true; was_resized = true;
} else if (want_allocation && has_allocation && !wp->w_grid.valid) {
grid_invalidate(grid);
grid->valid = true;
} }
grid->row_offset = wp->w_winrow; grid->row_offset = wp->w_winrow;
@ -5946,7 +5962,7 @@ void win_grid_alloc(win_T *wp)
// - a grid was just resized // - a grid was just resized
// - screen_resize was called and all grid sizes must be sent // - screen_resize was called and all grid sizes must be sent
// - the UI wants multigrid event (necessary) // - the UI wants multigrid event (necessary)
if ((send_grid_resize || was_resized) && ui_has(kUIMultigrid)) { if ((send_grid_resize || was_resized) && want_allocation) {
ui_call_grid_resize(grid->handle, grid->Columns, grid->Rows); ui_call_grid_resize(grid->handle, grid->Columns, grid->Rows);
} }
} }
@ -6008,7 +6024,7 @@ retry:
// win_new_shellsize will recompute floats position, but tell the // win_new_shellsize will recompute floats position, but tell the
// compositor to not redraw them yet // compositor to not redraw them yet
ui_comp_invalidate_screen(); ui_comp_set_screen_valid(false);
win_new_shellsize(); /* fit the windows in the new sized shell */ win_new_shellsize(); /* fit the windows in the new sized shell */
@ -6162,6 +6178,8 @@ void screenclear(void)
} }
ui_call_grid_clear(1); // clear the display ui_call_grid_clear(1); // clear the display
ui_comp_set_screen_valid(true);
clear_cmdline = false; clear_cmdline = false;
mode_displayed = false; mode_displayed = false;
@ -6170,6 +6188,11 @@ void screenclear(void)
redraw_tabline = true; redraw_tabline = true;
redraw_popupmenu = true; redraw_popupmenu = true;
pum_invalidate(); pum_invalidate();
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (wp->w_floating) {
wp->w_redr_type = CLEAR;
}
}
if (must_redraw == CLEAR) { if (must_redraw == CLEAR) {
must_redraw = NOT_VALID; // no need to clear again must_redraw = NOT_VALID; // no need to clear again
} }
@ -6559,14 +6582,14 @@ int showmode(void)
if (VIsual_active) if (VIsual_active)
clear_showcmd(); clear_showcmd();
/* If the last window has no status line, the ruler is after the mode // If the last window has no status line, the ruler is after the mode
* message and must be redrawn */ // message and must be redrawn
if (redrawing() win_T *last = lastwin_nofloating();
&& lastwin->w_status_height == 0 if (redrawing() && last->w_status_height == 0) {
) win_redr_ruler(last, true);
win_redr_ruler(lastwin, TRUE); }
redraw_cmdline = FALSE; redraw_cmdline = false;
clear_cmdline = FALSE; clear_cmdline = false;
return length; return length;
} }
@ -7150,7 +7173,7 @@ void screen_resize(int width, int height)
check_shellsize(); check_shellsize();
height = Rows; height = Rows;
width = Columns; width = Columns;
ui_resize(width, height); ui_call_grid_resize(1, width, height);
send_grid_resize = true; send_grid_resize = true;

View File

@ -165,6 +165,7 @@ UI *tui_start(void)
memset(ui->ui_ext, 0, sizeof(ui->ui_ext)); memset(ui->ui_ext, 0, sizeof(ui->ui_ext));
ui->ui_ext[kUILinegrid] = true; ui->ui_ext[kUILinegrid] = true;
ui->ui_ext[kUITermColors] = true;
return ui_bridge_attach(ui, tui_main, tui_scheduler); return ui_bridge_attach(ui, tui_main, tui_scheduler);
} }

View File

@ -219,11 +219,6 @@ void ui_schedule_refresh(void)
loop_schedule(&main_loop, event_create(ui_refresh_event, 0)); loop_schedule(&main_loop, event_create(ui_refresh_event, 0));
} }
void ui_resize(int width, int height)
{
ui_call_grid_resize(1, width, height);
}
void ui_default_colors_set(void) void ui_default_colors_set(void)
{ {
ui_call_default_colors_set(normal_fg, normal_bg, normal_sp, ui_call_default_colors_set(normal_fg, normal_bg, normal_sp,
@ -249,7 +244,7 @@ void ui_attach_impl(UI *ui)
if (ui_count == MAX_UI_COUNT) { if (ui_count == MAX_UI_COUNT) {
abort(); abort();
} }
if (!ui->ui_ext[kUIMultigrid]) { if (!ui->ui_ext[kUIMultigrid] && !ui->ui_ext[kUIFloatDebug]) {
ui_comp_attach(ui); ui_comp_attach(ui);
} }
@ -299,7 +294,7 @@ void ui_detach_impl(UI *ui)
ui_schedule_refresh(); ui_schedule_refresh();
} }
if (!ui->ui_ext[kUIMultigrid]) { if (!ui->ui_ext[kUIMultigrid] && !ui->ui_ext[kUIFloatDebug]) {
ui_comp_detach(ui); ui_comp_detach(ui);
} }
} }
@ -310,7 +305,7 @@ void ui_set_ext_option(UI *ui, UIExtension ext, bool active)
ui_refresh(); ui_refresh();
return; return;
} }
if (ui->option_set) { if (ui->option_set && (ui_ext_names[ext][0] != '_' || active)) {
ui->option_set(ui, cstr_as_string((char *)ui_ext_names[ext]), ui->option_set(ui, cstr_as_string((char *)ui_ext_names[ext]),
BOOLEAN_OBJ(active)); BOOLEAN_OBJ(active));
} }
@ -383,7 +378,7 @@ int ui_current_col(void)
void ui_flush(void) void ui_flush(void)
{ {
cmdline_ui_flush(); cmdline_ui_flush();
win_ui_flush(); win_ui_flush_positions();
msg_ext_ui_flush(); msg_ext_ui_flush();
if (pending_cursor_update) { if (pending_cursor_update) {
@ -405,7 +400,6 @@ void ui_flush(void)
ui_call_flush(); ui_call_flush();
} }
/// Check if current mode has changed. /// Check if current mode has changed.
/// May update the shape of the cursor. /// May update the shape of the cursor.
void ui_cursor_shape(void) void ui_cursor_shape(void)
@ -438,7 +432,9 @@ Array ui_array(void)
PUT(info, "height", INTEGER_OBJ(ui->height)); PUT(info, "height", INTEGER_OBJ(ui->height));
PUT(info, "rgb", BOOLEAN_OBJ(ui->rgb)); PUT(info, "rgb", BOOLEAN_OBJ(ui->rgb));
for (UIExtension j = 0; j < kUIExtCount; j++) { for (UIExtension j = 0; j < kUIExtCount; j++) {
PUT(info, ui_ext_names[j], BOOLEAN_OBJ(ui->ui_ext[j])); if (ui_ext_names[j][0] != '_' || ui->ui_ext[j]) {
PUT(info, ui_ext_names[j], BOOLEAN_OBJ(ui->ui_ext[j]));
}
} }
if (ui->inspect) { if (ui->inspect) {
ui->inspect(ui, &info); ui->inspect(ui, &info);
@ -462,8 +458,14 @@ void ui_grid_resize(handle_T grid_handle, int width, int height, Error *error)
return; return;
} }
// non-positive indicates no request if (wp->w_floating) {
wp->w_height_request = (int)MAX(height, 0); if (width != wp->w_width && height != wp->w_height) {
wp->w_width_request = (int)MAX(width, 0); win_config_float(wp, (int)width, (int)height, wp->w_float_config);
win_set_inner_size(wp); }
} else {
// non-positive indicates no request
wp->w_height_request = (int)MAX(height, 0);
wp->w_width_request = (int)MAX(width, 0);
win_set_inner_size(wp);
}
} }

View File

@ -20,6 +20,7 @@ typedef enum {
kUIMultigrid, kUIMultigrid,
kUIHlState, kUIHlState,
kUITermColors, kUITermColors,
kUIFloatDebug,
kUIExtCount, kUIExtCount,
} UIExtension; } UIExtension;
@ -33,9 +34,9 @@ EXTERN const char *ui_ext_names[] INIT(= {
"ext_multigrid", "ext_multigrid",
"ext_hlstate", "ext_hlstate",
"ext_termcolors", "ext_termcolors",
"_debug_float",
}); });
typedef struct ui_t UI; typedef struct ui_t UI;
enum { enum {

View File

@ -17,6 +17,7 @@
#include "nvim/ui.h" #include "nvim/ui.h"
#include "nvim/highlight.h" #include "nvim/highlight.h"
#include "nvim/memory.h" #include "nvim/memory.h"
#include "nvim/popupmnu.h"
#include "nvim/ui_compositor.h" #include "nvim/ui_compositor.h"
#include "nvim/ugrid.h" #include "nvim/ugrid.h"
#include "nvim/screen.h" #include "nvim/screen.h"
@ -55,7 +56,6 @@ void ui_comp_init(void)
compositor->rgb = true; compositor->rgb = true;
compositor->grid_resize = ui_comp_grid_resize; compositor->grid_resize = ui_comp_grid_resize;
compositor->grid_clear = ui_comp_grid_clear;
compositor->grid_scroll = ui_comp_grid_scroll; compositor->grid_scroll = ui_comp_grid_scroll;
compositor->grid_cursor_goto = ui_comp_grid_cursor_goto; compositor->grid_cursor_goto = ui_comp_grid_cursor_goto;
compositor->raw_line = ui_comp_raw_line; compositor->raw_line = ui_comp_raw_line;
@ -107,10 +107,12 @@ bool ui_comp_should_draw(void)
/// TODO(bfredl): later on the compositor should just use win_float_pos events, /// TODO(bfredl): later on the compositor should just use win_float_pos events,
/// though that will require slight event order adjustment: emit the win_pos /// though that will require slight event order adjustment: emit the win_pos
/// events in the beginning of update_screen(0), rather than in ui_flush() /// events in the beginning of update_screen(0), rather than in ui_flush()
bool ui_comp_put_grid(ScreenGrid *grid, int row, int col, int height, int width) bool ui_comp_put_grid(ScreenGrid *grid, int row, int col, int height, int width,
bool valid, bool on_top)
{ {
bool moved;
if (grid->comp_index != 0) { if (grid->comp_index != 0) {
bool moved = (row != grid->comp_row) || (col != grid->comp_col); moved = (row != grid->comp_row) || (col != grid->comp_col);
if (ui_comp_should_draw()) { if (ui_comp_should_draw()) {
// Redraw the area covered by the old position, and is not covered // Redraw the area covered by the old position, and is not covered
// by the new position. Disable the grid so that compose_area() will not // by the new position. Disable the grid so that compose_area() will not
@ -134,21 +136,41 @@ bool ui_comp_put_grid(ScreenGrid *grid, int row, int col, int height, int width)
} }
grid->comp_row = row; grid->comp_row = row;
grid->comp_col = col; grid->comp_col = col;
return moved; } else {
} moved = true;
#ifndef NDEBUG #ifndef NDEBUG
for (size_t i = 0; i < kv_size(layers); i++) { for (size_t i = 0; i < kv_size(layers); i++) {
if (kv_A(layers, i) == grid) { if (kv_A(layers, i) == grid) {
assert(false); assert(false);
}
} }
}
#endif #endif
// not found: new grid
kv_push(layers, grid); size_t insert_at = kv_size(layers);
grid->comp_row = row; if (kv_A(layers, insert_at-1) == &pum_grid) {
grid->comp_col = col; insert_at--;
grid->comp_index = kv_size(layers)-1; }
return true; if (insert_at > 1 && !on_top) {
insert_at--;
}
// not found: new grid
kv_push(layers, grid);
if (insert_at < kv_size(layers)-1) {
for (size_t i = kv_size(layers)-1; i > insert_at; i--) {
kv_A(layers, i) = kv_A(layers, i-i);
}
kv_A(layers, insert_at) = grid;
}
grid->comp_row = row;
grid->comp_col = col;
grid->comp_index = insert_at;
}
if (moved && valid && ui_comp_should_draw()) {
compose_area(grid->comp_row, grid->comp_row+grid->Rows,
grid->comp_col, grid->comp_col+grid->Columns);
}
return moved;
} }
void ui_comp_remove_grid(ScreenGrid *grid) void ui_comp_remove_grid(ScreenGrid *grid)
@ -194,6 +216,26 @@ bool ui_comp_set_grid(handle_T handle)
return false; return false;
} }
static void ui_comp_raise_grid(ScreenGrid *grid, size_t new_index)
{
size_t old_index = grid->comp_index;
for (size_t i = old_index; i < new_index; i++) {
kv_A(layers, i) = kv_A(layers, i+1);
kv_A(layers, i)->comp_index = i;
}
kv_A(layers, new_index) = grid;
grid->comp_index = new_index;
for (size_t i = old_index; i < new_index; i++) {
ScreenGrid *grid2 = kv_A(layers, i);
int startcol = MAX(grid->comp_col, grid2->comp_col);
int endcol = MIN(grid->comp_col+grid->Columns,
grid2->comp_col+grid2->Columns);
compose_area(MAX(grid->comp_row, grid2->comp_row),
MIN(grid->comp_row+grid->Rows, grid2->comp_row+grid2->Rows),
startcol, endcol);
}
}
static void ui_comp_grid_cursor_goto(UI *ui, Integer grid_handle, static void ui_comp_grid_cursor_goto(UI *ui, Integer grid_handle,
Integer r, Integer c) Integer r, Integer c)
{ {
@ -203,6 +245,18 @@ static void ui_comp_grid_cursor_goto(UI *ui, Integer grid_handle,
int cursor_row = curgrid->comp_row+(int)r; int cursor_row = curgrid->comp_row+(int)r;
int cursor_col = curgrid->comp_col+(int)c; int cursor_col = curgrid->comp_col+(int)c;
// TODO(bfredl): maybe not the best time to do this, for efficiency we
// should configure all grids before entering win_update()
if (curgrid != &default_grid) {
size_t new_index = kv_size(layers)-1;
if (kv_A(layers, new_index) == &pum_grid) {
new_index--;
}
if (curgrid->comp_index < new_index) {
ui_comp_raise_grid(curgrid, new_index);
}
}
if (cursor_col >= default_grid.Columns || cursor_row >= default_grid.Rows) { if (cursor_col >= default_grid.Columns || cursor_row >= default_grid.Rows) {
// TODO(bfredl): this happens with 'writedelay', refactor? // TODO(bfredl): this happens with 'writedelay', refactor?
// abort(); // abort();
@ -211,6 +265,18 @@ static void ui_comp_grid_cursor_goto(UI *ui, Integer grid_handle,
ui_composed_call_grid_cursor_goto(1, cursor_row, cursor_col); ui_composed_call_grid_cursor_goto(1, cursor_row, cursor_col);
} }
ScreenGrid *ui_comp_mouse_focus(int row, int col)
{
// TODO(bfredl): click "through" unfocusable grids?
for (ssize_t i = (ssize_t)kv_size(layers)-1; i > 0; i--) {
ScreenGrid *grid = kv_A(layers, i);
if (row >= grid->comp_row && row < grid->comp_row+grid->Rows
&& col >= grid->comp_col && col < grid->comp_col+grid->Columns) {
return grid;
}
}
return NULL;
}
/// Baseline implementation. This is always correct, but we can sometimes /// Baseline implementation. This is always correct, but we can sometimes
/// do something more efficient (where efficiency means smaller deltas to /// do something more efficient (where efficiency means smaller deltas to
@ -260,7 +326,7 @@ static void compose_line(Integer row, Integer startcol, Integer endcol,
memcpy(attrbuf+(col-startcol), grid->attrs+off, n * sizeof(*attrbuf)); memcpy(attrbuf+(col-startcol), grid->attrs+off, n * sizeof(*attrbuf));
// 'pumblend' // 'pumblend'
if (grid != &default_grid && p_pb) { if (grid == &pum_grid && p_pb) {
for (int i = col-(int)startcol; i < until-startcol; i++) { for (int i = col-(int)startcol; i < until-startcol; i++) {
bool thru = strequal((char *)linebuf[i], " "); // negative space bool thru = strequal((char *)linebuf[i], " "); // negative space
attrbuf[i] = (sattr_T)hl_blend_attrs(bg_attrs[i], attrbuf[i], thru); attrbuf[i] = (sattr_T)hl_blend_attrs(bg_attrs[i], attrbuf[i], thru);
@ -348,7 +414,8 @@ static void ui_comp_raw_line(UI *ui, Integer grid, Integer row,
assert(row < default_grid.Rows); assert(row < default_grid.Rows);
assert(clearcol <= default_grid.Columns); assert(clearcol <= default_grid.Columns);
if (flags & kLineFlagInvalid if (flags & kLineFlagInvalid
|| kv_size(layers) > (p_pb ? 1 : curgrid->comp_index+1)) { || kv_size(layers) > curgrid->comp_index+1
|| (p_pb && curgrid == &pum_grid)) {
compose_line(row, startcol, clearcol, flags); compose_line(row, startcol, clearcol, flags);
} else { } else {
ui_composed_call_raw_line(1, row, startcol, endcol, clearcol, clearattr, ui_composed_call_raw_line(1, row, startcol, endcol, clearcol, clearattr,
@ -359,17 +426,9 @@ static void ui_comp_raw_line(UI *ui, Integer grid, Integer row,
/// The screen is invalid and will soon be cleared /// The screen is invalid and will soon be cleared
/// ///
/// Don't redraw floats until screen is cleared /// Don't redraw floats until screen is cleared
void ui_comp_invalidate_screen(void) void ui_comp_set_screen_valid(bool valid)
{ {
valid_screen = false; valid_screen = valid;
}
static void ui_comp_grid_clear(UI *ui, Integer grid)
{
// By design, only first grid uses clearing.
assert(grid == 1);
ui_composed_call_grid_clear(1);
valid_screen = true;
} }
// TODO(bfredl): These events are somewhat of a hack. multiline messages // TODO(bfredl): These events are somewhat of a hack. multiline messages

View File

@ -6,6 +6,7 @@
#include <stdbool.h> #include <stdbool.h>
#include "nvim/api/private/handle.h" #include "nvim/api/private/handle.h"
#include "nvim/api/private/helpers.h"
#include "nvim/vim.h" #include "nvim/vim.h"
#include "nvim/ascii.h" #include "nvim/ascii.h"
#include "nvim/window.h" #include "nvim/window.h"
@ -48,6 +49,7 @@
#include "nvim/terminal.h" #include "nvim/terminal.h"
#include "nvim/undo.h" #include "nvim/undo.h"
#include "nvim/ui.h" #include "nvim/ui.h"
#include "nvim/ui_compositor.h"
#include "nvim/os/os.h" #include "nvim/os/os.h"
@ -203,14 +205,24 @@ newwindow:
wp = wp->w_next; wp = wp->w_next;
} }
} else { } else {
if (nchar == 'W') { /* go to previous window */ if (nchar == 'W') { // go to previous window
wp = curwin->w_prev; wp = curwin->w_prev;
if (wp == NULL) if (wp == NULL) {
wp = lastwin; /* wrap around */ wp = lastwin; // wrap around
} else { /* go to next window */ }
while (wp != NULL && wp->w_floating
&& !wp->w_float_config.focusable) {
wp = wp->w_prev;
}
} else { // go to next window
wp = curwin->w_next; wp = curwin->w_next;
if (wp == NULL) while (wp != NULL && wp->w_floating
wp = firstwin; /* wrap around */ && !wp->w_float_config.focusable) {
wp = wp->w_next;
}
if (wp == NULL) {
wp = firstwin; // wrap around
}
} }
} }
win_goto(wp); win_goto(wp);
@ -281,7 +293,7 @@ newwindow:
/* cursor to bottom-right window */ /* cursor to bottom-right window */
case 'b': case 'b':
case Ctrl_B: case Ctrl_B:
win_goto(lastwin); win_goto(lastwin_nofloating());
break; break;
/* cursor to last accessed (previous) window */ /* cursor to last accessed (previous) window */
@ -483,6 +495,22 @@ wingotofile:
cmdmod.tab = tabpage_index(curtab) + 1; cmdmod.tab = tabpage_index(curtab) + 1;
nchar = xchar; nchar = xchar;
goto wingotofile; goto wingotofile;
case 'e':
if (curwin->w_floating || !ui_has(kUIMultigrid)) {
beep_flush();
break;
}
FloatConfig config = FLOAT_CONFIG_INIT;
config.external = true;
Error err = ERROR_INIT;
if (!win_new_float(curwin, curwin->w_width, curwin->w_height, config,
&err)) {
EMSG(err.msg);
api_clear_error(&err);
beep_flush();
}
break;
default: default:
beep_flush(); beep_flush();
break; break;
@ -504,6 +532,302 @@ static void cmd_with_count(char *cmd, char_u *bufp, size_t bufsize,
} }
} }
/// Create a new float.
///
/// if wp == NULL allocate a new window, otherwise turn existing window into a
/// float. It must then already belong to the current tabpage!
///
/// config must already have been validated!
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 {
assert(!wp->w_floating);
if (firstwin == wp && lastwin_nofloating() == wp) {
// last non-float
api_set_error(err, kErrorTypeException,
"Cannot change last window into float");
return NULL;
} else if (!win_valid(wp)) {
api_set_error(err, kErrorTypeException,
"Cannot change window from different tabpage into float");
return NULL;
}
int dir;
winframe_remove(wp, &dir, NULL);
xfree(wp->w_frame);
wp->w_frame = NULL;
(void)win_comp_pos(); // recompute window positions
win_remove(wp, NULL);
win_append(lastwin_nofloating(), wp);
}
wp->w_floating = 1;
wp->w_status_height = 0;
wp->w_vsep_width = 0;
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;
}
void win_config_float(win_T *wp, int width, int height,
FloatConfig config)
{
wp->w_height = MAX(height, 1);
wp->w_width = MAX(width, 2);
if (config.relative == kFloatRelativeCursor) {
config.relative = kFloatRelativeWindow;
config.row += curwin->w_wrow;
config.col += curwin->w_wcol;
config.window = curwin->handle;
}
wp->w_float_config = config;
if (!ui_has(kUIMultigrid)) {
wp->w_height = MIN(wp->w_height, Rows-1);
wp->w_width = MIN(wp->w_width, Columns);
}
win_set_inner_size(wp);
must_redraw = MAX(must_redraw, VALID);
wp->w_pos_changed = true;
}
static void ui_ext_win_position(win_T *wp)
{
if (!wp->w_floating) {
ui_call_win_pos(wp->w_grid.handle, wp->handle, wp->w_winrow,
wp->w_wincol, wp->w_width, wp->w_height);
return;
}
const char *const anchor_str[] = {
"NW",
"NE",
"SW",
"SE"
};
FloatConfig c = wp->w_float_config;
if (!c.external) {
ScreenGrid *grid = &default_grid;
int row = c.row, col = c.col;
if (c.relative == kFloatRelativeWindow) {
Error dummy = ERROR_INIT;
win_T *win = find_window_by_handle(c.window, &dummy);
if (win) {
grid = &win->w_grid;
screen_adjust_grid(&grid, &row, &col);
}
api_clear_error(&dummy);
}
if (ui_has(kUIMultigrid)) {
String anchor = cstr_to_string(anchor_str[c.anchor]);
ui_call_win_float_pos(wp->w_grid.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.
bool east = c.anchor & kFloatAnchorEast;
bool south = c.anchor & kFloatAnchorSouth;
row -= (south ? wp->w_height : 0);
col -= (east ? wp->w_width : 0);
row = MAX(MIN(row, Rows-wp->w_height-1), 0);
col = MAX(MIN(col, Columns-wp->w_width), 0);
wp->w_winrow = row;
wp->w_wincol = col;
bool valid = (wp->w_redr_type == 0);
bool on_top = (curwin == wp) || !curwin->w_floating;
ui_comp_put_grid(&wp->w_grid, row, col, wp->w_height, wp->w_width,
valid, on_top);
if (!valid) {
wp->w_grid.valid = false;
redraw_win_later(wp, NOT_VALID);
}
}
} else {
ui_call_win_external_pos(wp->w_grid.handle, wp->handle);
}
}
static bool parse_float_anchor(String anchor, FloatAnchor *out)
{
if (anchor.size == 0) {
*out = (FloatAnchor)0;
}
char *str = anchor.data;
if (!STRICMP(str, "NW")) {
*out = kFloatAnchorNW;
} else if (!STRICMP(str, "NE")) {
*out = kFloatAnchorNE;
} else if (!STRICMP(str, "SW")) {
*out = kFloatAnchorSW;
} else if (!STRICMP(str, "SE")) {
*out = kFloatAnchorSE;
} else {
return false;
}
return true;
}
static bool parse_float_relative(String relative, FloatRelative *out)
{
if (relative.size == 0) {
*out = (FloatRelative)0;
}
char *str = relative.data;
if (!STRICMP(str, "editor")) {
*out = kFloatRelativeEditor;
} else if (!STRICMP(str, "win")) {
*out = kFloatRelativeWindow;
} else if (!STRICMP(str, "cursor")) {
*out = kFloatRelativeCursor;
} else {
return false;
}
return true;
}
bool parse_float_config(Dictionary config, FloatConfig *out, bool reconf,
Error *err)
{
bool has_row = false, has_col = false, has_relative = false;
bool has_external = false, has_window = false;
for (size_t i = 0; i < config.size; i++) {
char *key = config.items[i].key.data;
Object val = config.items[i].value;
if (!strcmp(key, "row")) {
has_row = true;
if (val.type == kObjectTypeInteger) {
out->row = val.data.integer;
} else if (val.type == kObjectTypeFloat) {
out->row = val.data.floating;
} else {
api_set_error(err, kErrorTypeValidation,
"'row' option has to be Integer or Float");
return false;
}
} else if (!strcmp(key, "col")) {
has_col = true;
if (val.type == kObjectTypeInteger) {
out->col = val.data.integer;
} else if (val.type == kObjectTypeFloat) {
out->col = val.data.floating;
} else {
api_set_error(err, kErrorTypeValidation,
"'col' option has to be Integer or Float");
return false;
}
} else if (!strcmp(key, "anchor")) {
if (val.type != kObjectTypeString) {
api_set_error(err, kErrorTypeValidation,
"'anchor' option has to be String");
return false;
}
if (!parse_float_anchor(val.data.string, &out->anchor)) {
api_set_error(err, kErrorTypeValidation,
"Invalid value of 'anchor' option");
return false;
}
} else if (!strcmp(key, "relative")) {
has_relative = true;
if (val.type != kObjectTypeString) {
api_set_error(err, kErrorTypeValidation,
"'relative' option has to be String");
return false;
}
if (!parse_float_relative(val.data.string, &out->relative)) {
api_set_error(err, kErrorTypeValidation,
"Invalid value of 'relative' option");
return false;
}
} else if (!strcmp(key, "win")) {
has_window = true;
if (val.type != kObjectTypeInteger
&& val.type != kObjectTypeWindow) {
api_set_error(err, kErrorTypeValidation,
"'win' option has to be Integer or Window");
return false;
}
out->window = val.data.integer;
} else if (!strcmp(key, "external")) {
if (val.type == kObjectTypeInteger) {
out->external = val.data.integer;
} else if (val.type == kObjectTypeBoolean) {
out->external = val.data.boolean;
} else {
api_set_error(err, kErrorTypeValidation,
"'external' option has to be Boolean");
return false;
}
has_external = out->external;
} else if (!strcmp(key, "focusable")) {
if (val.type == kObjectTypeInteger) {
out->focusable = val.data.integer;
} else if (val.type == kObjectTypeBoolean) {
out->focusable = val.data.boolean;
} else {
api_set_error(err, kErrorTypeValidation,
"'focusable' option has to be Boolean");
return false;
}
} else {
api_set_error(err, kErrorTypeValidation,
"Invalid options key '%s'", key);
return false;
}
}
if (has_window && !(has_relative && out->relative == kFloatRelativeWindow)) {
api_set_error(err, kErrorTypeValidation,
"'win' option is only valid with relative='win'");
return false;
}
if ((has_relative && out->relative == kFloatRelativeWindow)
&& (!has_window || out->window == 0)) {
out->window = curwin->handle;
}
if (has_relative && has_external) {
api_set_error(err, kErrorTypeValidation,
"Only one of 'relative' and 'external' should be used");
return false;
} else if (has_relative) {
out->external = false;
} else if (!reconf && !has_relative && !has_external) {
api_set_error(err, kErrorTypeValidation,
"One of 'relative' and 'external' must be used");
return false;
}
if (out->external && !ui_has(kUIMultigrid)) {
api_set_error(err, kErrorTypeValidation,
"UI doesn't support external windows");
return false;
}
if (has_relative != has_row || has_row != has_col) {
api_set_error(err, kErrorTypeValidation, "All of 'relative', 'row', and "
"'col' has to be specified at once");
return false;
}
return true;
}
/* /*
* split the current window, implements CTRL-W s and :split * split the current window, implements CTRL-W s and :split
* *
@ -566,16 +890,20 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
int wmh1; int wmh1;
bool did_set_fraction = false; bool did_set_fraction = false;
if (flags & WSP_TOP) if (flags & WSP_TOP) {
oldwin = firstwin; oldwin = firstwin;
else if (flags & WSP_BOT) } else if (flags & WSP_BOT || curwin->w_floating) {
oldwin = lastwin; // can't split float, use last nonfloating window instead
else oldwin = lastwin_nofloating();
} else {
oldwin = curwin; oldwin = curwin;
}
/* add a status line when p_ls == 1 and splitting the first window */ bool new_in_layout = (new_wp == NULL || new_wp->w_floating);
if (ONE_WINDOW && p_ls == 1 && oldwin->w_status_height == 0) {
if (oldwin->w_height <= p_wmh && new_wp == NULL) { // add a status line when p_ls == 1 and splitting the first window
if (one_nonfloat() && p_ls == 1 && oldwin->w_status_height == 0) {
if (oldwin->w_height <= p_wmh && new_in_layout) {
EMSG(_(e_noroom)); EMSG(_(e_noroom));
return FAIL; return FAIL;
} }
@ -624,7 +952,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
available = oldwin->w_frame->fr_width; available = oldwin->w_frame->fr_width;
needed += minwidth; needed += minwidth;
} }
if (available < needed && new_wp == NULL) { if (available < needed && new_in_layout) {
EMSG(_(e_noroom)); EMSG(_(e_noroom));
return FAIL; return FAIL;
} }
@ -702,7 +1030,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
available = oldwin->w_frame->fr_height; available = oldwin->w_frame->fr_height;
needed += minheight; needed += minheight;
} }
if (available < needed && new_wp == NULL) { if (available < needed && new_in_layout) {
EMSG(_(e_noroom)); EMSG(_(e_noroom));
return FAIL; return FAIL;
} }
@ -790,6 +1118,9 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
/* make the contents of the new window the same as the current one */ /* make the contents of the new window the same as the current one */
win_init(wp, curwin, flags); win_init(wp, curwin, flags);
} else if (wp->w_floating) {
new_frame(wp);
wp->w_floating = false;
} }
/* /*
@ -1192,7 +1523,13 @@ static void win_exchange(long Prenum)
win_T *wp2; win_T *wp2;
int temp; int temp;
if (ONE_WINDOW) { /* just one window */ if (curwin->w_floating) {
EMSG(e_floatexchange);
return;
}
if (firstwin == curwin && lastwin_nofloating() == curwin) {
// just one window
beep_flush(); beep_flush();
return; return;
} }
@ -1282,7 +1619,13 @@ static void win_rotate(int upwards, int count)
frame_T *frp; frame_T *frp;
int n; int n;
if (ONE_WINDOW) { /* nothing to do */ if (curwin->w_floating) {
EMSG(e_floatexchange);
return;
}
if (firstwin == curwin && lastwin_nofloating() == curwin) {
// nothing to do
beep_flush(); beep_flush();
return; return;
} }
@ -1355,16 +1698,27 @@ static void win_rotate(int upwards, int count)
*/ */
static void win_totop(int size, int flags) static void win_totop(int size, int flags)
{ {
int dir; int dir = 0;
int height = curwin->w_height; int height = curwin->w_height;
if (ONE_WINDOW) { if (firstwin == curwin && lastwin_nofloating() == curwin) {
beep_flush(); beep_flush();
return; return;
} }
/* Remove the window and frame from the tree of frames. */ if (curwin->w_floating) {
(void)winframe_remove(curwin, &dir, NULL); ui_comp_remove_grid(&curwin->w_grid);
if (ui_has(kUIMultigrid)) {
curwin->w_pos_changed = true;
} else {
// No longer a float, a non-multigrid UI shouldn't draw it as such
ui_call_win_hide(curwin->w_grid.handle);
win_free_grid(curwin, false);
}
} else {
// Remove the window and frame from the tree of frames.
(void)winframe_remove(curwin, &dir, NULL);
}
win_remove(curwin, NULL); win_remove(curwin, NULL);
last_status(FALSE); /* may need to remove last status line */ last_status(FALSE); /* may need to remove last status line */
(void)win_comp_pos(); /* recompute window positions */ (void)win_comp_pos(); /* recompute window positions */
@ -1795,13 +2149,13 @@ static bool last_window(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
} }
/// Check that current tab page contains no more then one window other than /// Check that current tab page contains no more then one window other than
/// "aucmd_win". /// "aucmd_win". Only counts floating window if it is current.
bool one_window(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT bool one_window(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{ {
bool seen_one = false; bool seen_one = false;
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (wp != aucmd_win) { if (wp != aucmd_win && (!wp->w_floating || wp == curwin)) {
if (seen_one) { if (seen_one) {
return false; return false;
} }
@ -1811,6 +2165,20 @@ bool one_window(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
return true; return true;
} }
/// Like ONE_WINDOW but only considers non-floating windows
bool one_nonfloat(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
return firstwin->w_next == NULL || firstwin->w_next->w_floating;
}
/// if wp is the last non-floating window
///
/// always false for a floating window
bool last_nonfloat(win_T *wp) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
return firstwin == wp && !(wp->w_next && !wp->w_floating);
}
/// Close the possibly last window in a tab page. /// Close the possibly last window in a tab page.
/// ///
/// @param win window to close /// @param win window to close
@ -1882,9 +2250,9 @@ int win_close(win_T *win, bool free_buf)
int dir; int dir;
bool help_window = false; bool help_window = false;
tabpage_T *prev_curtab = curtab; tabpage_T *prev_curtab = curtab;
frame_T *win_frame = win->w_frame->fr_parent; frame_T *win_frame = win->w_floating ? NULL : win->w_frame->fr_parent;
if (last_window()) { if (last_window() && !win->w_floating) {
EMSG(_("E444: Cannot close last window")); EMSG(_("E444: Cannot close last window"));
return FAIL; return FAIL;
} }
@ -1897,10 +2265,17 @@ int win_close(win_T *win, bool free_buf)
EMSG(_("E813: Cannot close autocmd window")); EMSG(_("E813: Cannot close autocmd window"));
return FAIL; return FAIL;
} }
if ((firstwin == aucmd_win || lastwin == aucmd_win) && one_window()) { if ((firstwin == aucmd_win || lastwin_nofloating() == aucmd_win)
&& one_window()) {
EMSG(_("E814: Cannot close window, only autocmd window would remain")); EMSG(_("E814: Cannot close window, only autocmd window would remain"));
return FAIL; return FAIL;
} }
if ((firstwin == win && lastwin_nofloating() == win)
&& lastwin->w_floating) {
// TODO(bfredl): we might close the float also instead
EMSG(e_floatonly);
return FAIL;
}
/* When closing the last window in a tab page first go to another tab page /* When closing the last window in a tab page first go to another tab page
* and then close the window and the tab page to avoid that curwin and * and then close the window and the tab page to avoid that curwin and
@ -1921,7 +2296,15 @@ int win_close(win_T *win, bool free_buf)
* Guess which window is going to be the new current window. * Guess which window is going to be the new current window.
* This may change because of the autocommands (sigh). * This may change because of the autocommands (sigh).
*/ */
wp = frame2win(win_altframe(win, NULL)); if (!win->w_floating) {
wp = frame2win(win_altframe(win, NULL));
} else {
if (win_valid(prevwin)) {
wp = prevwin;
} else {
wp = curtab->tp_firstwin;
}
}
/* /*
* Be careful: If autocommands delete the window or cause this window * Be careful: If autocommands delete the window or cause this window
@ -1949,6 +2332,27 @@ int win_close(win_T *win, bool free_buf)
return FAIL; return FAIL;
} }
bool was_floating = win->w_floating;
if (ui_has(kUIMultigrid)) {
ui_call_win_close(win->w_grid.handle);
}
if (win->w_floating) {
ui_comp_remove_grid(&win->w_grid);
if (win->w_float_config.external) {
for (tabpage_T *tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
if (tp == curtab) {
continue;
}
if (tp->tp_curwin == win) {
// NB: an autocmd can still abort the closing of this window,
// bur carring out this change anyway shouldn't be a catastrophe.
tp->tp_curwin = tp->tp_firstwin;
}
}
}
}
/* Free independent synblock before the buffer is freed. */ /* Free independent synblock before the buffer is freed. */
if (win->w_buffer != NULL) if (win->w_buffer != NULL)
@ -1975,7 +2379,8 @@ int win_close(win_T *win, bool free_buf)
if (only_one_window() && win_valid(win) && win->w_buffer == NULL if (only_one_window() && win_valid(win) && win->w_buffer == NULL
&& (last_window() || curtab != prev_curtab && (last_window() || curtab != prev_curtab
|| close_last_window_tabpage(win, free_buf, prev_curtab))) { || close_last_window_tabpage(win, free_buf, prev_curtab))
&& !win->w_floating) {
// Autocommands have closed all windows, quit now. Restore // Autocommands have closed all windows, quit now. Restore
// curwin->w_buffer, otherwise writing ShaDa file may fail. // curwin->w_buffer, otherwise writing ShaDa file may fail.
if (curwin->w_buffer == NULL) { if (curwin->w_buffer == NULL) {
@ -1992,7 +2397,7 @@ int win_close(win_T *win, bool free_buf)
} }
// Autocommands may have closed the window already, or closed the only // Autocommands may have closed the window already, or closed the only
// other window or moved to another tab page. // other window or moved to another tab page.
if (!win_valid(win) || last_window() if (!win_valid(win) || (!win->w_floating && last_window())
|| close_last_window_tabpage(win, free_buf, prev_curtab)) { || close_last_window_tabpage(win, free_buf, prev_curtab)) {
return FAIL; return FAIL;
} }
@ -2041,12 +2446,15 @@ int win_close(win_T *win, bool free_buf)
// using the window. // using the window.
check_cursor(); check_cursor();
} }
if (p_ea && (*p_ead == 'b' || *p_ead == dir)) {
// If the frame of the closed window contains the new current window, if (!was_floating) {
// only resize that frame. Otherwise resize all windows. if (p_ea && (*p_ead == 'b' || *p_ead == dir)) {
win_equal(curwin, curwin->w_frame->fr_parent == win_frame, dir); // If the frame of the closed window contains the new current window,
} else { // only resize that frame. Otherwise resize all windows.
win_comp_pos(); win_equal(curwin, curwin->w_frame->fr_parent == win_frame, dir);
} else {
win_comp_pos();
}
} }
if (close_curwin) { if (close_curwin) {
@ -2167,10 +2575,18 @@ win_free_mem (
frame_T *frp; frame_T *frp;
win_T *wp; win_T *wp;
/* Remove the window and its frame from the tree of frames. */ if (!win->w_floating) {
frp = win->w_frame; // Remove the window and its frame from the tree of frames.
wp = winframe_remove(win, dirp, tp); frp = win->w_frame;
xfree(frp); wp = winframe_remove(win, dirp, tp);
xfree(frp);
} else {
if (win_valid(prevwin)) {
wp = prevwin;
} else {
wp = curtab->tp_firstwin;
}
}
win_free(win, tp); win_free(win, tp);
/* When deleting the current window of another tab page select a new /* When deleting the current window of another tab page select a new
@ -2189,6 +2605,12 @@ void win_free_all(void)
while (first_tabpage->tp_next != NULL) while (first_tabpage->tp_next != NULL)
tabpage_close(TRUE); tabpage_close(TRUE);
while (lastwin != NULL && lastwin->w_floating) {
win_T *wp = lastwin;
win_remove(lastwin, NULL);
(void)win_free_mem(wp, &dummy, NULL);
}
if (aucmd_win != NULL) { if (aucmd_win != NULL) {
(void)win_free_mem(aucmd_win, &dummy, NULL); (void)win_free_mem(aucmd_win, &dummy, NULL);
aucmd_win = NULL; aucmd_win = NULL;
@ -2870,11 +3292,19 @@ close_others (
win_T *nextwp; win_T *nextwp;
int r; int r;
if (one_window()) { if (curwin->w_floating) {
if (message && !autocmd_busy) {
EMSG(e_floatonly);
}
return;
}
if (one_window() && !lastwin->w_floating) {
if (message if (message
&& !autocmd_busy && !autocmd_busy
) ) {
MSG(_(m_onlyone)); MSG(_(m_onlyone));
}
return; return;
} }
@ -3116,9 +3546,7 @@ int win_new_tabpage(int after, char_u *filename)
redraw_all_later(NOT_VALID); redraw_all_later(NOT_VALID);
if (ui_has(kUIMultigrid)) { tabpage_check_windows(tp);
tabpage_check_windows(tp);
}
apply_autocmds(EVENT_WINNEW, NULL, NULL, false, curbuf); apply_autocmds(EVENT_WINNEW, NULL, NULL, false, curbuf);
apply_autocmds(EVENT_WINENTER, NULL, NULL, false, curbuf); apply_autocmds(EVENT_WINENTER, NULL, NULL, false, curbuf);
@ -3317,7 +3745,7 @@ static void enter_tabpage(tabpage_T *tp, buf_T *old_curbuf, int trigger_enter_au
lastwin = tp->tp_lastwin; lastwin = tp->tp_lastwin;
topframe = tp->tp_topframe; topframe = tp->tp_topframe;
if (old_curtab != curtab && ui_has(kUIMultigrid)) { if (old_curtab != curtab) {
tabpage_check_windows(old_curtab); tabpage_check_windows(old_curtab);
} }
@ -3355,16 +3783,31 @@ static void enter_tabpage(tabpage_T *tp, buf_T *old_curbuf, int trigger_enter_au
redraw_all_later(NOT_VALID); redraw_all_later(NOT_VALID);
} }
/// called when changing current tabpage from old_curtab to curtab /// tells external UI that windows and inline floats in old_curtab are invisible
/// and that floats in curtab is now visible.
///
/// External floats are considered independent of tabpages. This is
/// implemented by always moving them to curtab.
static void tabpage_check_windows(tabpage_T *old_curtab) static void tabpage_check_windows(tabpage_T *old_curtab)
{ {
win_T *next_wp; win_T *next_wp;
for (win_T *wp = old_curtab->tp_firstwin; wp; wp = next_wp) { for (win_T *wp = old_curtab->tp_firstwin; wp; wp = next_wp) {
next_wp = wp->w_next; next_wp = wp->w_next;
if (wp->w_floating) {
if (wp->w_float_config.external) {
win_remove(wp, old_curtab);
win_append(lastwin_nofloating(), wp);
} else {
ui_comp_remove_grid(&wp->w_grid);
}
}
wp->w_pos_changed = true; wp->w_pos_changed = true;
} }
for (win_T *wp = firstwin; wp; wp = wp->w_next) { for (win_T *wp = firstwin; wp; wp = wp->w_next) {
if (wp->w_floating && !wp->w_float_config.external) {
win_config_float(wp, wp->w_width, wp->w_height, wp->w_float_config);
}
wp->w_pos_changed = true; wp->w_pos_changed = true;
} }
} }
@ -3577,6 +4020,12 @@ win_goto_ver (
frame_T *foundfr; frame_T *foundfr;
foundfr = curwin->w_frame; foundfr = curwin->w_frame;
if (curwin->w_floating) {
win_goto(prevwin);
return;
}
while (count--) { while (count--) {
/* /*
* First go upwards in the tree of frames until we find an upwards or * First go upwards in the tree of frames until we find an upwards or
@ -3636,6 +4085,12 @@ win_goto_hor (
frame_T *foundfr; frame_T *foundfr;
foundfr = curwin->w_frame; foundfr = curwin->w_frame;
if (curwin->w_floating) {
win_goto(prevwin);
return;
}
while (count--) { while (count--) {
/* /*
* First go upwards in the tree of frames until we find a left or * First go upwards in the tree of frames until we find a left or
@ -3740,6 +4195,7 @@ static void win_enter_ext(win_T *wp, bool undo_sync, int curwin_invalid,
} }
curwin = wp; curwin = wp;
curbuf = wp->w_buffer; curbuf = wp->w_buffer;
check_cursor(); check_cursor();
if (!virtual_active()) if (!virtual_active())
curwin->w_cursor.coladd = 0; curwin->w_cursor.coladd = 0;
@ -3809,9 +4265,10 @@ static void win_enter_ext(win_T *wp, bool undo_sync, int curwin_invalid,
else if (curwin->w_height == 0) else if (curwin->w_height == 0)
win_setheight(1); win_setheight(1);
/* set window width to desired minimal value */ // set window width to desired minimal value
if (curwin->w_width < p_wiw && !curwin->w_p_wfw) if (curwin->w_width < p_wiw && !curwin->w_p_wfw && !wp->w_floating) {
win_setwidth((int)p_wiw); win_setwidth((int)p_wiw);
}
setmouse(); /* in case jumped to/from help buffer */ setmouse(); /* in case jumped to/from help buffer */
@ -3916,6 +4373,7 @@ static win_T *win_alloc(win_T *after, int hidden)
new_wp->w_botline = 2; new_wp->w_botline = 2;
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;
/* 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;
@ -4204,6 +4662,13 @@ int win_comp_pos(void)
int col = 0; int col = 0;
frame_comp_pos(topframe, &row, &col); frame_comp_pos(topframe, &row, &col);
// Too often, but when we support anchoring floats to split windows,
// this will be needed
for (win_T *wp = lastwin; wp && wp->w_floating; wp = wp->w_prev) {
win_config_float(wp, wp->w_width, wp->w_height, wp->w_float_config);
}
return row; return row;
} }
@ -4263,8 +4728,6 @@ void win_setheight(int height)
*/ */
void win_setheight_win(int height, win_T *win) void win_setheight_win(int height, win_T *win)
{ {
int row;
if (win == curwin) { if (win == curwin) {
/* Always keep current window at least one line high, even when /* Always keep current window at least one line high, even when
* 'winminheight' is zero. */ * 'winminheight' is zero. */
@ -4274,21 +4737,28 @@ void win_setheight_win(int height, win_T *win)
height = 1; height = 1;
} }
frame_setheight(win->w_frame, height + win->w_status_height); if (win->w_floating) {
if (win->w_float_config.external) {
win_config_float(win, win->w_width, height, win->w_float_config);
} else {
beep_flush();
return;
}
} else {
frame_setheight(win->w_frame, height + win->w_status_height);
/* recompute the window positions */ // recompute the window positions
row = win_comp_pos(); int row = win_comp_pos();
/* // If there is extra space created between the last window and the command
* If there is extra space created between the last window and the command // line, clear it.
* line, clear it. if (full_screen && msg_scrolled == 0 && row < cmdline_row) {
*/ grid_fill(&default_grid, row, cmdline_row, 0, (int)Columns, ' ', ' ', 0);
if (full_screen && msg_scrolled == 0 && row < cmdline_row) { }
grid_fill(&default_grid, row, cmdline_row, 0, (int)Columns, ' ', ' ', 0); cmdline_row = row;
msg_row = row;
msg_col = 0;
} }
cmdline_row = row;
msg_row = row;
msg_col = 0;
redraw_all_later(NOT_VALID); redraw_all_later(NOT_VALID);
} }
@ -4358,14 +4828,16 @@ static void frame_setheight(frame_T *curfrp, int height)
if (frp != curfrp) if (frp != curfrp)
room -= frame_minheight(frp, NULL); room -= frame_minheight(frp, NULL);
} }
if (curfrp->fr_width != Columns) if (curfrp->fr_width != Columns) {
room_cmdline = 0; room_cmdline = 0;
else { } else {
room_cmdline = Rows - p_ch - (lastwin->w_winrow win_T *wp = lastwin_nofloating();
+ lastwin->w_height + room_cmdline = Rows - p_ch - (wp->w_winrow
lastwin->w_status_height); + wp->w_height +
if (room_cmdline < 0) wp->w_status_height);
if (room_cmdline < 0) {
room_cmdline = 0; room_cmdline = 0;
}
} }
if (height <= room + room_cmdline) { if (height <= room + room_cmdline) {
@ -4470,11 +4942,19 @@ void win_setwidth_win(int width, win_T *wp)
if (width == 0) if (width == 0)
width = 1; width = 1;
} }
if (wp->w_floating) {
if (wp->w_float_config.external) {
win_config_float(wp, width, wp->w_height, wp->w_float_config);
} else {
beep_flush();
return;
}
} else {
frame_setwidth(wp->w_frame, width + wp->w_vsep_width);
frame_setwidth(wp->w_frame, width + wp->w_vsep_width); // recompute the window positions
(void)win_comp_pos();
/* recompute the window positions */ }
(void)win_comp_pos();
redraw_all_later(NOT_VALID); redraw_all_later(NOT_VALID);
} }
@ -5015,6 +5495,7 @@ void win_set_inner_size(win_T *wp)
if (!exiting) { if (!exiting) {
scroll_to_fraction(wp, prev_height); scroll_to_fraction(wp, prev_height);
} }
redraw_win_later(wp, NOT_VALID); // SOME_VALID??
} }
if (width != wp->w_width_inner) { if (width != wp->w_width_inner) {
@ -5026,6 +5507,7 @@ void win_set_inner_size(win_T *wp)
update_topline(); update_topline();
curs_columns(true); // validate w_wrow curs_columns(true); // validate w_wrow
} }
redraw_win_later(wp, NOT_VALID);
} }
if (wp->w_buffer->terminal) { if (wp->w_buffer->terminal) {
@ -5039,9 +5521,7 @@ void win_new_width(win_T *wp, int width)
wp->w_width = width; wp->w_width = width;
win_set_inner_size(wp); win_set_inner_size(wp);
redraw_win_later(wp, NOT_VALID); wp->w_redr_status = true;
wp->w_redr_status = TRUE;
wp->w_pos_changed = true; wp->w_pos_changed = true;
} }
@ -5379,7 +5859,7 @@ int min_rows(void)
/// Check that there is only one window (and only one tab page), not counting a /// Check that there is only one window (and only one tab page), not counting a
/// help or preview window, unless it is the current window. Does not count /// help or preview window, unless it is the current window. Does not count
/// "aucmd_win". /// "aucmd_win". Does not count floats unless it is current.
bool only_one_window(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT bool only_one_window(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{ {
// If there is another tab page there always is another window. // If there is another tab page there always is another window.
@ -5390,7 +5870,7 @@ bool only_one_window(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
int count = 0; int count = 0;
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (wp->w_buffer != NULL if (wp->w_buffer != NULL
&& (!((bt_help(wp->w_buffer) && !bt_help(curbuf)) && (!((bt_help(wp->w_buffer) && !bt_help(curbuf)) || wp->w_floating
|| wp->w_p_pvw) || wp == curwin) && wp != aucmd_win) { || wp->w_p_pvw) || wp == curwin) && wp != aucmd_win) {
count++; count++;
} }
@ -6080,22 +6560,24 @@ void win_findbuf(typval_T *argvars, list_T *list)
} }
} }
void win_ui_flush(void) void win_ui_flush_positions(void)
{ {
if (!ui_has(kUIMultigrid)) {
return;
}
FOR_ALL_TAB_WINDOWS(tp, wp) { FOR_ALL_TAB_WINDOWS(tp, wp) {
if (wp->w_pos_changed && wp->w_grid.chars != NULL) { if (wp->w_pos_changed && wp->w_grid.chars != NULL) {
if (tp == curtab) { if (tp == curtab) {
ui_call_win_pos(wp->w_grid.handle, wp->handle, wp->w_winrow, ui_ext_win_position(wp);
wp->w_wincol, wp->w_width, wp->w_height);
} else { } else {
ui_call_win_hide(wp->w_grid.handle); ui_call_win_hide(wp->w_grid.handle);
} }
wp->w_pos_changed = false; wp->w_pos_changed = false;
} }
} }
}
win_T *lastwin_nofloating(void) {
win_T *res = lastwin;
while (res->w_floating) {
res = res->w_prev;
}
return res;
} }

File diff suppressed because it is too large Load Diff

View File

@ -158,6 +158,7 @@ function Screen.new(width, height)
wildmenu_items = nil, wildmenu_items = nil,
wildmenu_selected = nil, wildmenu_selected = nil,
win_position = {}, win_position = {},
float_pos = {},
_session = nil, _session = nil,
messages = {}, messages = {},
msg_history = {}, msg_history = {},
@ -227,10 +228,9 @@ function Screen:attach(options, session)
-- simplify test code by doing the same. -- simplify test code by doing the same.
self._options.rgb = true self._options.rgb = true
end end
if self._options.ext_multigrid then if self._options.ext_multigrid or self._options.ext_float then
self._options.ext_linegrid = true self._options.ext_linegrid = true
end end
self._session = session
end end
function Screen:detach() function Screen:detach()
@ -256,7 +256,7 @@ end
-- canonical order of ext keys, used to generate asserts -- canonical order of ext keys, used to generate asserts
local ext_keys = { local ext_keys = {
'popupmenu', 'cmdline', 'cmdline_block', 'wildmenu_items', 'wildmenu_pos', 'popupmenu', 'cmdline', 'cmdline_block', 'wildmenu_items', 'wildmenu_pos',
'messages', 'showmode', 'showcmd', 'ruler', 'messages', 'showmode', 'showcmd', 'ruler', 'float_pos',
} }
-- Asserts that the screen state eventually matches an expected state -- Asserts that the screen state eventually matches an expected state
@ -642,7 +642,7 @@ function Screen:_handle_grid_resize(grid, width, height)
end end
if self._cursor.grid == grid then if self._cursor.grid == grid then
self._cursor.row = 1 self._cursor.row = 1 -- -1 ?
self._cursor.col = 1 self._cursor.col = 1
end end
self._grids[grid] = { self._grids[grid] = {
@ -676,7 +676,6 @@ function Screen:_reset()
self.wildmenu_pos = nil self.wildmenu_pos = nil
end end
function Screen:_handle_mode_info_set(cursor_style_enabled, mode_info) function Screen:_handle_mode_info_set(cursor_style_enabled, mode_info)
self._cursor_style_enabled = cursor_style_enabled self._cursor_style_enabled = cursor_style_enabled
for _, item in pairs(mode_info) do for _, item in pairs(mode_info) do
@ -713,7 +712,6 @@ end
function Screen:_handle_grid_destroy(grid) function Screen:_handle_grid_destroy(grid)
self._grids[grid] = nil self._grids[grid] = nil
if self._options.ext_multigrid then if self._options.ext_multigrid then
assert(self.win_position[grid])
self.win_position[grid] = nil self.win_position[grid] = nil
end end
end end
@ -734,6 +732,36 @@ function Screen:_handle_grid_cursor_goto(grid, row, col)
self._cursor.col = col + 1 self._cursor.col = col + 1
end end
function Screen:_handle_win_pos(grid, win, startrow, startcol, width, height)
self.win_position[grid] = {
win = win,
startrow = startrow,
startcol = startcol,
width = width,
height = height
}
self.float_pos[grid] = nil
end
function Screen:_handle_win_float_pos(grid, ...)
self.win_position[grid] = nil
self.float_pos[grid] = {...}
end
function Screen:_handle_win_external_pos(grid)
self.win_position[grid] = nil
self.float_pos[grid] = {external=true}
end
function Screen:_handle_win_hide(grid)
self.win_position[grid] = nil
self.float_pos[grid] = nil
end
function Screen:_handle_win_close(grid)
self.float_pos[grid] = nil
end
function Screen:_handle_busy_start() function Screen:_handle_busy_start()
self._busy = true self._busy = true
end end
@ -815,20 +843,6 @@ function Screen:_handle_hl_attr_define(id, rgb_attrs, cterm_attrs, info)
self._new_attrs = true self._new_attrs = true
end end
function Screen:_handle_win_pos(grid, win, startrow, startcol, width, height)
self.win_position[grid] = {
win = win,
startrow = startrow,
startcol = startcol,
width = width,
height = height
}
end
function Screen:_handle_win_hide(grid)
self.win_position[grid] = nil
end
function Screen:get_hl(val) function Screen:get_hl(val)
if self._options.ext_newgrid then if self._options.ext_newgrid then
return self._attr_table[val][1] return self._attr_table[val][1]
@ -922,8 +936,11 @@ function Screen:_handle_option_set(name, value)
self.options[name] = value self.options[name] = value
end end
function Screen:_handle_popupmenu_show(items, selected, row, col) function Screen:_handle_popupmenu_show(items, selected, row, col, grid)
self.popupmenu = {items=items, pos=selected, anchor={row, col}} if (not self._options.ext_multigrid) and grid == 1 then
grid = nil
end
self.popupmenu = {items=items, pos=selected, anchor={row, col, grid}}
end end
function Screen:_handle_popupmenu_select(selected) function Screen:_handle_popupmenu_select(selected)
@ -1112,6 +1129,7 @@ function Screen:_extstate_repr(attr_state)
showcmd=self:_chunks_repr(self.showcmd, attr_state), showcmd=self:_chunks_repr(self.showcmd, attr_state),
ruler=self:_chunks_repr(self.ruler, attr_state), ruler=self:_chunks_repr(self.ruler, attr_state),
msg_history=msg_history, msg_history=msg_history,
float_pos=self.float_pos
} }
end end
@ -1146,7 +1164,10 @@ function Screen:redraw_debug(attrs, ignore, timeout)
local function notification_cb(method, args) local function notification_cb(method, args)
assert(method == 'redraw') assert(method == 'redraw')
for _, update in ipairs(args) do for _, update in ipairs(args) do
print(require('inspect')(update)) -- mode_info_set is quite verbose, comment out the condition to debug it.
if update[1] ~= "mode_info_set" then
print(inspect(update))
end
end end
self:_redraw(args) self:_redraw(args)
self:print_snapshot(attrs, ignore) self:print_snapshot(attrs, ignore)
@ -1159,7 +1180,7 @@ function Screen:redraw_debug(attrs, ignore, timeout)
end end
function Screen:render(headers, attr_state, preview) function Screen:render(headers, attr_state, preview)
headers = headers and self._options.ext_multigrid headers = headers and (self._options.ext_multigrid or self._options._debug_float)
local rv = {} local rv = {}
for igrid,grid in pairs(self._grids) do for igrid,grid in pairs(self._grids) do
if headers then if headers then
@ -1227,6 +1248,7 @@ function Screen:print_snapshot(attrs, ignore)
io.stdout:write( "]]"..attrstr) io.stdout:write( "]]"..attrstr)
for _, k in ipairs(ext_keys) do for _, k in ipairs(ext_keys) do
if ext_state[k] ~= nil then if ext_state[k] ~= nil then
-- TODO(bfredl): improve formating, remove ext metatables
io.stdout:write(", "..k.."="..inspect(ext_state[k])) io.stdout:write(", "..k.."="..inspect(ext_state[k]))
end end
end end

View File

@ -47,8 +47,8 @@ local check_logs_useless_lines = {
['See README_MISSING_SYSCALL_OR_IOCTL for guidance']=3, ['See README_MISSING_SYSCALL_OR_IOCTL for guidance']=3,
} }
local function eq(expected, actual, ctx) local function eq(expected, actual, context)
return assert.are.same(expected, actual, ctx) return assert.are.same(expected, actual, context)
end end
local function neq(expected, actual, context) local function neq(expected, actual, context)
return assert.are_not.same(expected, actual, context) return assert.are_not.same(expected, actual, context)