diff options
83 files changed, 5748 insertions, 1005 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 988c69cd2d..848e100b02 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -379,7 +379,7 @@ endif() include_directories("${PROJECT_BINARY_DIR}/config") include_directories("${PROJECT_SOURCE_DIR}/src") -find_package(LibUV REQUIRED) +find_package(LibUV REQUIRED) # minimum version: v1.12 include_directories(SYSTEM ${LIBUV_INCLUDE_DIRS}) find_package(Msgpack 1.0.0 REQUIRED) diff --git a/config/CMakeLists.txt b/config/CMakeLists.txt index 442d91524b..4fb44b9a27 100644 --- a/config/CMakeLists.txt +++ b/config/CMakeLists.txt @@ -40,24 +40,14 @@ check_function_exists(fseeko HAVE_FSEEKO) check_function_exists(getpwent HAVE_GETPWENT) check_function_exists(getpwnam HAVE_GETPWNAM) check_function_exists(getpwuid HAVE_GETPWUID) -check_function_exists(uv_translate_sys_error HAVE_UV_TRANSLATE_SYS_ERROR) check_function_exists(readv HAVE_READV) if(Iconv_FOUND) set(HAVE_ICONV 1) endif() -check_function_exists(_putenv_s HAVE_PUTENV_S) -if(WIN32 AND NOT HAVE_PUTENV_S) - message(SEND_ERROR "_putenv_s() function not found on your system.") -endif() check_function_exists(opendir HAVE_OPENDIR) check_function_exists(readlink HAVE_READLINK) -check_function_exists(setenv HAVE_SETENV) -if(UNIX AND NOT HAVE_SETENV) - message(SEND_ERROR "setenv() function not found on your system.") -endif() -check_function_exists(unsetenv HAVE_UNSETENV) check_function_exists(setpgid HAVE_SETPGID) check_function_exists(setsid HAVE_SETSID) check_function_exists(sigaction HAVE_SIGACTION) diff --git a/config/config.h.in b/config/config.h.in index 56d46e9f14..ef2fea4042 100644 --- a/config/config.h.in +++ b/config/config.h.in @@ -27,14 +27,10 @@ #cmakedefine HAVE_LOCALE_H #cmakedefine HAVE_NL_LANGINFO_CODESET #cmakedefine HAVE_NL_MSG_CAT_CNTR -#cmakedefine HAVE_PUTENV_S #cmakedefine HAVE_PWD_H #cmakedefine HAVE_READLINK -#cmakedefine HAVE_UV_TRANSLATE_SYS_ERROR // TODO: add proper cmake check // #define HAVE_SELINUX 1 -#cmakedefine HAVE_SETENV -#cmakedefine HAVE_UNSETENV #cmakedefine HAVE_SETPGID #cmakedefine HAVE_SETSID #cmakedefine HAVE_SIGACTION diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 38bbd66ea2..3efe651dfe 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -5774,6 +5774,9 @@ mode([expr]) Return a string that indicates the current mode. This is useful in the 'statusline' option or when used with |remote_expr()| In most other places it always returns "c" or "n". + Note that in the future more modes and more specific modes may + be added. It's better not to compare the whole string but only + the leading character(s). Also see |visualmode()|. msgpackdump({list}) *msgpackdump()* diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt index 77a829b150..270c4fb556 100644 --- a/runtime/doc/ui.txt +++ b/runtime/doc/ui.txt @@ -520,19 +520,33 @@ tabs. size). If the window was previously hidden, it should now be shown 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] - Stop displaying the window. + Stop displaying the window. The window can be shown again later. ["win_scroll_over_start"] Hint that following `grid_scroll` on the default grid should scroll over windows. This is a temporary workaround to allow 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"] Hint that scrolled over windows should be redrawn again, and not be overdrawn by default grid scrolling anymore. +["win_close", grid] + Close the window. + See |ui-linegrid| for grid events. 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. diff --git a/runtime/doc/windows.txt b/runtime/doc/windows.txt index 2e4b6f6e76..6e1ad0f1f4 100644 --- a/runtime/doc/windows.txt +++ b/runtime/doc/windows.txt @@ -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 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 window will appear. diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh index 11305421e5..7339fdc691 100755 --- a/scripts/vim-patch.sh +++ b/scripts/vim-patch.sh @@ -297,6 +297,7 @@ submit_pr() { submit_fn="git_hub_pr" else >&2 echo "${BASENAME}: 'hub' or 'git-hub' not found in PATH or not executable." + >&2 echo " Get it here: https://hub.github.com/" exit 1 fi diff --git a/src/clint.py b/src/clint.py index 34af5d15fd..1ef31820ee 100755 --- a/src/clint.py +++ b/src/clint.py @@ -29,10 +29,10 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -"""Does neovim-lint on c files. +"""Lints C files in the Neovim source tree. The goal of this script is to identify places in the code that *may* -be in non-compliance with neovim style. It does not attempt to fix +be in non-compliance with Neovim style. It does not attempt to fix up these problems -- the point is to educate. It does also not attempt to find all problems, or to ensure that everything it does find is legitimately a problem. @@ -88,7 +88,7 @@ Syntax: clint.py [--verbose=#] [--output=vs7] [--filter=-x,+y,...] * [whitespace/braces] { should almost always be at the end of the previous line * [build/include] Include the directory when naming .h files - * [runtime/int] Use int16/int64/etc, rather than the C type. + * [runtime/int] Use int16_t/int64_t/etc, rather than the C type. Every problem is given a confidence score from 1-5, with 5 meaning we are certain of the problem, and 1 meaning it could be a legitimate construct. @@ -1487,6 +1487,37 @@ def CheckMemoryFunctions(filename, clean_lines, linenum, error): '...) instead of ' + function + '...).') +os_functions = ( + ('setenv(', 'os_setenv('), + ('getenv(', 'os_getenv('), + ('_wputenv(', 'os_setenv('), + ('_putenv_s(', 'os_setenv('), + ('putenv(', 'os_setenv('), + ('unsetenv(', 'os_unsetenv('), +) + + +def CheckOSFunctions(filename, clean_lines, linenum, error): + """Checks for calls to invalid functions. + + Args: + filename: The name of the current file. + clean_lines: A CleansedLines instance containing the file. + linenum: The number of the line to check. + error: The function to call with any errors found. + """ + line = clean_lines.elided[linenum] + for function, suggested_function in os_functions: + ix = line.find(function) + # Comparisons made explicit for clarity -- pylint: + # disable=g-explicit-bool-comparison + if ix >= 0 and (ix == 0 or (not line[ix - 1].isalnum() and + line[ix - 1] not in ('_', '.', '>'))): + error(filename, linenum, 'runtime/os_fn', 2, + 'Use ' + suggested_function + + '...) instead of ' + function + '...).') + + # Matches invalid increment: *count++, which moves pointer instead of # incrementing a value. _RE_PATTERN_INVALID_INCREMENT = re.compile( @@ -3370,6 +3401,7 @@ def ProcessLine(filename, file_extension, clean_lines, line, nesting_state, error) CheckPosixThreading(filename, clean_lines, line, error) CheckMemoryFunctions(filename, clean_lines, line, error) + CheckOSFunctions(filename, clean_lines, line, error) for check_fn in extra_check_functions: check_fn(filename, clean_lines, line, error) diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 9cd178eaeb..3613a8f8bc 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -380,8 +380,6 @@ void nvim_buf_set_lines(uint64_t channel_id, } } - win_T *save_curwin = NULL; - tabpage_T *save_curtab = NULL; size_t new_len = replacement.size; size_t old_len = (size_t)(end - start); ptrdiff_t extra = 0; // lines added to text, can be negative @@ -397,8 +395,8 @@ void nvim_buf_set_lines(uint64_t channel_id, } try_start(); - bufref_T save_curbuf = { NULL, 0, 0 }; - switch_to_win_for_buf(buf, &save_curwin, &save_curtab, &save_curbuf); + aco_save_T aco; + aucmd_prepbuf(&aco, (buf_T *)buf); if (u_save((linenr_T)(start - 1), (linenr_T)end) == FAIL) { api_set_error(err, kErrorTypeException, "Failed to save undo information"); @@ -465,27 +463,21 @@ void nvim_buf_set_lines(uint64_t channel_id, // changed range, and move any in the remainder of the buffer. // Only adjust marks if we managed to switch to a window that holds // the buffer, otherwise line numbers will be invalid. - if (save_curbuf.br_buf == NULL) { - mark_adjust((linenr_T)start, - (linenr_T)(end - 1), - MAXLNUM, - (long)extra, - false); - } + mark_adjust((linenr_T)start, + (linenr_T)(end - 1), + MAXLNUM, + (long)extra, + false); changed_lines((linenr_T)start, 0, (linenr_T)end, (long)extra, true); - if (save_curbuf.br_buf == NULL) { - fix_cursor((linenr_T)start, (linenr_T)end, (linenr_T)extra); - } - end: for (size_t i = 0; i < new_len; i++) { xfree(lines[i]); } xfree(lines); - restore_win_for_buf(save_curwin, save_curtab, &save_curbuf); + aucmd_restbuf(&aco); try_end(err); } @@ -1109,28 +1101,6 @@ free_exit: return 0; } -// Check if deleting lines made the cursor position invalid. -// Changed the lines from "lo" to "hi" and added "extra" lines (negative if -// deleted). -static void fix_cursor(linenr_T lo, linenr_T hi, linenr_T extra) -{ - if (curwin->w_cursor.lnum >= lo) { - // Adjust the cursor position if it's in/after the changed - // lines. - if (curwin->w_cursor.lnum >= hi) { - curwin->w_cursor.lnum += extra; - check_cursor_col(); - } else if (extra < 0) { - curwin->w_cursor.lnum = lo; - check_cursor(); - } else { - check_cursor_col(); - } - changed_cline_bef_curs(); - } - invalidate_botline(); -} - // Normalizes 0-based indexes to buffer line numbers static int64_t normalize_index(buf_T *buf, int64_t index, bool *oob) { diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 19a3368c1c..c2b382804d 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -1004,7 +1004,9 @@ static void init_ui_event_metadata(Dictionary *metadata) Array ui_options = ARRAY_DICT_INIT; ADD(ui_options, STRING_OBJ(cstr_to_string("rgb"))); 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)); } diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index 9e9be588e3..d3cbb46dad 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -17,6 +17,8 @@ #include "nvim/popupmnu.h" #include "nvim/cursor_shape.h" #include "nvim/highlight.h" +#include "nvim/screen.h" +#include "nvim/window.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/ui.c.generated.h" diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h index b57cf8d3ef..b89c5b6014 100644 --- a/src/nvim/api/ui_events.in.h +++ b/src/nvim/api/ui_events.in.h @@ -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) FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL; 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) FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL; 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, Integer startcol, Integer width, Integer height) 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) 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) FUNC_API_SINCE(6) FUNC_API_BRIDGE_IMPL FUNC_API_COMPOSITOR_IMPL; void win_scroll_over_reset(void) diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 5a4d0a11e7..cb5ed5ecda 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -31,6 +31,7 @@ #include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/eval/typval.h" +#include "nvim/fileio.h" #include "nvim/option.h" #include "nvim/state.h" #include "nvim/syntax.h" @@ -978,15 +979,94 @@ Buffer nvim_create_buf(Boolean listed, Boolean scratch, Error *err) return 0; } if (scratch) { - WITH_BUFFER(buf, { - set_option_value("bh", 0L, "hide", OPT_LOCAL); - set_option_value("bt", 0L, "nofile", OPT_LOCAL); - set_option_value("swf", 0L, NULL, OPT_LOCAL); - }); + aco_save_T aco; + aucmd_prepbuf(&aco, buf); + set_option_value("bh", 0L, "hide", OPT_LOCAL); + set_option_value("bt", 0L, "nofile", OPT_LOCAL); + set_option_value("swf", 0L, NULL, OPT_LOCAL); + aucmd_restbuf(&aco); } 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. /// /// @return List of tabpage handles diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index 33857f95b7..157f73c9fa 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -9,6 +9,7 @@ #include "nvim/api/window.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" +#include "nvim/ex_docmd.h" #include "nvim/vim.h" #include "nvim/buffer.h" #include "nvim/cursor.h" @@ -432,3 +433,67 @@ Boolean nvim_win_is_valid(Window window) 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; + } +} + +/// Close a window. +/// +/// This is equivalent to |:close| with count except that it takes a window id. +/// +/// @param window Window handle +/// @param force Behave like `:close!` The last window of a buffer with +/// unwritten changes can be closed. The buffer will become +/// hidden, even if 'hidden' is not set. +/// +/// @param[out] err Error details, if any +/// @return Window number +void nvim_win_close(Window window, Boolean force, Error *err) + FUNC_API_SINCE(6) +{ + win_T *win = find_window_by_handle(window, err); + if (!win) { + return; + } + tabpage_T *tabpage = win_find_tabpage(win); + + TryState tstate; + try_enter(&tstate); + ex_win_close(force, win, tabpage == curtab ? NULL : tabpage); + vim_ignored = try_leave(&tstate, err); +} diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 7fd4326914..6e4e7afeb2 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -474,8 +474,8 @@ void close_buffer(win_T *win, buf_T *buf, int action, int abort_if_last) return; } buf->b_locked--; - if (abort_if_last && one_window()) { - /* Autocommands made this the only window. */ + if (abort_if_last && last_nonfloat(win)) { + // Autocommands made this the only window. EMSG(_(e_auabort)); return; } @@ -491,8 +491,8 @@ void close_buffer(win_T *win, buf_T *buf, int action, int abort_if_last) return; } buf->b_locked--; - if (abort_if_last && one_window()) { - /* Autocommands made this the only window. */ + if (abort_if_last && last_nonfloat(win)) { + // Autocommands made this the only window. EMSG(_(e_auabort)); return; } @@ -591,7 +591,8 @@ void close_buffer(win_T *win, buf_T *buf, int action, int abort_if_last) /* Change directories when the 'acd' option is set. */ do_autochdir(); - // disable buffer updates for the current buffer + // Disable buffer-updates for the current buffer. + // No need to check `unload_buf`: in that case the function returned above. buf_updates_unregister_all(buf); /* diff --git a/src/nvim/buffer.h b/src/nvim/buffer.h index 79bed049ea..64c906fc96 100644 --- a/src/nvim/buffer.h +++ b/src/nvim/buffer.h @@ -63,35 +63,6 @@ enum bfa_values { # include "buffer.h.generated.h" #endif -// Find a window that contains "buf" and switch to it. -// If there is no such window, use the current window and change "curbuf". -// Caller must initialize save_curbuf to NULL. -// restore_win_for_buf() MUST be called later! -static inline void switch_to_win_for_buf(buf_T *buf, - win_T **save_curwinp, - tabpage_T **save_curtabp, - bufref_T *save_curbuf) -{ - win_T *wp; - tabpage_T *tp; - - if (!find_win_for_buf(buf, &wp, &tp) - || switch_win(save_curwinp, save_curtabp, wp, tp, true) == FAIL) { - switch_buffer(save_curbuf, buf); - } -} - -static inline void restore_win_for_buf(win_T *save_curwin, - tabpage_T *save_curtab, - bufref_T *save_curbuf) -{ - if (save_curbuf->br_buf == NULL) { - restore_win(save_curwin, save_curtab, true); - } else { - restore_buffer(save_curbuf); - } -} - static inline void buf_set_changedtick(buf_T *const buf, const varnumber_T changedtick) REAL_FATTR_NONNULL_ALL REAL_FATTR_ALWAYS_INLINE; @@ -145,15 +116,4 @@ static inline void buf_inc_changedtick(buf_T *const buf) buf_set_changedtick(buf, buf_get_changedtick(buf) + 1); } -#define WITH_BUFFER(b, code) \ - do { \ - win_T *save_curwin = NULL; \ - tabpage_T *save_curtab = NULL; \ - bufref_T save_curbuf = { NULL, 0, 0 }; \ - switch_to_win_for_buf(b, &save_curwin, &save_curtab, &save_curbuf); \ - code; \ - restore_win_for_buf(save_curwin, save_curtab, &save_curbuf); \ - } while (0) - - #endif // NVIM_BUFFER_H diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index da1e3ce9c6..48cef9b1e7 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -958,6 +958,35 @@ struct matchitem { 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 * @@ -1221,6 +1250,8 @@ struct window_S { ScreenGrid w_grid; // the grid specific to the window 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 diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 5a21c50fa6..6b31406b0c 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -494,7 +494,7 @@ static int insert_check(VimState *state) s->inserted_space = false; } - if (can_cindent && cindent_on() && ctrl_x_mode == 0) { + if (can_cindent && cindent_on() && ctrl_x_mode == 0 && !compl_started) { insert_do_cindent(s); } @@ -5110,11 +5110,9 @@ int get_literal(void) } } - if (cc == 0) /* NUL is stored as NL */ + if (cc == 0) { // NUL is stored as NL cc = '\n'; - if (enc_dbcs && (cc & 0xff) == 0) - cc = '?'; /* don't accept an illegal DBCS char, the NUL in the - second byte will cause trouble! */ + } --no_mapping; if (nc) diff --git a/src/nvim/event/socket.c b/src/nvim/event/socket.c index 6fcb9f7e7a..af326f9c82 100644 --- a/src/nvim/event/socket.c +++ b/src/nvim/event/socket.c @@ -169,7 +169,7 @@ void socket_watcher_close(SocketWatcher *watcher, socket_close_cb cb) FUNC_ATTR_NONNULL_ARG(1) { watcher->close_cb = cb; - uv_close((uv_handle_t *)watcher->stream, close_cb); + uv_close(STRUCT_CAST(uv_handle_t, watcher->stream), close_cb); } static void connection_event(void **argv) diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 0b57f9cc3a..2a5793f0d4 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -366,7 +366,10 @@ typedef struct { varnumber_T start_col_nr; ///< starting column number varnumber_T end_col_nr; ///< ending column number } line; - varnumber_T value; ///< value if sorting by integer + struct { + varnumber_T value; ///< value if sorting by integer + bool is_number; ///< true when line contains a number + } num; float_T value_flt; ///< value if sorting by float } st_u; } sorti_T; @@ -390,9 +393,15 @@ static int sort_compare(const void *s1, const void *s2) // When sorting numbers "start_col_nr" is the number, not the column // number. if (sort_nr) { - result = l1.st_u.value == l2.st_u.value - ? 0 : l1.st_u.value > l2.st_u.value - ? 1 : -1; + if (l1.st_u.num.is_number != l2.st_u.num.is_number) { + result = l1.st_u.num.is_number - l2.st_u.num.is_number; + } else { + result = l1.st_u.num.value == l2.st_u.num.value + ? 0 + : l1.st_u.num.value > l2.st_u.num.value + ? 1 + : -1; + } } else if (sort_flt) { result = l1.st_u.value_flt == l2.st_u.value_flt ? 0 : l1.st_u.value_flt > l2.st_u.value_flt @@ -567,11 +576,13 @@ void ex_sort(exarg_T *eap) s--; // include preceding negative sign } if (*s == NUL) { - // empty line should sort before any number - nrs[lnum - eap->line1].st_u.value = -MAXLNUM; + // line without number should sort before any number + nrs[lnum - eap->line1].st_u.num.is_number = false; + nrs[lnum - eap->line1].st_u.num.value = 0; } else { + nrs[lnum - eap->line1].st_u.num.is_number = true; vim_str2nr(s, NULL, NULL, sort_what, - &nrs[lnum - eap->line1].st_u.value, NULL, 0); + &nrs[lnum - eap->line1].st_u.num.value, NULL, 0); } } else { s = skipwhite(p); @@ -2465,8 +2476,8 @@ int do_ecmd( } set_bufref(&bufref, buf); if (p_ur < 0 || curbuf->b_ml.ml_line_count <= p_ur) { - /* Save all the text, so that the reload can be undone. - * Sync first so that this is a separate undo-able action. */ + // Save all the text, so that the reload can be undone. + // Sync first so that this is a separate undo-able action. u_sync(false); if (u_savecommon(0, curbuf->b_ml.ml_line_count + 1, 0, true) == FAIL) { @@ -2480,6 +2491,7 @@ int do_ecmd( // Tell readfile() not to clear or reload undo info. readfile_flags = READ_KEEP_UNDO; } else { + buf_updates_unregister_all(curbuf); buf_freeall(curbuf, 0); // Free all things for buffer. } // If autocommands deleted the buffer we were going to re-edit, give diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index cda80dad39..6b39ad8e87 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -5489,24 +5489,18 @@ uc_check_code( break; case 1: /* Quote, but don't split */ result = STRLEN(eap->arg) + 2; - for (p = eap->arg; *p; ++p) { - if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2) - /* DBCS can contain \ in a trail byte, skip the - * double-byte character. */ - ++p; - else if (*p == '\\' || *p == '"') - ++result; + for (p = eap->arg; *p; p++) { + if (*p == '\\' || *p == '"') { + result++; + } } if (buf != NULL) { *buf++ = '"'; - for (p = eap->arg; *p; ++p) { - if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2) - /* DBCS can contain \ in a trail byte, copy the - * double-byte character to avoid escaping. */ - *buf++ = *p++; - else if (*p == '\\' || *p == '"') + for (p = eap->arg; *p; p++) { + if (*p == '\\' || *p == '"') { *buf++ = '\\'; + } *buf++ = *p; } *buf = '"'; @@ -6176,7 +6170,7 @@ static void ex_pclose(exarg_T *eap) * Close window "win" and take care of handling closing the last window for a * modified buffer. */ -static void +void ex_win_close( int forceit, win_T *win, @@ -6285,6 +6279,9 @@ void tabpage_close(int forceit) { // First close all the windows but the current one. If that worked then // close the last window in this tab, that will close it. + while (curwin->w_floating) { + ex_win_close(forceit, curwin, NULL); + } if (!ONE_WINDOW) { close_others(true, forceit); } @@ -6309,8 +6306,8 @@ void tabpage_close_other(tabpage_T *tp, int forceit) /* Limit to 1000 windows, autocommands may add a window while we close * one. OK, so I'm paranoid... */ while (++done < 1000) { - sprintf((char *)prev_idx, "%i", tabpage_index(tp)); - wp = tp->tp_firstwin; + snprintf((char *)prev_idx, sizeof(prev_idx), "%i", tabpage_index(tp)); + wp = tp->tp_lastwin; ex_win_close(forceit, wp, tp); /* Autocommands may delete the tab page under our fingers and we may @@ -6331,6 +6328,7 @@ static void ex_only(exarg_T *eap) { win_T *wp; int wnr; + if (eap->addr_count > 0) { wnr = eap->line2; for (wp = firstwin; --wnr > 0;) { @@ -6339,6 +6337,10 @@ static void ex_only(exarg_T *eap) else wp = wp->w_next; } + } else { + wp = curwin; + } + if (wp != curwin) { win_goto(wp); } close_others(TRUE, eap->forceit); diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 27bd23a668..a50e18efce 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -3470,11 +3470,13 @@ void redrawcmd(void) void compute_cmdrow(void) { - if (exmode_active || msg_scrolled != 0) + if (exmode_active || msg_scrolled != 0) { cmdline_row = Rows - 1; - else - cmdline_row = lastwin->w_winrow + lastwin->w_height - + lastwin->w_status_height; + } else { + win_T *wp = lastwin_nofloating(); + cmdline_row = wp->w_winrow + wp->w_height + + wp->w_status_height; + } } static void cursorcmd(void) diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 6356290b9c..b9de46efc8 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -3405,7 +3405,9 @@ restore_backup: // (could be a pipe). // If the 'fsync' option is FALSE, don't fsync(). Useful for laptops. int error; - if (p_fs && (error = os_fsync(fd)) != 0 && !device) { + if (p_fs && (error = os_fsync(fd)) != 0 && !device + // fsync not supported on this storage. + && error != UV_ENOTSUP) { SET_ERRMSG_ARG(_("E667: Fsync failed: %s"), error); end = 0; } @@ -5311,9 +5313,7 @@ void forward_slash(char_u *fname) } for (p = fname; *p != NUL; p++) { // The Big5 encoding can have '\' in the trail byte. - if (enc_dbcs != 0 && (*mb_ptr2len)(p) > 1) { - p++; - } else if (*p == '\\') { + if (*p == '\\') { *p = '/'; } } @@ -7613,10 +7613,6 @@ char_u * file_pat_to_reg_pat( #endif default: size++; - if (enc_dbcs != 0 && (*mb_ptr2len)(p) > 1) { - ++p; - ++size; - } break; } } @@ -7737,10 +7733,9 @@ char_u * file_pat_to_reg_pat( reg_pat[i++] = ','; break; default: - if (enc_dbcs != 0 && (*mb_ptr2len)(p) > 1) - reg_pat[i++] = *p++; - else if (allow_dirs != NULL && vim_ispathsep(*p)) - *allow_dirs = TRUE; + if (allow_dirs != NULL && vim_ispathsep(*p)) { + *allow_dirs = true; + } reg_pat[i++] = *p; break; } diff --git a/src/nvim/globals.h b/src/nvim/globals.h index f47697b190..52c5d65512 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -83,10 +83,10 @@ EXTERN struct nvim_stats_s { int64_t redraw; } g_stats INIT(= { 0, 0 }); -/* Values for "starting" */ -#define NO_SCREEN 2 /* no screen updating yet */ -#define NO_BUFFERS 1 /* not all buffers loaded yet */ -/* 0 not starting anymore */ +// Values for "starting". +#define NO_SCREEN 2 // no screen updating yet +#define NO_BUFFERS 1 // not all buffers loaded yet +// 0 not starting anymore /* * Number of Rows and Columns in the screen. @@ -641,7 +641,6 @@ EXTERN int vr_lines_changed INIT(= 0); /* #Lines changed by "gR" so far */ // mbyte flags that used to depend on 'encoding'. These are now deprecated, as // 'encoding' is always "utf-8". Code that use them can be refactored to // remove dead code. -#define enc_dbcs 0 #define enc_utf8 true #define has_mbyte true @@ -1049,6 +1048,10 @@ EXTERN char_u e_cmdmap_repeated[] INIT(=N_( "E5521: <Cmd> mapping must end with <CR> before second <Cmd>")); EXTERN char_u e_cmdmap_key[] INIT(=N_( "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")); diff --git a/src/nvim/grid_defs.h b/src/nvim/grid_defs.h index d9ccdbbdab..38fc513baa 100644 --- a/src/nvim/grid_defs.h +++ b/src/nvim/grid_defs.h @@ -47,6 +47,9 @@ typedef struct { int Rows; 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 int row_offset; int col_offset; @@ -58,7 +61,7 @@ typedef struct { bool comp_disabled; } ScreenGrid; -#define SCREEN_GRID_INIT { 0, NULL, NULL, NULL, NULL, 0, 0, 0, 0, 0, 0, 0, \ - false } +#define SCREEN_GRID_INIT { 0, NULL, NULL, NULL, NULL, 0, 0, false, 0, 0, 0, \ + 0, 0, false } #endif // NVIM_GRID_DEFS_H diff --git a/src/nvim/hashtab.h b/src/nvim/hashtab.h index 973b97d476..a70a8bea63 100644 --- a/src/nvim/hashtab.h +++ b/src/nvim/hashtab.h @@ -19,7 +19,7 @@ typedef size_t hash_T; #define HASHITEM_EMPTY(hi) ((hi)->hi_key == NULL \ || (hi)->hi_key == (char_u *)&hash_removed) -/// A hastable item. +/// Hashtable item. /// /// Each item has a NUL terminated string key. /// A key can appear only once in the table. diff --git a/src/nvim/lib/khash.h b/src/nvim/lib/khash.h index 8287cb14da..b2994a3159 100644 --- a/src/nvim/lib/khash.h +++ b/src/nvim/lib/khash.h @@ -24,24 +24,24 @@ */ /* - An example: + Example: #include "nvim/khash.h" KHASH_MAP_INIT_INT(32, char) int main() { - int ret, is_missing; - khiter_t k; - khash_t(32) *h = kh_init(32); - k = kh_put(32, h, 5, &ret); - kh_value(h, k) = 10; - k = kh_get(32, h, 10); - is_missing = (k == kh_end(h)); - k = kh_get(32, h, 5); - kh_del(32, h, k); - for (k = kh_begin(h); k != kh_end(h); ++k) - if (kh_exist(h, k)) kh_value(h, k) = 1; - kh_destroy(32, h); - return 0; + int ret, is_missing; + khiter_t k; + khash_t(32) *h = kh_init(32); + k = kh_put(32, h, 5, &ret); + kh_value(h, k) = 10; + k = kh_get(32, h, 10); + is_missing = (k == kh_end(h)); + k = kh_get(32, h, 5); + kh_del(32, h, k); + for (k = kh_begin(h); k != kh_end(h); ++k) + if (kh_exist(h, k)) kh_value(h, k) = 1; + kh_destroy(32, h); + return 0; } */ @@ -539,7 +539,7 @@ static kh_inline khint_t __ac_Wang_hash(khint_t key) @param r Extra return code: -1 if the operation failed; 0 if the key is present in the hash table; 1 if the bucket is empty (never used); 2 if the element in - the bucket has been deleted [int*] + the bucket has been deleted [int*] @return Iterator to the inserted element [khint_t] */ #define kh_put(name, h, k, r) kh_put_##name(h, k, r) diff --git a/src/nvim/main.c b/src/nvim/main.c index 251a54ad5b..5dffca95a2 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -46,6 +46,7 @@ #include "nvim/os/os_defs.h" #include "nvim/path.h" #include "nvim/profile.h" +#include "nvim/popupmnu.h" #include "nvim/quickfix.h" #include "nvim/screen.h" #include "nvim/state.h" @@ -184,6 +185,7 @@ bool event_teardown(void) void early_init(void) { log_init(); + env_init(); fs_init(); handle_init(); eval_init(); // init global variables @@ -1769,7 +1771,7 @@ static bool do_user_initialization(void) FUNC_ATTR_WARN_UNUSED_RESULT { bool do_exrc = p_exrc; - if (process_env("VIMINIT") == OK) { + if (execute_env("VIMINIT") == OK) { do_exrc = p_exrc; return do_exrc; } @@ -1814,7 +1816,7 @@ static bool do_user_initialization(void) } while (iter != NULL); xfree(config_dirs); } - if (process_env("EXINIT") == OK) { + if (execute_env("EXINIT") == OK) { do_exrc = p_exrc; return do_exrc; } @@ -1878,7 +1880,7 @@ static void source_startup_scripts(const mparm_T *const parmp) /// /// @return FAIL if the environment variable was not executed, /// OK otherwise. -static int process_env(char *env) +static int execute_env(char *env) FUNC_ATTR_NONNULL_ALL { const char *initstr = os_getenv(env); diff --git a/src/nvim/map.c b/src/nvim/map.c index 53ab734802..9b6f57a56f 100644 --- a/src/nvim/map.c +++ b/src/nvim/map.c @@ -1,6 +1,13 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com +/// +/// map.c: khash.h wrapper +/// +/// NOTE: Callers must manage memory (allocate) for keys and values. +/// khash.h does not make its own copy of the key or value. +/// + #include <stdlib.h> #include <stdbool.h> #include <string.h> @@ -72,6 +79,16 @@ return kh_get(T##_##U##_map, map->table, key) != kh_end(map->table); \ } \ \ + T map_##T##_##U##_key(Map(T, U) *map, T key) \ + { \ + khiter_t k; \ + \ + if ((k = kh_get(T##_##U##_map, map->table, key)) == kh_end(map->table)) { \ + abort(); /* Caller must check map_has(). */ \ + } \ + \ + return kh_key(map->table, k); \ + } \ U map_##T##_##U##_put(Map(T, U) *map, T key, U value) \ { \ int ret; \ @@ -167,3 +184,18 @@ MAP_IMPL(String, MsgpackRpcRequestHandler, MSGPACK_HANDLER_INITIALIZER) #define KVEC_INITIALIZER { .size = 0, .capacity = 0, .items = NULL } MAP_IMPL(HlEntry, int, DEFAULT_INITIALIZER) MAP_IMPL(String, handle_T, 0) + + +/// Deletes a key:value pair from a string:pointer map, and frees the +/// storage of both key and value. +/// +void pmap_del2(PMap(cstr_t) *map, const char *key) +{ + if (pmap_has(cstr_t)(map, key)) { + void *k = (void *)pmap_key(cstr_t)(map, key); + void *v = pmap_get(cstr_t)(map, key); + pmap_del(cstr_t)(map, key); + xfree(k); + xfree(v); + } +} diff --git a/src/nvim/map.h b/src/nvim/map.h index 0e4308b953..75ab64cca4 100644 --- a/src/nvim/map.h +++ b/src/nvim/map.h @@ -25,11 +25,15 @@ void map_##T##_##U##_free(Map(T, U) *map); \ U map_##T##_##U##_get(Map(T, U) *map, T key); \ bool map_##T##_##U##_has(Map(T, U) *map, T key); \ + T map_##T##_##U##_key(Map(T, U) *map, T key); \ U map_##T##_##U##_put(Map(T, U) *map, T key, U value); \ U *map_##T##_##U##_ref(Map(T, U) *map, T key, bool put); \ U map_##T##_##U##_del(Map(T, U) *map, T key); \ void map_##T##_##U##_clear(Map(T, U) *map); +// +// NOTE: Keys AND values must be allocated! khash.h does not make a copy. +// MAP_DECLS(int, int) MAP_DECLS(cstr_t, ptr_t) MAP_DECLS(ptr_t, ptr_t) @@ -43,6 +47,7 @@ MAP_DECLS(String, handle_T) #define map_free(T, U) map_##T##_##U##_free #define map_get(T, U) map_##T##_##U##_get #define map_has(T, U) map_##T##_##U##_has +#define map_key(T, U) map_##T##_##U##_key #define map_put(T, U) map_##T##_##U##_put #define map_ref(T, U) map_##T##_##U##_ref #define map_del(T, U) map_##T##_##U##_del @@ -52,7 +57,9 @@ MAP_DECLS(String, handle_T) #define pmap_free(T) map_free(T, ptr_t) #define pmap_get(T) map_get(T, ptr_t) #define pmap_has(T) map_has(T, ptr_t) +#define pmap_key(T) map_key(T, ptr_t) #define pmap_put(T) map_put(T, ptr_t) +/// @see pmap_del2 #define pmap_del(T) map_del(T, ptr_t) #define pmap_clear(T) map_clear(T, ptr_t) @@ -62,4 +69,6 @@ MAP_DECLS(String, handle_T) #define map_foreach_value(map, value, block) \ kh_foreach_value(map->table, value, block) +void pmap_del2(PMap(cstr_t) *map, const char *key); + #endif // NVIM_MAP_H diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index ead6b4405d..5ed2b4c564 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -4,9 +4,8 @@ /// mbyte.c: Code specifically for handling multi-byte characters. /// Multibyte extensions partly by Sung-Hoon Baek /// -/// The encoding used in nvim is always UTF-8. "enc_utf8" and "has_mbyte" is -/// thus always true. "enc_dbcs" is always zero. The 'encoding' option is -/// read-only and always reads "utf-8". +/// Strings internal to Nvim are always encoded as UTF-8 (thus the legacy +/// 'encoding' option is always "utf-8"). /// /// The cell width on the display needs to be determined from the character /// value. Recognizing UTF-8 bytes is easy: 0xxx.xxxx is a single-byte char, @@ -1375,6 +1374,7 @@ int utf8_to_utf16(const char *str, wchar_t **strw) int utf16_to_utf8(const wchar_t *strw, char **str) FUNC_ATTR_NONNULL_ALL { + *str = NULL; // Compute the space required to store the string as UTF-8. DWORD utf8_len = WideCharToMultiByte(CP_UTF8, 0, @@ -1400,7 +1400,7 @@ int utf16_to_utf8(const wchar_t *strw, char **str) NULL, NULL); if (utf8_len == 0) { - free(*str); + xfree(*str); *str = NULL; return GetLastError(); } diff --git a/src/nvim/memory.c b/src/nvim/memory.c index b49b521bc9..4ed816b157 100644 --- a/src/nvim/memory.c +++ b/src/nvim/memory.c @@ -109,7 +109,7 @@ void *xmalloc(size_t size) return ret; } -/// free wrapper that returns delegates to the backing memory manager +/// free() wrapper that delegates to the backing memory manager void xfree(void *ptr) { free(ptr); @@ -572,8 +572,13 @@ void free_all_mem(void) p_ea = false; if (first_tabpage->tp_next != NULL) 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!"); + } /* Free all spell info. */ spell_free_all(); diff --git a/src/nvim/message.c b/src/nvim/message.c index b22508c23f..b4aa333a48 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -281,15 +281,9 @@ msg_strtrunc ( /* Use up to 'showcmd' column. */ room = (int)(Rows - msg_row - 1) * Columns + sc_col - 1; if (len > room && room > 0) { - if (enc_utf8) - /* may have up to 18 bytes per cell (6 per char, up to two - * composing chars) */ - len = (room + 2) * 18; - else if (enc_dbcs == DBCS_JPNU) - /* may have up to 2 bytes per cell for euc-jp */ - len = (room + 2) * 2; - else - len = room + 2; + // may have up to 18 bytes per cell (6 per char, up to two + // composing chars) + len = (room + 2) * 18; buf = xmalloc(len); trunc_string(s, buf, room, len); } @@ -2777,9 +2771,11 @@ void msg_ext_flush_showmode(void) { // Showmode messages doesn't interrupt normal message flow, so we use // separate event. Still reuse the same chunking logic, for simplicity. - msg_ext_emit_chunk(); - ui_call_msg_showmode(msg_ext_chunks); - msg_ext_chunks = (Array)ARRAY_DICT_INIT; + if (ui_has(kUIMessages)) { + msg_ext_emit_chunk(); + ui_call_msg_showmode(msg_ext_chunks); + msg_ext_chunks = (Array)ARRAY_DICT_INIT; + } } void msg_ext_clear(bool force) diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c index e752910a4b..a8cfc2d700 100644 --- a/src/nvim/misc1.c +++ b/src/nvim/misc1.c @@ -1819,6 +1819,9 @@ void changed(void) changed_int(); } buf_inc_changedtick(curbuf); + + // If a pattern is highlighted, the position may now be invalid. + highlight_match = false; } /* diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index 50dba92eca..6d1a517ce8 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -12,6 +12,7 @@ #include "nvim/screen.h" #include "nvim/syntax.h" #include "nvim/ui.h" +#include "nvim/ui_compositor.h" #include "nvim/os_unix.h" #include "nvim/fold.h" #include "nvim/diff.h" @@ -441,12 +442,6 @@ win_T *mouse_find_win(int *gridp, int *rowp, int *colp) 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; fp = topframe; @@ -478,15 +473,31 @@ win_T *mouse_find_win(int *gridp, int *rowp, int *colp) 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) { - win_T *wp = get_win_by_grid_handle(*grid); - if (wp && wp->w_grid.chars) { + if (*gridp > 1) { + win_T *wp = get_win_by_grid_handle(*gridp); + if (wp && wp->w_grid.chars + && !(wp->w_floating && !wp->w_float_config.focusable)) { *rowp = MIN(*rowp, wp->w_grid.Rows-1); *colp = MIN(*colp, wp->w_grid.Columns-1); 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; } diff --git a/src/nvim/normal.c b/src/nvim/normal.c index d857d1a79e..49eef72a05 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -2200,6 +2200,7 @@ do_mouse ( * one. Speeds up dragging the status line. */ if (vpeekc() != NUL) { int nc; + int save_mouse_grid = mouse_grid; int save_mouse_row = mouse_row; int save_mouse_col = mouse_col; @@ -2209,6 +2210,7 @@ do_mouse ( if (c == nc) continue; vungetc(nc); + mouse_grid = save_mouse_grid; mouse_row = save_mouse_row; mouse_col = save_mouse_col; } diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 674a9244f0..99dee939fc 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -1974,8 +1974,6 @@ int swapchar(int op_type, pos_T *pos) inc(pos); } - if (enc_dbcs != 0 && c >= 0x100) /* No lower/uppercase letter */ - return FALSE; nc = c; if (mb_islower(c)) { if (op_type == OP_ROT13) { @@ -3610,6 +3608,7 @@ int do_join(size_t count, int remove_comments = (use_formatoptions == TRUE) && has_format_option(FO_REMOVE_COMS); bool prev_was_comment = false; + assert(count >= 1); if (save_undo && u_save(curwin->w_cursor.lnum - 1, curwin->w_cursor.lnum + (linenr_T)count) == FAIL) { diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index c6794e4be5..e7bfbc8240 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -19,6 +19,7 @@ #include "nvim/eval.h" #include "nvim/ex_getln.h" #include "nvim/version.h" +#include "nvim/map.h" #ifdef WIN32 #include "nvim/mbyte.h" // for utf8_to_utf16, utf16_to_utf8 @@ -32,91 +33,164 @@ #include <sys/utsname.h> #endif +// Because `uv_os_getenv` requires allocating, we must manage a map to maintain +// the behavior of `os_getenv`. +static PMap(cstr_t) *envmap; +static uv_mutex_t mutex; + +void env_init(void) +{ + envmap = pmap_new(cstr_t)(); + uv_mutex_init(&mutex); +} + /// Like getenv(), but returns NULL if the variable is empty. const char *os_getenv(const char *name) FUNC_ATTR_NONNULL_ALL { - const char *e = getenv(name); - return e == NULL || *e == NUL ? NULL : e; + char *e; + size_t size = 64; + if (name[0] == '\0') { + return NULL; + } + uv_mutex_lock(&mutex); + if (pmap_has(cstr_t)(envmap, name) + && !!(e = (char *)pmap_get(cstr_t)(envmap, name))) { + if (e[0] != '\0') { + // Found non-empty cached env var. + // NOTE: This risks incoherence if an in-process library changes the + // environment without going through our os_setenv() wrapper. If + // that turns out to be a problem, we can just remove this codepath. + goto end; + } + pmap_del2(envmap, name); + } + e = xmalloc(size); + int r = uv_os_getenv(name, e, &size); + if (r == UV_ENOBUFS) { + e = xrealloc(e, size); + r = uv_os_getenv(name, e, &size); + } + if (r != 0 || size == 0 || e[0] == '\0') { + xfree(e); + e = NULL; + if (r != 0 && r != UV_ENOENT && r != UV_UNKNOWN) { + ELOG("uv_os_getenv(%s) failed: %d %s", name, r, uv_err_name(r)); + } + goto end; + } + pmap_put(cstr_t)(envmap, xstrdup(name), e); +end: + uv_mutex_unlock(&mutex); + return (e == NULL || size == 0 || e[0] == '\0') ? NULL : e; } -/// Returns `true` if the environment variable, `name`, has been defined -/// (even if empty). +/// Returns true if environment variable `name` is defined (even if empty). +/// Returns false if not found (UV_ENOENT) or other failure. bool os_env_exists(const char *name) FUNC_ATTR_NONNULL_ALL { - return getenv(name) != NULL; + if (name[0] == '\0') { + return false; + } + // Use a tiny buffer because we don't care about the value: if uv_os_getenv() + // returns UV_ENOBUFS, the env var was found. + char buf[1]; + size_t size = sizeof(buf); + int r = uv_os_getenv(name, buf, &size); + assert(r != UV_EINVAL); + if (r != 0 && r != UV_ENOENT && r != UV_ENOBUFS) { + ELOG("uv_os_getenv(%s) failed: %d %s", name, r, uv_err_name(r)); + } + return (r == 0 || r == UV_ENOBUFS); } int os_setenv(const char *name, const char *value, int overwrite) FUNC_ATTR_NONNULL_ALL { -#ifdef WIN32 - size_t envbuflen = strlen(name) + strlen(value) + 2; - char *envbuf = xmalloc(envbuflen); - snprintf(envbuf, envbuflen, "%s=%s", name, value); - - wchar_t *p; - utf8_to_utf16(envbuf, &p); - xfree(envbuf); - if (p == NULL) { + if (name[0] == '\0') { return -1; } - _wputenv(p); - xfree(p); // Unlike Unix systems, we can free the string for _wputenv(). - return 0; -#elif defined(HAVE_SETENV) - return setenv(name, value, overwrite); -#elif defined(HAVE_PUTENV_S) +#ifdef WIN32 if (!overwrite && os_getenv(name) != NULL) { return 0; } - if (_putenv_s(name, value) == 0) { +#else + if (!overwrite && os_env_exists(name)) { return 0; } - return -1; -#else -# error "This system has no implementation available for os_setenv()" #endif + uv_mutex_lock(&mutex); + pmap_del2(envmap, name); + int r = uv_os_setenv(name, value); + assert(r != UV_EINVAL); + if (r != 0) { + ELOG("uv_os_setenv(%s) failed: %d %s", name, r, uv_err_name(r)); + } + uv_mutex_unlock(&mutex); + return r == 0 ? 0 : -1; } /// Unset environment variable -/// -/// For systems where unsetenv() is not available the value will be set as an -/// empty string int os_unsetenv(const char *name) + FUNC_ATTR_NONNULL_ALL { -#ifdef HAVE_UNSETENV - return unsetenv(name); -#else - return os_setenv(name, "", 1); -#endif + if (name[0] == '\0') { + return -1; + } + uv_mutex_lock(&mutex); + pmap_del2(envmap, name); + int r = uv_os_unsetenv(name); + if (r != 0) { + ELOG("uv_os_unsetenv(%s) failed: %d %s", name, r, uv_err_name(r)); + } + uv_mutex_unlock(&mutex); + return r == 0 ? 0 : -1; } char *os_getenvname_at_index(size_t index) { +#ifdef _WIN32 + // Check if index is inside the environ array and is not the last element. + for (size_t i = 0; i <= index; i++) { + if (_wenviron[i] == NULL) { + return NULL; + } + } + wchar_t *utf16_str = _wenviron[index]; + char *utf8_str; + int conversion_result = utf16_to_utf8(utf16_str, &utf8_str); + if (conversion_result != 0) { + EMSG2("utf16_to_utf8 failed: %d", conversion_result); + return NULL; + } + size_t namesize = 0; + while (utf8_str[namesize] != '=' && utf8_str[namesize] != NUL) { + namesize++; + } + char *name = (char *)vim_strnsave((char_u *)utf8_str, namesize); + xfree(utf8_str); + return name; +#else # if defined(HAVE__NSGETENVIRON) char **environ = *_NSGetEnviron(); -# elif !defined(__WIN32__) - // Borland C++ 5.2 has this in a header file. +# else extern char **environ; # endif - // check if index is inside the environ array - for (size_t i = 0; i < index; i++) { + // Check if index is inside the environ array and is not the last element. + for (size_t i = 0; i <= index; i++) { if (environ[i] == NULL) { return NULL; } } char *str = environ[index]; - if (str == NULL) { - return NULL; - } size_t namesize = 0; while (str[namesize] != '=' && str[namesize] != NUL) { namesize++; } char *name = (char *)vim_strnsave((char_u *)str, namesize); return name; +#endif } /// Get the process ID of the Neovim process. @@ -349,7 +423,7 @@ void expand_env_esc(char_u *restrict srcp, var = NULL; } else { if (src[1] == '{') { - ++tail; + tail++; } #endif *var = NUL; @@ -527,7 +601,7 @@ static char *remove_tail(char *path, char *pend, char *dirname) return pend; } -/// Iterate over a delimited list. +/// Iterates $PATH-like delimited list `val`. /// /// @note Environment variables must not be modified during iteration. /// @@ -562,7 +636,7 @@ const void *vim_env_iter(const char delim, } } -/// Iterate over a delimited list in reverse order. +/// Iterates $PATH-like delimited list `val` in reverse order. /// /// @note Environment variables must not be modified during iteration. /// @@ -599,11 +673,12 @@ const void *vim_env_iter_rev(const char delim, } } -/// Vim's version of getenv(). -/// Special handling of $HOME, $VIM and $VIMRUNTIME, allowing the user to -/// override the vim runtime directory at runtime. Also does ACP to 'enc' -/// conversion for Win32. Result must be freed by the caller. +/// Vim getenv() wrapper with special handling of $HOME, $VIM, $VIMRUNTIME, +/// allowing the user to override the Nvim runtime directory at runtime. +/// Result must be freed by the caller. +/// /// @param name Environment variable to expand +/// @return [allocated] Expanded environment variable, or NULL char *vim_getenv(const char *name) { // init_path() should have been called before now. @@ -869,9 +944,8 @@ char_u * home_replace_save(buf_T *buf, char_u *src) FUNC_ATTR_NONNULL_RET return dst; } -/// Our portable version of setenv. -/// Has special handling for $VIMRUNTIME to keep the localization machinery -/// sane. +/// Vim setenv() wrapper with special handling for $VIMRUNTIME to keep the +/// localization machinery sane. void vim_setenv(const char *name, const char *val) { os_setenv(name, val, 1); diff --git a/src/nvim/os/fileio.c b/src/nvim/os/fileio.c index ccf35fd57c..bb68326a03 100644 --- a/src/nvim/os/fileio.c +++ b/src/nvim/os/fileio.c @@ -229,7 +229,10 @@ int file_fsync(FileDescriptor *const fp) return flush_error; } const int fsync_error = os_fsync(fp->fd); - if (fsync_error != UV_EINVAL && fsync_error != UV_EROFS) { + if (fsync_error != UV_EINVAL + && fsync_error != UV_EROFS + // fsync not supported on this storage. + && fsync_error != UV_ENOTSUP) { return fsync_error; } return 0; diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c index 9a4391a0ae..7f2ebeec2f 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -643,7 +643,7 @@ ptrdiff_t os_write(const int fd, const char *const buf, const size_t size, /// /// @param fd the file descriptor of the file to flush to disk. /// -/// @return `0` on success, a libuv error code on failure. +/// @return 0 on success, or libuv error code on failure. int os_fsync(int fd) { int r; @@ -1104,7 +1104,7 @@ char *os_resolve_shortcut(const char *fname) if (hr == S_OK && wsz[0] != NUL) { const int conversion_result = utf16_to_utf8(wsz, &rfname); if (conversion_result != 0) { - EMSG2("utf16_to_utf8 failed: %s", uv_strerror(conversion_result)); + EMSG2("utf16_to_utf8 failed: %d", conversion_result); } } @@ -1128,127 +1128,3 @@ shortcut_end: } #endif - -int os_translate_sys_error(int sys_errno) -{ -#ifdef HAVE_UV_TRANSLATE_SYS_ERROR - return uv_translate_sys_error(sys_errno); -#elif defined(WIN32) - // TODO(equalsraf): libuv does not yet expose uv_translate_sys_error() - // in its public API, include a version here until it can be used. - // See https://github.com/libuv/libuv/issues/79 -# ifndef ERROR_SYMLINK_NOT_SUPPORTED -# define ERROR_SYMLINK_NOT_SUPPORTED 1464 -# endif - - if (sys_errno <= 0) { - return sys_errno; // If < 0 then it's already a libuv error - } - - switch (sys_errno) { - case ERROR_NOACCESS: return UV_EACCES; - case WSAEACCES: return UV_EACCES; - case ERROR_ADDRESS_ALREADY_ASSOCIATED: return UV_EADDRINUSE; - case WSAEADDRINUSE: return UV_EADDRINUSE; - case WSAEADDRNOTAVAIL: return UV_EADDRNOTAVAIL; - case WSAEAFNOSUPPORT: return UV_EAFNOSUPPORT; - case WSAEWOULDBLOCK: return UV_EAGAIN; - case WSAEALREADY: return UV_EALREADY; - case ERROR_INVALID_FLAGS: return UV_EBADF; - case ERROR_INVALID_HANDLE: return UV_EBADF; - case ERROR_LOCK_VIOLATION: return UV_EBUSY; - case ERROR_PIPE_BUSY: return UV_EBUSY; - case ERROR_SHARING_VIOLATION: return UV_EBUSY; - case ERROR_OPERATION_ABORTED: return UV_ECANCELED; - case WSAEINTR: return UV_ECANCELED; - case ERROR_NO_UNICODE_TRANSLATION: return UV_ECHARSET; - case ERROR_CONNECTION_ABORTED: return UV_ECONNABORTED; - case WSAECONNABORTED: return UV_ECONNABORTED; - case ERROR_CONNECTION_REFUSED: return UV_ECONNREFUSED; - case WSAECONNREFUSED: return UV_ECONNREFUSED; - case ERROR_NETNAME_DELETED: return UV_ECONNRESET; - case WSAECONNRESET: return UV_ECONNRESET; - case ERROR_ALREADY_EXISTS: return UV_EEXIST; - case ERROR_FILE_EXISTS: return UV_EEXIST; - case ERROR_BUFFER_OVERFLOW: return UV_EFAULT; - case WSAEFAULT: return UV_EFAULT; - case ERROR_HOST_UNREACHABLE: return UV_EHOSTUNREACH; - case WSAEHOSTUNREACH: return UV_EHOSTUNREACH; - case ERROR_INSUFFICIENT_BUFFER: return UV_EINVAL; - case ERROR_INVALID_DATA: return UV_EINVAL; - case ERROR_INVALID_PARAMETER: return UV_EINVAL; - case ERROR_SYMLINK_NOT_SUPPORTED: return UV_EINVAL; - case WSAEINVAL: return UV_EINVAL; - case WSAEPFNOSUPPORT: return UV_EINVAL; - case WSAESOCKTNOSUPPORT: return UV_EINVAL; - case ERROR_BEGINNING_OF_MEDIA: return UV_EIO; - case ERROR_BUS_RESET: return UV_EIO; - case ERROR_CRC: return UV_EIO; - case ERROR_DEVICE_DOOR_OPEN: return UV_EIO; - case ERROR_DEVICE_REQUIRES_CLEANING: return UV_EIO; - case ERROR_DISK_CORRUPT: return UV_EIO; - case ERROR_EOM_OVERFLOW: return UV_EIO; - case ERROR_FILEMARK_DETECTED: return UV_EIO; - case ERROR_GEN_FAILURE: return UV_EIO; - case ERROR_INVALID_BLOCK_LENGTH: return UV_EIO; - case ERROR_IO_DEVICE: return UV_EIO; - case ERROR_NO_DATA_DETECTED: return UV_EIO; - case ERROR_NO_SIGNAL_SENT: return UV_EIO; - case ERROR_OPEN_FAILED: return UV_EIO; - case ERROR_SETMARK_DETECTED: return UV_EIO; - case ERROR_SIGNAL_REFUSED: return UV_EIO; - case WSAEISCONN: return UV_EISCONN; - case ERROR_CANT_RESOLVE_FILENAME: return UV_ELOOP; - case ERROR_TOO_MANY_OPEN_FILES: return UV_EMFILE; - case WSAEMFILE: return UV_EMFILE; - case WSAEMSGSIZE: return UV_EMSGSIZE; - case ERROR_FILENAME_EXCED_RANGE: return UV_ENAMETOOLONG; - case ERROR_NETWORK_UNREACHABLE: return UV_ENETUNREACH; - case WSAENETUNREACH: return UV_ENETUNREACH; - case WSAENOBUFS: return UV_ENOBUFS; - case ERROR_BAD_PATHNAME: return UV_ENOENT; - case ERROR_DIRECTORY: return UV_ENOENT; - case ERROR_FILE_NOT_FOUND: return UV_ENOENT; - case ERROR_INVALID_NAME: return UV_ENOENT; - case ERROR_INVALID_DRIVE: return UV_ENOENT; - case ERROR_INVALID_REPARSE_DATA: return UV_ENOENT; - case ERROR_MOD_NOT_FOUND: return UV_ENOENT; - case ERROR_PATH_NOT_FOUND: return UV_ENOENT; - case WSAHOST_NOT_FOUND: return UV_ENOENT; - case WSANO_DATA: return UV_ENOENT; - case ERROR_NOT_ENOUGH_MEMORY: return UV_ENOMEM; - case ERROR_OUTOFMEMORY: return UV_ENOMEM; - case ERROR_CANNOT_MAKE: return UV_ENOSPC; - case ERROR_DISK_FULL: return UV_ENOSPC; - case ERROR_EA_TABLE_FULL: return UV_ENOSPC; - case ERROR_END_OF_MEDIA: return UV_ENOSPC; - case ERROR_HANDLE_DISK_FULL: return UV_ENOSPC; - case ERROR_NOT_CONNECTED: return UV_ENOTCONN; - case WSAENOTCONN: return UV_ENOTCONN; - case ERROR_DIR_NOT_EMPTY: return UV_ENOTEMPTY; - case WSAENOTSOCK: return UV_ENOTSOCK; - case ERROR_NOT_SUPPORTED: return UV_ENOTSUP; - case ERROR_BROKEN_PIPE: return UV_EOF; - case ERROR_ACCESS_DENIED: return UV_EPERM; - case ERROR_PRIVILEGE_NOT_HELD: return UV_EPERM; - case ERROR_BAD_PIPE: return UV_EPIPE; - case ERROR_NO_DATA: return UV_EPIPE; - case ERROR_PIPE_NOT_CONNECTED: return UV_EPIPE; - case WSAESHUTDOWN: return UV_EPIPE; - case WSAEPROTONOSUPPORT: return UV_EPROTONOSUPPORT; - case ERROR_WRITE_PROTECT: return UV_EROFS; - case ERROR_SEM_TIMEOUT: return UV_ETIMEDOUT; - case WSAETIMEDOUT: return UV_ETIMEDOUT; - case ERROR_NOT_SAME_DEVICE: return UV_EXDEV; - case ERROR_INVALID_FUNCTION: return UV_EISDIR; - case ERROR_META_EXPANSION_TOO_LONG: return UV_E2BIG; - default: return UV_UNKNOWN; - } -#else - const int error = -errno; - STATIC_ASSERT(-EINTR == UV_EINTR, "Need to translate error codes"); - STATIC_ASSERT(-EAGAIN == UV_EAGAIN, "Need to translate error codes"); - STATIC_ASSERT(-ENOMEM == UV_ENOMEM, "Need to translate error codes"); - return error; -#endif -} diff --git a/src/nvim/os/fs_defs.h b/src/nvim/os/fs_defs.h index 2277d926b3..68e5c74ee1 100644 --- a/src/nvim/os/fs_defs.h +++ b/src/nvim/os/fs_defs.h @@ -21,11 +21,12 @@ typedef struct { uv_dirent_t ent; ///< @private The entry information. } Directory; -/// Function to convert libuv error to char * error description -/// -/// negative libuv error codes are returned by a number of os functions. +/// Converts libuv error (negative int) to error description string. #define os_strerror uv_strerror +/// Converts system error code to libuv error code. +#define os_translate_sys_error uv_translate_sys_error + // Values returned by os_nodetype() #define NODE_NORMAL 0 // file or directory, check with os_isdir() #define NODE_WRITABLE 1 // something we can write to (character diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c index 5e2c9ecb36..8070f4c420 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -331,7 +331,6 @@ static unsigned int handle_mouse_event(char **ptr, uint8_t *buf, uint8_t modifiers = check_multiclick(mouse_code, mouse_grid, mouse_row, mouse_col); - if (modifiers) { if (buf[1] != KS_MODIFIER) { // no modifiers in the buffer yet, shift the bytes 3 positions diff --git a/src/nvim/os/pty_process_unix.c b/src/nvim/os/pty_process_unix.c index bcf57e1b5b..5fdf0e6181 100644 --- a/src/nvim/os/pty_process_unix.c +++ b/src/nvim/os/pty_process_unix.c @@ -157,11 +157,11 @@ static void init_child(PtyProcess *ptyproc) // New session/process-group. #6530 setsid(); - unsetenv("COLUMNS"); - unsetenv("LINES"); - unsetenv("TERMCAP"); - unsetenv("COLORTERM"); - unsetenv("COLORFGBG"); + os_unsetenv("COLUMNS"); + os_unsetenv("LINES"); + os_unsetenv("TERMCAP"); + os_unsetenv("COLORTERM"); + os_unsetenv("COLORFGBG"); signal(SIGCHLD, SIG_DFL); signal(SIGHUP, SIG_DFL); @@ -177,7 +177,7 @@ static void init_child(PtyProcess *ptyproc) } char *prog = ptyproc->process.argv[0]; - setenv("TERM", ptyproc->term_name ? ptyproc->term_name : "ansi", 1); + os_setenv("TERM", ptyproc->term_name ? ptyproc->term_name : "ansi", 1); execvp(prog, ptyproc->process.argv); ELOG("execvp failed: %s: %s", strerror(errno), prog); _exit(122); // 122 is EXEC_FAILED in the Vim source. diff --git a/src/nvim/path.c b/src/nvim/path.c index 7903e3f4f4..a706e32773 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -2271,7 +2271,7 @@ int path_is_absolute(const char_u *fname) void path_guess_exepath(const char *argv0, char *buf, size_t bufsize) FUNC_ATTR_NONNULL_ALL { - char *path = getenv("PATH"); + const char *path = os_getenv("PATH"); if (path == NULL || path_is_absolute((char_u *)argv0)) { xstrlcpy(buf, argv0, bufsize); diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c index 3c10b7ae0f..499ee11cad 100644 --- a/src/nvim/popupmnu.c +++ b/src/nvim/popupmnu.c @@ -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_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_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_drawn = false; static bool pum_external = false; static bool pum_invalid = false; // the screen was just cleared -static ScreenGrid pum_grid = SCREEN_GRID_INIT; - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "popupmnu.c.generated.h" #endif @@ -103,9 +103,9 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed) col = curwin->w_wcol; } - int grid = (int)curwin->w_grid.handle; + pum_anchor_grid = (int)curwin->w_grid.handle; if (!ui_has(kUIMultigrid)) { - grid = (int)default_grid.handle; + pum_anchor_grid = (int)default_grid.handle; row += curwin->w_winrow; 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(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 { 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 && row - above_row > (below_row - above_row) / 2) { // pum above "row" + pum_above = true; // Leave two lines of context if possible 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 { // pum below "row" + pum_above = false; // Leave two lines of context if possible 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); 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; pum_invalid = false; @@ -371,6 +373,13 @@ void pum_redraw(void) } else if (invalid_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 @@ -783,6 +792,10 @@ void pum_check_clear(void) ui_call_popupmenu_hide(); } else { 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. grid_free(&pum_grid); } diff --git a/src/nvim/popupmnu.h b/src/nvim/popupmnu.h index 7e1588dbdd..42e6ef5653 100644 --- a/src/nvim/popupmnu.h +++ b/src/nvim/popupmnu.h @@ -1,6 +1,8 @@ #ifndef NVIM_POPUPMNU_H #define NVIM_POPUPMNU_H +#include "nvim/macros.h" +#include "nvim/grid_defs.h" #include "nvim/types.h" /// Used for popup menu items. @@ -11,6 +13,7 @@ typedef struct { char_u *pum_info; // extra info } pumitem_T; +EXTERN ScreenGrid pum_grid INIT(= SCREEN_GRID_INIT); #ifdef INCLUDE_GENERATED_DECLARATIONS # include "popupmnu.h.generated.h" diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index 53479294de..3b3ca29dad 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -2098,18 +2098,20 @@ static char_u *regatom(int *flagp) default: i = -1; break; } - if (i < 0) - EMSG2_RET_NULL( - _("E678: Invalid character after %s%%[dxouU]"), - reg_magic == MAGIC_ALL); - if (use_multibytecode(i)) + if (i < 0 || i > INT_MAX) { + EMSG2_RET_NULL(_("E678: Invalid character after %s%%[dxouU]"), + reg_magic == MAGIC_ALL); + } + if (use_multibytecode(i)) { ret = regnode(MULTIBYTECODE); - else + } else { ret = regnode(EXACTLY); - if (i == 0) + } + if (i == 0) { regc(0x0a); - else + } else { regmbc(i); + } regc(NUL); *flagp |= HASWIDTH; break; @@ -3063,10 +3065,10 @@ static int coll_get_char(void) case 'u': nr = gethexchrs(4); break; case 'U': nr = gethexchrs(8); break; } - if (nr < 0) { - /* If getting the number fails be backwards compatible: the character - * is a backslash. */ - --regparse; + if (nr < 0 || nr > INT_MAX) { + // If getting the number fails be backwards compatible: the character + // is a backslash. + regparse--; nr = '\\'; } return nr; @@ -7088,6 +7090,7 @@ regprog_T *vim_regcomp(char_u *expr_arg, int re_flags) { regprog_T *prog = NULL; char_u *expr = expr_arg; + int save_called_emsg; regexp_engine = p_re; @@ -7114,9 +7117,11 @@ regprog_T *vim_regcomp(char_u *expr_arg, int re_flags) bt_regengine.expr = expr; nfa_regengine.expr = expr; - /* - * First try the NFA engine, unless backtracking was requested. - */ + // + // First try the NFA engine, unless backtracking was requested. + // + save_called_emsg = called_emsg; + called_emsg = false; if (regexp_engine != BACKTRACKING_ENGINE) { prog = nfa_regengine.regcomp(expr, re_flags + (regexp_engine == AUTOMATIC_ENGINE ? RE_AUTO : 0)); @@ -7141,11 +7146,13 @@ regprog_T *vim_regcomp(char_u *expr_arg, int re_flags) // If the NFA engine failed, try the backtracking engine. The NFA engine // also fails for patterns that it can't handle well but are still valid // patterns, thus a retry should work. - if (regexp_engine == AUTOMATIC_ENGINE) { + // But don't try if an error message was given. + if (regexp_engine == AUTOMATIC_ENGINE && !called_emsg) { regexp_engine = BACKTRACKING_ENGINE; prog = bt_regengine.regcomp(expr, re_flags); } } + called_emsg |= save_called_emsg; if (prog != NULL) { // Store the info needed to call regcomp() again when the engine turns out diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c index d34e653058..95030974d8 100644 --- a/src/nvim/regexp_nfa.c +++ b/src/nvim/regexp_nfa.c @@ -1420,12 +1420,12 @@ static int nfa_regatom(void) default: nr = -1; break; } - if (nr < 0) - EMSG2_RET_FAIL( - _("E678: Invalid character after %s%%[dxouU]"), - reg_magic == MAGIC_ALL); - /* A NUL is stored in the text as NL */ - /* TODO: what if a composing character follows? */ + if (nr < 0 || nr > INT_MAX) { + EMSG2_RET_FAIL(_("E678: Invalid character after %s%%[dxouU]"), + reg_magic == MAGIC_ALL); + } + // A NUL is stored in the text as NL + // TODO(vim): what if a composing character follows? EMIT(nr == 0 ? 0x0a : nr); } break; @@ -6476,16 +6476,10 @@ static regprog_T *nfa_regcomp(char_u *expr, int re_flags) nfa_regcomp_start(expr, re_flags); - /* Build postfix form of the regexp. Needed to build the NFA - * (and count its size). */ + // Build postfix form of the regexp. Needed to build the NFA + // (and count its size). postfix = re2post(); if (postfix == NULL) { - // TODO(vim): only give this error for debugging? - if (post_ptr >= post_end) { - IEMSGN("Internal error: estimated max number " - "of states insufficient: %" PRId64, - post_end - post_start); - } goto fail; // Cascaded (syntax?) error } diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 81d463a70c..08bb4e4a52 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -148,9 +148,6 @@ typedef struct { /// Whether to call "ui_call_grid_resize" in win_grid_alloc 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 redraw_popupmenu = false; @@ -197,8 +194,10 @@ void redraw_all_later(int type) void screen_invalidate_highlights(void) { - redraw_all_later(NOT_VALID); - highlights_invalid = true; + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + 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, 0, (int)Columns); FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_floating) { + continue; + } if (wp->w_winrow < msg_scrolled) { if (W_ENDROW(wp) > msg_scrolled && wp->w_redr_type < REDRAW_TOP @@ -361,6 +363,10 @@ void update_screen(int type) need_wait_return = FALSE; } + if (type >= NOT_VALID) { + ui_comp_set_screen_valid(false); + } + win_ui_flush_positions(); msg_ext_check_prompt(); /* reset cmdline_row now (may have been changed temporarily) */ @@ -376,9 +382,11 @@ void update_screen(int type) type = NOT_VALID; // must_redraw may be set indirectly, avoid another redraw later must_redraw = 0; - } else if (highlights_invalid) { + } else if (!default_grid.valid) { grid_invalidate(&default_grid); + default_grid.valid = true; } + ui_comp_set_screen_valid(true); if (clear_cmdline) /* going to clear cmdline (done below) */ check_for_delay(FALSE); @@ -449,7 +457,14 @@ void update_screen(int type) */ did_one = FALSE; search_hl.rm.regprog = NULL; + + 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 (!did_one) { did_one = TRUE; @@ -471,7 +486,6 @@ void update_screen(int type) } send_grid_resize = false; - highlights_invalid = false; redraw_popupmenu = false; /* Reset b_mod_set flags. Going through all windows is probably faster @@ -2603,7 +2617,7 @@ win_line ( } // Highlight one character for an empty match. if (shl->startcol == shl->endcol) { - if (has_mbyte && line[shl->endcol] != NUL) { + if (line[shl->endcol] != NUL) { shl->endcol += (*mb_ptr2len)(line + shl->endcol); } else { ++shl->endcol; @@ -2949,13 +2963,8 @@ win_line ( shl->endcol = MAXCOL; if (shl->startcol == shl->endcol) { - /* highlight empty match, try again after - * it */ - if (has_mbyte) - shl->endcol += (*mb_ptr2len)(line - + shl->endcol); - else - ++shl->endcol; + // highlight empty match, try again after it + shl->endcol += (*mb_ptr2len)(line + shl->endcol); } /* Loop to check if the match starts at the @@ -3055,7 +3064,7 @@ win_line ( if (c_extra != NUL || (n_extra == 1 && c_final != NUL)) { c = (n_extra == 1 && c_final != NUL) ? c_final : c_extra; mb_c = c; // doesn't handle non-utf-8 multi-byte! - if (enc_utf8 && utf_char2len(c) > 1) { + if (utf_char2len(c) > 1) { mb_utf8 = true; u8cc[0] = 0; c = 0xc0; @@ -3064,43 +3073,38 @@ win_line ( } } else { c = *p_extra; - if (has_mbyte) { + mb_c = c; + // If the UTF-8 character is more than one byte: + // Decode it into "mb_c". + mb_l = utfc_ptr2len(p_extra); + mb_utf8 = false; + if (mb_l > n_extra) { + mb_l = 1; + } else if (mb_l > 1) { + mb_c = utfc_ptr2char(p_extra, u8cc); + mb_utf8 = true; + c = 0xc0; + } + if (mb_l == 0) { // at the NUL at end-of-line + mb_l = 1; + } + + // If a double-width char doesn't fit display a '>' in the last column. + if ((wp->w_p_rl ? (col <= 0) : (col >= grid->Columns - 1)) + && (*mb_char2cells)(mb_c) == 2) { + c = '>'; mb_c = c; - if (enc_utf8) { - // If the UTF-8 character is more than one byte: - // Decode it into "mb_c". - mb_l = utfc_ptr2len(p_extra); - mb_utf8 = false; - if (mb_l > n_extra) { - mb_l = 1; - } else if (mb_l > 1) { - mb_c = utfc_ptr2char(p_extra, u8cc); - mb_utf8 = true; - c = 0xc0; - } - } - if (mb_l == 0) /* at the NUL at end-of-line */ - mb_l = 1; - - /* If a double-width char doesn't fit display a '>' in the - * last column. */ - if ((wp->w_p_rl ? (col <= 0) : - (col >= grid->Columns - 1)) - && (*mb_char2cells)(mb_c) == 2) { - c = '>'; - mb_c = c; - mb_l = 1; - mb_utf8 = false; - multi_attr = win_hl_attr(wp, HLF_AT); + mb_l = 1; + mb_utf8 = false; + multi_attr = win_hl_attr(wp, HLF_AT); - // put the pointer back to output the double-width - // character at the start of the next line. - n_extra++; - p_extra--; - } else { - n_extra -= mb_l - 1; - p_extra += mb_l - 1; - } + // put the pointer back to output the double-width + // character at the start of the next line. + n_extra++; + p_extra--; + } else { + n_extra -= mb_l - 1; + p_extra += mb_l - 1; } ++p_extra; } @@ -3115,151 +3119,113 @@ win_line ( // Get a character from the line itself. c0 = c = *ptr; - if (has_mbyte) { - mb_c = c; - if (enc_utf8) { - // If the UTF-8 character is more than one byte: Decode it - // into "mb_c". - mb_l = utfc_ptr2len(ptr); - mb_utf8 = false; - if (mb_l > 1) { - mb_c = utfc_ptr2char(ptr, u8cc); - // Overlong encoded ASCII or ASCII with composing char - // is displayed normally, except a NUL. - if (mb_c < 0x80) { - c0 = c = mb_c; - } - mb_utf8 = true; + mb_c = c; + // If the UTF-8 character is more than one byte: Decode it + // into "mb_c". + mb_l = utfc_ptr2len(ptr); + mb_utf8 = false; + if (mb_l > 1) { + mb_c = utfc_ptr2char(ptr, u8cc); + // Overlong encoded ASCII or ASCII with composing char + // is displayed normally, except a NUL. + if (mb_c < 0x80) { + c0 = c = mb_c; + } + mb_utf8 = true; - /* At start of the line we can have a composing char. - * Draw it as a space with a composing char. */ - if (utf_iscomposing(mb_c)) { - int i; + // At start of the line we can have a composing char. + // Draw it as a space with a composing char. + if (utf_iscomposing(mb_c)) { + int i; - for (i = MAX_MCO - 1; i > 0; i--) { - u8cc[i] = u8cc[i - 1]; - } - u8cc[0] = mb_c; - mb_c = ' '; - } + for (i = MAX_MCO - 1; i > 0; i--) { + u8cc[i] = u8cc[i - 1]; } + u8cc[0] = mb_c; + mb_c = ' '; + } + } - if ((mb_l == 1 && c >= 0x80) - || (mb_l >= 1 && mb_c == 0) - || (mb_l > 1 && (!vim_isprintc(mb_c)))) { - // Illegal UTF-8 byte: display as <xx>. - // Non-BMP character : display as ? or fullwidth ?. - transchar_hex((char *)extra, mb_c); - if (wp->w_p_rl) { // reverse - rl_mirror(extra); - } - - p_extra = extra; - c = *p_extra; - mb_c = mb_ptr2char_adv((const char_u **)&p_extra); - mb_utf8 = (c >= 0x80); - n_extra = (int)STRLEN(p_extra); - c_extra = NUL; - c_final = NUL; - if (area_attr == 0 && search_attr == 0) { - n_attr = n_extra + 1; - extra_attr = win_hl_attr(wp, HLF_8); - saved_attr2 = char_attr; // save current attr - } - } else if (mb_l == 0) /* at the NUL at end-of-line */ - mb_l = 1; - else if (p_arshape && !p_tbidi && arabic_char(mb_c)) { - /* Do Arabic shaping. */ - int pc, pc1, nc; - int pcc[MAX_MCO]; - - /* The idea of what is the previous and next - * character depends on 'rightleft'. */ - if (wp->w_p_rl) { - pc = prev_c; - pc1 = prev_c1; - nc = utf_ptr2char(ptr + mb_l); - prev_c1 = u8cc[0]; - } else { - pc = utfc_ptr2char(ptr + mb_l, pcc); - nc = prev_c; - pc1 = pcc[0]; - } - prev_c = mb_c; + if ((mb_l == 1 && c >= 0x80) + || (mb_l >= 1 && mb_c == 0) + || (mb_l > 1 && (!vim_isprintc(mb_c)))) { + // Illegal UTF-8 byte: display as <xx>. + // Non-BMP character : display as ? or fullwidth ?. + transchar_hex((char *)extra, mb_c); + if (wp->w_p_rl) { // reverse + rl_mirror(extra); + } - mb_c = arabic_shape(mb_c, &c, &u8cc[0], pc, pc1, nc); - } else - prev_c = mb_c; - } else { /* enc_dbcs */ - mb_l = MB_BYTE2LEN(c); - if (mb_l == 0) /* at the NUL at end-of-line */ - mb_l = 1; - else if (mb_l > 1) { - /* We assume a second byte below 32 is illegal. - * Hopefully this is OK for all double-byte encodings! - */ - if (ptr[1] >= 32) - mb_c = (c << 8) + ptr[1]; - else { - if (ptr[1] == NUL) { - /* head byte at end of line */ - mb_l = 1; - transchar_nonprint(extra, c); - } else { - /* illegal tail byte */ - mb_l = 2; - STRCPY(extra, "XX"); - } - p_extra = extra; - n_extra = (int)STRLEN(extra) - 1; - c_extra = NUL; - c_final = NUL; - c = *p_extra++; - if (area_attr == 0 && search_attr == 0) { - n_attr = n_extra + 1; - extra_attr = win_hl_attr(wp, HLF_8); - saved_attr2 = char_attr; // save current attr - } - mb_c = c; - } - } + p_extra = extra; + c = *p_extra; + mb_c = mb_ptr2char_adv((const char_u **)&p_extra); + mb_utf8 = (c >= 0x80); + n_extra = (int)STRLEN(p_extra); + c_extra = NUL; + c_final = NUL; + if (area_attr == 0 && search_attr == 0) { + n_attr = n_extra + 1; + extra_attr = win_hl_attr(wp, HLF_8); + saved_attr2 = char_attr; // save current attr } - /* If a double-width char doesn't fit display a '>' in the - * last column; the character is displayed at the start of the - * next line. */ - if ((wp->w_p_rl ? (col <= 0) : - (col >= grid->Columns - 1)) - && (*mb_char2cells)(mb_c) == 2) { - c = '>'; - mb_c = c; - mb_utf8 = false; - mb_l = 1; - multi_attr = win_hl_attr(wp, HLF_AT); - // Put pointer back so that the character will be - // displayed at the start of the next line. - ptr--; - } else if (*ptr != NUL) { - ptr += mb_l - 1; + } else if (mb_l == 0) { // at the NUL at end-of-line + mb_l = 1; + } else if (p_arshape && !p_tbidi && arabic_char(mb_c)) { + // Do Arabic shaping. + int pc, pc1, nc; + int pcc[MAX_MCO]; + + // The idea of what is the previous and next + // character depends on 'rightleft'. + if (wp->w_p_rl) { + pc = prev_c; + pc1 = prev_c1; + nc = utf_ptr2char(ptr + mb_l); + prev_c1 = u8cc[0]; + } else { + pc = utfc_ptr2char(ptr + mb_l, pcc); + nc = prev_c; + pc1 = pcc[0]; } + prev_c = mb_c; - /* If a double-width char doesn't fit at the left side display - * a '<' in the first column. Don't do this for unprintable - * characters. */ - if (n_skip > 0 && mb_l > 1 && n_extra == 0) { - n_extra = 1; - c_extra = MB_FILLER_CHAR; - c_final = NUL; - c = ' '; - if (area_attr == 0 && search_attr == 0) { - n_attr = n_extra + 1; - extra_attr = win_hl_attr(wp, HLF_AT); - saved_attr2 = char_attr; // save current attr - } - mb_c = c; - mb_utf8 = false; - mb_l = 1; + mb_c = arabic_shape(mb_c, &c, &u8cc[0], pc, pc1, nc); + } else { + prev_c = mb_c; + } + // If a double-width char doesn't fit display a '>' in the + // last column; the character is displayed at the start of the + // next line. + if ((wp->w_p_rl ? (col <= 0) : + (col >= grid->Columns - 1)) + && (*mb_char2cells)(mb_c) == 2) { + c = '>'; + mb_c = c; + mb_utf8 = false; + mb_l = 1; + multi_attr = win_hl_attr(wp, HLF_AT); + // Put pointer back so that the character will be + // displayed at the start of the next line. + ptr--; + } else if (*ptr != NUL) { + ptr += mb_l - 1; + } + + // If a double-width char doesn't fit at the left side display a '<' in + // the first column. Don't do this for unprintable characters. + if (n_skip > 0 && mb_l > 1 && n_extra == 0) { + n_extra = 1; + c_extra = MB_FILLER_CHAR; + c_final = NUL; + c = ' '; + if (area_attr == 0 && search_attr == 0) { + n_attr = n_extra + 1; + extra_attr = win_hl_attr(wp, HLF_AT); + saved_attr2 = char_attr; // save current attr } - + mb_c = c; + mb_utf8 = false; + mb_l = 1; } ptr++; @@ -3317,11 +3283,8 @@ win_line ( char_u *p; int len; hlf_T spell_hlf = HLF_COUNT; - if (has_mbyte) { - prev_ptr = ptr - mb_l; - v -= mb_l - 1; - } else - prev_ptr = ptr - 1; + prev_ptr = ptr - mb_l; + v -= mb_l - 1; /* Use nextline[] if possible, it has the start of the * next line concatenated. */ @@ -3431,7 +3394,7 @@ win_line ( extra_attr = win_hl_attr(wp, HLF_0); saved_attr2 = char_attr; // save current attr mb_c = c; - if (enc_utf8 && utf_char2len(c) > 1) { + if (utf_char2len(c) > 1) { mb_utf8 = true; u8cc[0] = 0; c = 0xc0; @@ -3446,7 +3409,7 @@ win_line ( extra_attr = win_hl_attr(wp, HLF_0); saved_attr2 = char_attr; // save current attr mb_c = c; - if (enc_utf8 && utf_char2len(c) > 1) { + if (utf_char2len(c) > 1) { mb_utf8 = true; u8cc[0] = 0; c = 0xc0; @@ -3553,7 +3516,7 @@ win_line ( extra_attr = win_hl_attr(wp, HLF_0); saved_attr2 = char_attr; // save current attr mb_c = c; - if (enc_utf8 && utf_char2len(c) > 1) { + if (utf_char2len(c) > 1) { mb_utf8 = true; u8cc[0] = 0; c = 0xc0; @@ -3600,7 +3563,7 @@ win_line ( extra_attr = win_hl_attr(wp, HLF_AT); n_attr = 1; mb_c = c; - if (enc_utf8 && utf_char2len(c) > 1) { + if (utf_char2len(c) > 1) { mb_utf8 = true; u8cc[0] = 0; c = 0xc0; @@ -3689,7 +3652,7 @@ win_line ( n_skip = 1; } mb_c = c; - if (enc_utf8 && utf_char2len(c) > 1) { + if (utf_char2len(c) > 1) { mb_utf8 = true; u8cc[0] = 0; c = 0xc0; @@ -3735,9 +3698,9 @@ win_line ( && c != NUL) { c = wp->w_p_lcs_chars.prec; lcs_prec_todo = NUL; - if (has_mbyte && (*mb_char2cells)(mb_c) > 1) { - /* Double-width character being overwritten by the "precedes" - * character, need to fill up half the character. */ + if ((*mb_char2cells)(mb_c) > 1) { + // Double-width character being overwritten by the "precedes" + // character, need to fill up half the character. c_extra = MB_FILLER_CHAR; c_final = NUL; n_extra = 1; @@ -3745,7 +3708,7 @@ win_line ( extra_attr = win_hl_attr(wp, HLF_AT); } mb_c = c; - if (enc_utf8 && utf_char2len(c) > 1) { + if (utf_char2len(c) > 1) { mb_utf8 = true; u8cc[0] = 0; c = 0xc0; @@ -4019,7 +3982,7 @@ win_line ( c = wp->w_p_lcs_chars.ext; char_attr = win_hl_attr(wp, HLF_AT); mb_c = c; - if (enc_utf8 && utf_char2len(c) > 1) { + if (utf_char2len(c) > 1) { mb_utf8 = true; u8cc[0] = 0; c = 0xc0; @@ -4060,13 +4023,13 @@ win_line ( */ vcol_prev = vcol; if (draw_state < WL_LINE || n_skip <= 0) { - /* - * Store the character. - */ - if (has_mbyte && wp->w_p_rl && (*mb_char2cells)(mb_c) > 1) { - /* A double-wide character is: put first halve in left cell. */ - --off; - --col; + // + // Store the character. + // + if (wp->w_p_rl && (*mb_char2cells)(mb_c) > 1) { + // A double-wide character is: put first halve in left cell. + off--; + col--; } if (mb_utf8) { schar_from_cc(linebuf_char[off], mb_c, u8cc); @@ -4080,7 +4043,7 @@ win_line ( linebuf_attr[off] = char_attr; } - if (has_mbyte && (*mb_char2cells)(mb_c) > 1) { + if ((*mb_char2cells)(mb_c) > 1) { // Need to fill two screen columns. off++; col++; @@ -4140,8 +4103,8 @@ win_line ( } - if (has_mbyte && (*mb_char2cells)(mb_c) > 1) { - /* Need to fill two screen columns. */ + if ((*mb_char2cells)(mb_c) > 1) { + // Need to fill two screen columns. if (wp->w_p_rl) { --boguscols; --col; @@ -4287,9 +4250,12 @@ win_line ( /// If UI did not request multigrid support, draw all windows on the /// 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 /// 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) { *row_off += (*grid)->row_offset; @@ -4738,8 +4704,8 @@ win_redr_status_matches ( for (; *s != NUL; ++s) { s += skip_status_match_char(xp, s); clen += ptr2cells(s); - if (has_mbyte && (l = (*mb_ptr2len)(s)) > 1) { - STRNCPY(buf + len, s, l); + if ((l = (*mb_ptr2len)(s)) > 1) { + STRNCPY(buf + len, s, l); // NOLINT(runtime/printf) s += l - 1; len += l; } else { @@ -5007,10 +4973,11 @@ get_keymap_str ( curbuf = old_curbuf; curwin = old_curwin; if (p == NULL || *p == NUL) { - if (wp->w_buffer->b_kmap_state & KEYMAP_LOADED) + if (wp->w_buffer->b_kmap_state & KEYMAP_LOADED) { p = wp->w_buffer->b_p_keymap; - else + } else { p = (char_u *)"lang"; + } } if (vim_snprintf((char *)buf, len, (char *)fmt, p) > len - 1) { buf[0] = NUL; @@ -5292,7 +5259,6 @@ void grid_getbytes(ScreenGrid *grid, int row, int col, char_u *bytes, screen_adjust_grid(&grid, &row, &col); - // safety check if (grid->chars != NULL && row < grid->Rows && col < grid->Columns) { off = grid->line_offset[row] + col; @@ -5672,12 +5638,10 @@ next_search_hl ( shl->lnum = 0; break; } - if (has_mbyte) - matchcol += mb_ptr2len(ml); - else - ++matchcol; - } else + matchcol += mb_ptr2len(ml); + } else { matchcol = shl->rm.endpos[0].col; + } shl->lnum = lnum; if (shl->rm.regprog != NULL) { @@ -5812,18 +5776,16 @@ void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, } for (int row = start_row; row < end_row; row++) { - if (has_mbyte) { - // When drawing over the right halve of a double-wide char clear - // out the left halve. When drawing over the left halve of a - // double wide-char clear out the right halve. Only needed in a - // terminal. - if (start_col > 0 && grid_fix_col(grid, start_col, row) != start_col) { - grid_puts_len(grid, (char_u *)" ", 1, row, start_col - 1, 0); - } - if (end_col < grid->Columns - && grid_fix_col(grid, end_col, row) != end_col) { - grid_puts_len(grid, (char_u *)" ", 1, row, end_col, 0); - } + // When drawing over the right halve of a double-wide char clear + // out the left halve. When drawing over the left halve of a + // double wide-char clear out the right halve. Only needed in a + // terminal. + if (start_col > 0 && grid_fix_col(grid, start_col, row) != start_col) { + grid_puts_len(grid, (char_u *)" ", 1, row, start_col - 1, 0); + } + if (end_col < grid->Columns + && grid_fix_col(grid, end_col, row) != end_col) { + grid_puts_len(grid, (char_u *)" ", 1, row, end_col, 0); } // if grid was resized (in ext_multigrid mode), the UI has no redraw updates @@ -5909,14 +5871,9 @@ void win_grid_alloc(win_T *wp) int rows = wp->w_height_inner; int cols = wp->w_width_inner; - // TODO(bfredl): floating windows should force this to true - bool want_allocation = ui_has(kUIMultigrid); + bool want_allocation = ui_has(kUIMultigrid) || wp->w_floating; bool has_allocation = (grid->chars != NULL); - if (want_allocation && has_allocation && highlights_invalid) { - grid_invalidate(grid); - } - if (grid->Rows != rows) { wp->w_lines_valid = 0; xfree(wp->w_lines); @@ -5928,15 +5885,20 @@ void win_grid_alloc(win_T *wp) || grid->Rows != rows || grid->Columns != cols) { 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 { // Single grid mode, all rendering will be redirected to default_grid. // Only keep track of the size and offset of the window. grid_free(grid); grid->Rows = rows; grid->Columns = cols; + grid->valid = false; } 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; @@ -5946,7 +5908,7 @@ void win_grid_alloc(win_T *wp) // - a grid was just resized // - screen_resize was called and all grid sizes must be sent // - 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); } } @@ -6008,7 +5970,7 @@ retry: // win_new_shellsize will recompute floats position, but tell the // 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 */ @@ -6162,6 +6124,8 @@ void screenclear(void) } ui_call_grid_clear(1); // clear the display + ui_comp_set_screen_valid(true); + clear_cmdline = false; mode_displayed = false; @@ -6170,6 +6134,11 @@ void screenclear(void) redraw_tabline = true; redraw_popupmenu = true; pum_invalidate(); + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_floating) { + wp->w_redr_type = CLEAR; + } + } if (must_redraw == CLEAR) { must_redraw = NOT_VALID; // no need to clear again } @@ -6559,14 +6528,14 @@ int showmode(void) if (VIsual_active) clear_showcmd(); - /* If the last window has no status line, the ruler is after the mode - * message and must be redrawn */ - if (redrawing() - && lastwin->w_status_height == 0 - ) - win_redr_ruler(lastwin, TRUE); - redraw_cmdline = FALSE; - clear_cmdline = FALSE; + // If the last window has no status line, the ruler is after the mode + // message and must be redrawn + win_T *last = lastwin_nofloating(); + if (redrawing() && last->w_status_height == 0) { + win_redr_ruler(last, true); + } + redraw_cmdline = false; + clear_cmdline = false; return length; } @@ -6747,14 +6716,9 @@ static void draw_tabline(void) (void)shorten_dir(NameBuff); len = vim_strsize(NameBuff); p = NameBuff; - if (has_mbyte) - while (len > room) { - len -= ptr2cells(p); - MB_PTR_ADV(p); - } - else if (len > room) { - p += len - room; - len = room; + while (len > room) { + len -= ptr2cells(p); + MB_PTR_ADV(p); } if (len > Columns - col - 1) { len = Columns - col - 1; @@ -7150,7 +7114,7 @@ void screen_resize(int width, int height) check_shellsize(); height = Rows; width = Columns; - ui_resize(width, height); + ui_call_grid_resize(1, width, height); send_grid_resize = true; diff --git a/src/nvim/search.c b/src/nvim/search.c index d635763acc..f6b80d1b79 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -360,17 +360,16 @@ int ignorecase_opt(char_u *pat, int ic_in, int scs) return ic; } -/* - * Return TRUE if pattern "pat" has an uppercase character. - */ -int pat_has_uppercase(char_u *pat) +/// Returns true if pattern `pat` has an uppercase character. +bool pat_has_uppercase(char_u *pat) + FUNC_ATTR_NONNULL_ALL { char_u *p = pat; while (*p != NUL) { - int l; + const int l = mb_ptr2len(p); - if ((l = mb_ptr2len(p)) > 1) { + if (l > 1) { if (mb_isupper(utf_ptr2char(p))) { return true; } @@ -391,7 +390,7 @@ int pat_has_uppercase(char_u *pat) p++; } } - return FALSE; + return false; } const char *last_csearch(void) @@ -744,37 +743,29 @@ int searchit( } else break; - /* - * We found a valid match, now check if there is - * another one after it. - * If vi-compatible searching, continue at the end - * of the match, otherwise continue one position - * forward. - */ + // We found a valid match, now check if there is + // another one after it. + // If vi-compatible searching, continue at the end + // of the match, otherwise continue one position + // forward. if (vim_strchr(p_cpo, CPO_SEARCH) != NULL) { - if (nmatched > 1) + if (nmatched > 1) { break; + } matchcol = endpos.col; - /* for empty match: advance one char */ + // for empty match: advance one char if (matchcol == matchpos.col && ptr[matchcol] != NUL) { - if (has_mbyte) - matchcol += - (*mb_ptr2len)(ptr + matchcol); - else - ++matchcol; + matchcol += mb_ptr2len(ptr + matchcol); } } else { - /* Stop when the match is in a next line. */ - if (matchpos.lnum > 0) + // Stop when the match is in a next line. + if (matchpos.lnum > 0) { break; + } matchcol = matchpos.col; if (ptr[matchcol] != NUL) { - if (has_mbyte) - matchcol += - (*mb_ptr2len)(ptr + matchcol); - else - ++matchcol; + matchcol += mb_ptr2len(ptr + matchcol); } } if (ptr[matchcol] == NUL @@ -3229,37 +3220,18 @@ static int in_html_tag(int end_tag) int lc = NUL; pos_T pos; - if (enc_dbcs) { - char_u *lp = NULL; - - /* We search forward until the cursor, because searching backwards is - * very slow for DBCS encodings. */ - for (p = line; p < line + curwin->w_cursor.col; MB_PTR_ADV(p)) { - if (*p == '>' || *p == '<') { - lc = *p; - lp = p; - } - } - if (*p != '<') { // check for '<' under cursor - if (lc != '<') { - return false; - } - p = lp; - } - } else { - for (p = line + curwin->w_cursor.col; p > line; ) { - if (*p == '<') { // find '<' under/before cursor - break; - } - MB_PTR_BACK(line, p); - if (*p == '>') { // find '>' before cursor - break; - } + for (p = line + curwin->w_cursor.col; p > line; ) { + if (*p == '<') { // find '<' under/before cursor + break; } - if (*p != '<') { - return false; + MB_PTR_BACK(line, p); + if (*p == '>') { // find '>' before cursor + break; } } + if (*p != '<') { + return false; + } pos.lnum = curwin->w_cursor.lnum; pos.col = (colnr_T)(p - line); @@ -3655,16 +3627,14 @@ find_next_quote( for (;; ) { c = line[col]; - if (c == NUL) + if (c == NUL) { return -1; - else if (escape != NULL && vim_strchr(escape, c)) - ++col; - else if (c == quotechar) + } else if (escape != NULL && vim_strchr(escape, c)) { + col++; + } else if (c == quotechar) { break; - if (has_mbyte) - col += (*mb_ptr2len)(line + col); - else - ++col; + } + col += mb_ptr2len(line + col); } return col; } @@ -3717,11 +3687,12 @@ current_quote( int col_end; int col_start = curwin->w_cursor.col; bool inclusive = false; - int vis_empty = TRUE; /* Visual selection <= 1 char */ - int vis_bef_curs = FALSE; /* Visual starts before cursor */ - int inside_quotes = FALSE; /* Looks like "i'" done before */ - int selected_quote = FALSE; /* Has quote inside selection */ + int vis_empty = true; // Visual selection <= 1 char + int vis_bef_curs = false; // Visual starts before cursor + int inside_quotes = false; // Looks like "i'" done before + int selected_quote = false; // Has quote inside selection int i; + int restore_vis_bef = false; // resotre VIsual on abort // Correct cursor when 'selection' is "exclusive". if (VIsual_active) { @@ -3739,6 +3710,7 @@ current_quote( curwin->w_cursor = VIsual; VIsual = t; vis_bef_curs = true; + restore_vis_bef = true; } dec_cursor(); } @@ -3779,8 +3751,9 @@ current_quote( /* Assume we are on a closing quote: move to after the next * opening quote. */ col_start = find_next_quote(line, col_start + 1, quotechar, NULL); - if (col_start < 0) - return FALSE; + if (col_start < 0) { + goto abort_search; + } col_end = find_next_quote(line, col_start + 1, quotechar, curbuf->b_p_qe); if (col_end < 0) { @@ -3790,8 +3763,9 @@ current_quote( } } else { col_end = find_prev_quote(line, col_start, quotechar, NULL); - if (line[col_end] != quotechar) - return FALSE; + if (line[col_end] != quotechar) { + goto abort_search; + } col_start = find_prev_quote(line, col_end, quotechar, curbuf->b_p_qe); if (line[col_start] != quotechar) { @@ -3819,17 +3793,20 @@ current_quote( for (;; ) { /* Find open quote character. */ col_start = find_next_quote(line, col_start, quotechar, NULL); - if (col_start < 0 || col_start > first_col) - return FALSE; - /* Find close quote character. */ + if (col_start < 0 || col_start > first_col) { + goto abort_search; + } + // Find close quote character. col_end = find_next_quote(line, col_start + 1, quotechar, curbuf->b_p_qe); - if (col_end < 0) - return FALSE; - /* If is cursor between start and end quote character, it is - * target text object. */ - if (col_start <= first_col && first_col <= col_end) + if (col_end < 0) { + goto abort_search; + } + // If is cursor between start and end quote character, it is + // target text object. + if (col_start <= first_col && first_col <= col_end) { break; + } col_start = col_end + 1; } } else { @@ -3838,15 +3815,17 @@ current_quote( if (line[col_start] != quotechar) { /* No quote before the cursor, look after the cursor. */ col_start = find_next_quote(line, col_start, quotechar, NULL); - if (col_start < 0) - return FALSE; + if (col_start < 0) { + goto abort_search; + } } /* Find close quote character. */ col_end = find_next_quote(line, col_start + 1, quotechar, - curbuf->b_p_qe); - if (col_end < 0) - return FALSE; + curbuf->b_p_qe); + if (col_end < 0) { + goto abort_search; + } } /* When "include" is TRUE, include spaces after closing quote or before @@ -3923,6 +3902,18 @@ current_quote( } return OK; + +abort_search: + if (VIsual_active && *p_sel == 'e') { + inc_cursor(); + if (restore_vis_bef) { + pos_T t = curwin->w_cursor; + + curwin->w_cursor = VIsual; + VIsual = t; + } + } + return false; } diff --git a/src/nvim/state.c b/src/nvim/state.c index bfd73050c3..e6eeaac8d0 100644 --- a/src/nvim/state.c +++ b/src/nvim/state.c @@ -162,6 +162,10 @@ char *get_mode(void) buf[1] = 'o'; // to be able to detect force-linewise/blockwise/characterwise operations buf[2] = (char)motion_force; + } else if (restart_edit == 'I' || restart_edit == 'R' + || restart_edit == 'V') { + buf[1] = 'i'; + buf[2] = (char)restart_edit; } } diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 8b4ad4d3af..ffe650f416 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -1112,11 +1112,15 @@ static void refresh_terminal(Terminal *term) return; } long ml_before = buf->b_ml.ml_line_count; - WITH_BUFFER(buf, { - refresh_size(term, buf); - refresh_scrollback(term, buf); - refresh_screen(term, buf); - }); + + // refresh_ functions assume the terminal buffer is current + aco_save_T aco; + aucmd_prepbuf(&aco, buf); + refresh_size(term, buf); + refresh_scrollback(term, buf); + refresh_screen(term, buf); + aucmd_restbuf(&aco); + long ml_added = buf->b_ml.ml_line_count - ml_before; adjust_topline(term, buf, ml_added); } diff --git a/src/nvim/testdir/test_findfile.vim b/src/nvim/testdir/test_findfile.vim index 78e51ed836..0bae161a8b 100644 --- a/src/nvim/testdir/test_findfile.vim +++ b/src/nvim/testdir/test_findfile.vim @@ -119,6 +119,14 @@ func Test_findfile() let &shellslash = save_shellslash endfunc +func Test_findfile_error() + call assert_fails('call findfile([])', 'E730:') + call assert_fails('call findfile("x", [])', 'E730:') + call assert_fails('call findfile("x", "", [])', 'E745:') + call assert_fails('call findfile("x", "**x")', 'E343:') + call assert_fails('call findfile("x", repeat("x", 5000))', 'E854:') +endfunc + " Test finddir({name} [, {path} [, {count}]]) func Test_finddir() let save_path = &path @@ -167,3 +175,11 @@ func Test_finddir() let &path = save_path let &shellslash = save_shellslash endfunc + +func Test_finddir_error() + call assert_fails('call finddir([])', 'E730:') + call assert_fails('call finddir("x", [])', 'E730:') + call assert_fails('call finddir("x", "", [])', 'E745:') + call assert_fails('call finddir("x", "**x")', 'E343:') + call assert_fails('call finddir("x", repeat("x", 5000))', 'E854:') +endfunc diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim index 7dc9f31ce7..bfe13d6b2d 100644 --- a/src/nvim/testdir/test_functions.vim +++ b/src/nvim/testdir/test_functions.vim @@ -556,6 +556,18 @@ func Test_mode() call assert_equal('n', mode(0)) call assert_equal('n', mode(1)) + " i_CTRL-O + exe "normal i\<C-O>:call Save_mode()\<Cr>\<Esc>" + call assert_equal("n-niI", g:current_modes) + + " R_CTRL-O + exe "normal R\<C-O>:call Save_mode()\<Cr>\<Esc>" + call assert_equal("n-niR", g:current_modes) + + " gR_CTRL-O + exe "normal gR\<C-O>:call Save_mode()\<Cr>\<Esc>" + call assert_equal("n-niV", g:current_modes) + " How to test operator-pending mode? call feedkeys("v", 'xt') diff --git a/src/nvim/testdir/test_listdict.vim b/src/nvim/testdir/test_listdict.vim index 999d4dbd4a..9e060cdff6 100644 --- a/src/nvim/testdir/test_listdict.vim +++ b/src/nvim/testdir/test_listdict.vim @@ -635,17 +635,67 @@ func Test_listdict_compare_complex() endfunc func Test_listdict_extend() + " Test extend() with lists + " Pass the same List to extend() - let l = [1, 2, 3, 4, 5] - call extend(l, l) - call assert_equal([1, 2, 3, 4, 5, 1, 2, 3, 4, 5], l) + let l = [1, 2, 3] + call assert_equal([1, 2, 3, 1, 2, 3], extend(l, l)) + call assert_equal([1, 2, 3, 1, 2, 3], l) + + let l = [1, 2, 3] + call assert_equal([1, 2, 3, 4, 5, 6], extend(l, [4, 5, 6])) + call assert_equal([1, 2, 3, 4, 5, 6], l) + + let l = [1, 2, 3] + call extend(l, [4, 5, 6], 0) + call assert_equal([4, 5, 6, 1, 2, 3], l) + + let l = [1, 2, 3] + call extend(l, [4, 5, 6], 1) + call assert_equal([1, 4, 5, 6, 2, 3], l) + + let l = [1, 2, 3] + call extend(l, [4, 5, 6], 3) + call assert_equal([1, 2, 3, 4, 5, 6], l) + + let l = [1, 2, 3] + call extend(l, [4, 5, 6], -1) + call assert_equal([1, 2, 4, 5, 6, 3], l) + + let l = [1, 2, 3] + call extend(l, [4, 5, 6], -3) + call assert_equal([4, 5, 6, 1, 2, 3], l) + + let l = [1, 2, 3] + call assert_fails("call extend(l, [4, 5, 6], 4)", 'E684:') + call assert_fails("call extend(l, [4, 5, 6], -4)", 'E684:') + call assert_fails("call extend(l, [4, 5, 6], 1.2)", 'E805:') + + " Test extend() with dictionaries. " Pass the same Dict to extend() let d = { 'a': {'b': 'B'}} call extend(d, d) call assert_equal({'a': {'b': 'B'}}, d) - " Pass the same Dict to extend() with "error" - call assert_fails("call extend(d, d, 'error')", 'E737:') - call assert_equal({'a': {'b': 'B'}}, d) + let d = {'a': 'A', 'b': 'B'} + call assert_equal({'a': 'A', 'b': 0, 'c': 'C'}, extend(d, {'b': 0, 'c':'C'})) + call assert_equal({'a': 'A', 'b': 0, 'c': 'C'}, d) + + let d = {'a': 'A', 'b': 'B'} + call extend(d, {'a': 'A', 'b': 0, 'c': 'C'}, "force") + call assert_equal({'a': 'A', 'b': 0, 'c': 'C'}, d) + + let d = {'a': 'A', 'b': 'B'} + call extend(d, {'b': 0, 'c':'C'}, "keep") + call assert_equal({'a': 'A', 'b': 'B', 'c': 'C'}, d) + + let d = {'a': 'A', 'b': 'B'} + call assert_fails("call extend(d, {'b': 0, 'c':'C'}, 'error')", 'E737:') + call assert_fails("call extend(d, {'b': 0, 'c':'C'}, 'xxx')", 'E475:') + call assert_fails("call extend(d, {'b': 0, 'c':'C'}, 1.2)", 'E806:') + call assert_equal({'a': 'A', 'b': 'B'}, d) + + call assert_fails("call extend([1, 2], 1)", 'E712:') + call assert_fails("call extend([1, 2], {})", 'E712:') endfunc diff --git a/src/nvim/testdir/test_marks.vim b/src/nvim/testdir/test_marks.vim index 8858cd22b8..7fd115fd68 100644 --- a/src/nvim/testdir/test_marks.vim +++ b/src/nvim/testdir/test_marks.vim @@ -136,3 +136,44 @@ func Test_marks_cmd_multibyte() bwipe! endfunc + +func Test_delmarks() + new + norm mx + norm `x + delmarks x + call assert_fails('norm `x', 'E20:') + + " Deleting an already deleted mark should not fail. + delmarks x + + " Test deleting a range of marks. + norm ma + norm mb + norm mc + norm mz + delmarks b-z + norm `a + call assert_fails('norm `b', 'E20:') + call assert_fails('norm `c', 'E20:') + call assert_fails('norm `z', 'E20:') + call assert_fails('delmarks z-b', 'E475:') + + call assert_fails('delmarks', 'E471:') + call assert_fails('delmarks /', 'E475:') + + " Test delmarks! + norm mx + norm `x + delmarks! + call assert_fails('norm `x', 'E20:') + call assert_fails('delmarks! x', 'E474:') + + bwipe! +endfunc + +func Test_mark_error() + call assert_fails('mark', 'E471:') + call assert_fails('mark xx', 'E488:') + call assert_fails('mark _', 'E191:') +endfunc diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim index 9e2f80fcba..08ccc8d4fe 100644 --- a/src/nvim/testdir/test_search.vim +++ b/src/nvim/testdir/test_search.vim @@ -489,6 +489,30 @@ func Test_incsearch_substitute_dump() call delete('Xis_subst_script') endfunc +func Test_incsearch_with_change() + if !has('timers') || !exists('+incsearch') || !CanRunVimInTerminal() + return + endif + + call writefile([ + \ 'set incsearch hlsearch scrolloff=0', + \ 'call setline(1, ["one", "two ------ X", "three"])', + \ 'call timer_start(200, { _ -> setline(2, "x")})', + \ ], 'Xis_change_script') + let buf = RunVimInTerminal('-S Xis_change_script', {'rows': 9, 'cols': 70}) + " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by + " the 'ambiwidth' check. + sleep 300m + + " Highlight X, it will be deleted by the timer callback. + call term_sendkeys(buf, ':%s/X') + call VerifyScreenDump(buf, 'Test_incsearch_change_01', {}) + call term_sendkeys(buf, "\<Esc>") + + call StopVimInTerminal(buf) + call delete('Xis_change_script') +endfunc + func Test_search_undefined_behaviour() if !has("terminal") return @@ -524,3 +548,40 @@ func Test_search_sentence() /\%'( / endfunc + +func Test_large_hex_chars1() + " This used to cause a crash, the character becomes an NFA state. + try + /\%Ufffffc23 + catch + call assert_match('E678:', v:exception) + endtry + try + set re=1 + /\%Ufffffc23 + catch + call assert_match('E678:', v:exception) + endtry + set re& +endfunc + +func Test_large_hex_chars2() + " This used to cause a crash, the character becomes an NFA state. + try + /[\Ufffffc1f] + catch + call assert_match('E486:', v:exception) + endtry + try + set re=1 + /[\Ufffffc1f] + catch + call assert_match('E486:', v:exception) + endtry + set re& +endfunc + +func Test_one_error_msg() + " This was also giving an internal error + call assert_fails('call search(" \\((\\v[[=P=]]){185}+ ")', 'E871:') +endfunc diff --git a/src/nvim/testdir/test_sort.vim b/src/nvim/testdir/test_sort.vim index 14d008a17f..7da82b0185 100644 --- a/src/nvim/testdir/test_sort.vim +++ b/src/nvim/testdir/test_sort.vim @@ -1222,6 +1222,77 @@ func Test_sort_cmd() enew! endfunc +func Test_sort_large_num() + new + a +-2147483648 +-2147483647 + +-1 +0 +1 +-2147483646 +2147483646 +2147483647 +2147483647 +-2147483648 +abc + +. + " Numerical sort. Non-numeric lines are ordered before numerical lines. + " Ordering of non-numerical is stable. + sort n + call assert_equal(['', + \ 'abc', + \ '', + \ '-2147483648', + \ '-2147483648', + \ '-2147483647', + \ '-2147483646', + \ '-1', + \ '0', + \ '1', + \ '2147483646', + \ '2147483647', + \ '2147483647'], getline(1, '$')) + bwipe! + + if has('num64') + new + a +-9223372036854775808 +-9223372036854775807 + +-1 +0 +1 +-9223372036854775806 +9223372036854775806 +9223372036854775807 +9223372036854775807 +-9223372036854775808 +abc + +. + sort n + call assert_equal(['', + \ 'abc', + \ '', + \ '-9223372036854775808', + \ '-9223372036854775808', + \ '-9223372036854775807', + \ '-9223372036854775806', + \ '-1', + \ '0', + \ '1', + \ '9223372036854775806', + \ '9223372036854775807', + \ '9223372036854775807'], getline(1, '$')) + bwipe! + endif +endfunc + + func Test_sort_cmd_report() enew! call append(0, repeat([1], 3) + repeat([2], 3) + repeat([3], 3)) diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim index dbd26be089..d02454fbf0 100644 --- a/src/nvim/testdir/test_substitute.vim +++ b/src/nvim/testdir/test_substitute.vim @@ -322,6 +322,90 @@ func Test_sub_cmd_8() set titlestring& endfunc +" Test %s/\n// which is implemented as a special case to use a +" more efficient join rather than doing a regular substitution. +func Test_substitute_join() + new + + call setline(1, ["foo\tbar", "bar\<C-H>foo"]) + let a = execute('%s/\n//') + call assert_equal("", a) + call assert_equal(["foo\tbarbar\<C-H>foo"], getline(1, '$')) + call assert_equal('\n', histget("search", -1)) + + call setline(1, ["foo\tbar", "bar\<C-H>foo"]) + let a = execute('%s/\n//g') + call assert_equal("", a) + call assert_equal(["foo\tbarbar\<C-H>foo"], getline(1, '$')) + call assert_equal('\n', histget("search", -1)) + + call setline(1, ["foo\tbar", "bar\<C-H>foo"]) + let a = execute('%s/\n//p') + call assert_equal("\nfoo barbar^Hfoo", a) + call assert_equal(["foo\tbarbar\<C-H>foo"], getline(1, '$')) + call assert_equal('\n', histget("search", -1)) + + call setline(1, ["foo\tbar", "bar\<C-H>foo"]) + let a = execute('%s/\n//l') + call assert_equal("\nfoo^Ibarbar^Hfoo$", a) + call assert_equal(["foo\tbarbar\<C-H>foo"], getline(1, '$')) + call assert_equal('\n', histget("search", -1)) + + call setline(1, ["foo\tbar", "bar\<C-H>foo"]) + let a = execute('%s/\n//#') + call assert_equal("\n 1 foo barbar^Hfoo", a) + call assert_equal(["foo\tbarbar\<C-H>foo"], getline(1, '$')) + call assert_equal('\n', histget("search", -1)) + + bwipe! +endfunc + +func Test_substitute_count() + new + call setline(1, ['foo foo', 'foo foo', 'foo foo', 'foo foo', 'foo foo']) + 2 + + s/foo/bar/3 + call assert_equal(['foo foo', 'bar foo', 'bar foo', 'bar foo', 'foo foo'], + \ getline(1, '$')) + + call assert_fails('s/foo/bar/0', 'E939:') + + bwipe! +endfunc + +" Test substitute 'n' flag (report number of matches, do not substitute). +func Test_substitute_flag_n() + new + let lines = ['foo foo', 'foo foo', 'foo foo', 'foo foo', 'foo foo'] + call setline(1, lines) + + call assert_equal("\n3 matches on 3 lines", execute('2,4s/foo/bar/n')) + call assert_equal("\n6 matches on 3 lines", execute('2,4s/foo/bar/gn')) + + " c flag (confirm) should be ignored when using n flag. + call assert_equal("\n3 matches on 3 lines", execute('2,4s/foo/bar/nc')) + + " No substitution should have been done. + call assert_equal(lines, getline(1, '$')) + + bwipe! +endfunc + +func Test_substitute_errors() + new + call setline(1, 'foobar') + + call assert_fails('s/FOO/bar/', 'E486:') + call assert_fails('s/foo/bar/@', 'E488:') + call assert_fails('s/\(/bar/', 'E476:') + + setl nomodifiable + call assert_fails('s/foo/bar/', 'E21:') + + bwipe! +endfunc + " Test for *sub-replace-special* and *sub-replace-expression* on substitute(). func Test_sub_replace_1() " Run the tests with 'magic' on diff --git a/src/nvim/testdir/test_textobjects.vim b/src/nvim/testdir/test_textobjects.vim index 6a2f5044cc..9194e0014d 100644 --- a/src/nvim/testdir/test_textobjects.vim +++ b/src/nvim/testdir/test_textobjects.vim @@ -52,6 +52,31 @@ func Test_quote_selection_selection_exclusive() bw! endfunc +func Test_quote_selection_selection_exclusive_abort() + new + set selection=exclusive + call setline(1, "'abzzc'") + let exp_curs = [0, 1, 6, 0] + call cursor(1,1) + exe 'norm! fcdvi"' + " make sure to end visual mode to have a clear state + exe "norm! \<esc>" + call assert_equal(exp_curs, getpos('.')) + call cursor(1,1) + exe 'norm! fcvi"' + exe "norm! \<esc>" + call assert_equal(exp_curs, getpos('.')) + call cursor(1,2) + exe 'norm! vfcoi"' + exe "norm! \<esc>" + let exp_curs = [0, 1, 2, 0] + let exp_visu = [0, 1, 7, 0] + call assert_equal(exp_curs, getpos('.')) + call assert_equal(exp_visu, getpos("'>")) + set selection&vim + bw! +endfunc + " Tests for string and html text objects func Test_string_html_objects() enew! diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index a0df80a8e8..3eb88366d6 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -7,8 +7,10 @@ #include "nvim/api/vim.h" #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" +#include "nvim/charset.h" #include "nvim/main.h" #include "nvim/aucmd.h" +#include "nvim/option.h" #include "nvim/os/os.h" #include "nvim/os/input.h" #include "nvim/event/rstream.h" @@ -352,6 +354,85 @@ static bool handle_forced_escape(TermInput *input) return false; } +static void set_bg_deferred(void **argv) +{ + char *bgvalue = argv[0]; + if (starting) { + // Wait until after startup, so OptionSet is triggered. + loop_schedule(&main_loop, event_create(set_bg_deferred, 1, bgvalue)); + return; + } + if (!option_was_set("bg") && !strequal((char *)p_bg, bgvalue)) { + // Value differs, apply it. + set_option_value("bg", 0L, bgvalue, 0); + reset_option_was_set("bg"); + } +} + +// During startup, tui.c requests the background color (see `ext.get_bg`). +// +// Here in input.c, we watch for the terminal response `\e]11;COLOR\a`. If +// COLOR matches `rgb:RRRR/GGGG/BBBB` where R, G, and B are hex digits, then +// compute the luminance[1] of the RGB color and classify it as light/dark +// accordingly. Note that the color components may have anywhere from one to +// four hex digits, and require scaling accordingly as values out of 4, 8, 12, +// or 16 bits. +// +// [1] https://en.wikipedia.org/wiki/Luma_%28video%29 +static bool handle_background_color(TermInput *input) +{ + size_t count = 0; + size_t component = 0; + uint16_t rgb[] = { 0, 0, 0 }; + uint16_t rgb_max[] = { 0, 0, 0 }; + bool eat_backslash = false; + bool done = false; + bool bad = false; + if (rbuffer_size(input->read_stream.buffer) >= 9 + && !rbuffer_cmp(input->read_stream.buffer, "\x1b]11;rgb:", 9)) { + rbuffer_consumed(input->read_stream.buffer, 9); + RBUFFER_EACH(input->read_stream.buffer, c, i) { + count = i + 1; + if (eat_backslash) { + done = true; + break; + } else if (c == '\x07') { + done = true; + break; + } else if (c == '\x1b') { + eat_backslash = true; + } else if (bad) { + // ignore + } else if (c == '/') { + if (component < 3) { + component++; + } + } else if (ascii_isxdigit(c)) { + if (component < 3 && rgb_max[component] != 0xffff) { + rgb_max[component] = (uint16_t)((rgb_max[component] << 4) | 0xf); + rgb[component] = (uint16_t)((rgb[component] << 4) | hex2nr(c)); + } + } else { + bad = true; + } + } + rbuffer_consumed(input->read_stream.buffer, count); + if (done && !bad && rgb_max[0] && rgb_max[1] && rgb_max[2]) { + double r = (double)rgb[0] / (double)rgb_max[0]; + double g = (double)rgb[1] / (double)rgb_max[1]; + double b = (double)rgb[2] / (double)rgb_max[2]; + double luminance = (0.299 * r) + (0.587 * g) + (0.114 * b); // CCIR 601 + char *bgvalue = luminance < 0.5 ? "dark" : "light"; + DLOG("bg response: %s", bgvalue); + loop_schedule(&main_loop, event_create(set_bg_deferred, 1, bgvalue)); + } else { + DLOG("failed to parse bg response"); + } + return true; + } + return false; +} + static void read_cb(Stream *stream, RBuffer *buf, size_t count_, void *data, bool eof) { @@ -381,7 +462,8 @@ static void read_cb(Stream *stream, RBuffer *buf, size_t count_, void *data, do { if (handle_focus_event(input) || handle_bracketed_paste(input) - || handle_forced_escape(input)) { + || handle_forced_escape(input) + || handle_background_color(input)) { continue; } diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index bbf2306f90..e20cf15a79 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -123,11 +123,13 @@ typedef struct { int set_cursor_style, reset_cursor_style; int save_title, restore_title; int enter_undercurl_mode, exit_undercurl_mode, set_underline_color; + int get_bg; } unibi_ext; char *space_buf; } TUIData; static bool volatile got_winch = false; +static bool did_user_set_dimensions = false; static bool cursor_style_enabled = false; #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -163,6 +165,7 @@ UI *tui_start(void) memset(ui->ui_ext, 0, sizeof(ui->ui_ext)); ui->ui_ext[kUILinegrid] = true; + ui->ui_ext[kUITermColors] = true; return ui_bridge_attach(ui, tui_main, tui_scheduler); } @@ -211,6 +214,7 @@ static void terminfo_start(UI *ui) data->unibi_ext.reset_scroll_region = -1; data->unibi_ext.set_cursor_style = -1; data->unibi_ext.reset_cursor_style = -1; + data->unibi_ext.get_bg = -1; data->out_fd = 1; data->out_isatty = os_isatty(data->out_fd); @@ -281,6 +285,8 @@ static void terminfo_start(UI *ui) unibi_out_ext(ui, data->unibi_ext.save_title); unibi_out(ui, unibi_keypad_xmit); unibi_out(ui, unibi_clear_screen); + // Ask the terminal to send us the background color. + unibi_out_ext(ui, data->unibi_ext.get_bg); // Enable bracketed paste unibi_out_ext(ui, data->unibi_ext.enable_bracketed_paste); @@ -334,7 +340,7 @@ static void tui_terminal_start(UI *ui) data->print_attr_id = -1; ugrid_init(&data->grid); terminfo_start(ui); - update_size(ui); + tui_guess_size(ui); signal_watcher_start(&data->winch_handle, sigwinch_cb, SIGWINCH); term_input_start(&data->input); } @@ -461,7 +467,7 @@ static void sigwinch_cb(SignalWatcher *watcher, int signum, void *data) return; } - update_size(ui); + tui_guess_size(ui); ui_schedule_refresh(); } @@ -885,7 +891,8 @@ static void tui_grid_resize(UI *ui, Integer g, Integer width, Integer height) r->right = MIN(r->right, grid->width); } - if (!got_winch) { // Try to resize the terminal window. + if (!got_winch && (!starting || did_user_set_dimensions)) { + // Resize the _host_ terminal. UNIBI_SET_NUM_VAR(data->params[0], (int)height); UNIBI_SET_NUM_VAR(data->params[1], (int)width); unibi_out_ext(ui, data->unibi_ext.resize_screen); @@ -1343,13 +1350,16 @@ static void invalidate(UI *ui, int top, int bot, int left, int right) } } -static void update_size(UI *ui) +/// Tries to get the user's wanted dimensions (columns and rows) for the entire +/// application (i.e., the host terminal). +static void tui_guess_size(UI *ui) { TUIData *data = ui->data; int width = 0, height = 0; // 1 - look for non-default 'columns' and 'lines' options during startup - if (starting != 0 && (Columns != DFLT_COLS || Rows != DFLT_ROWS)) { + if (starting && (Columns != DFLT_COLS || Rows != DFLT_ROWS)) { + did_user_set_dimensions = true; assert(Columns >= INT_MIN && Columns <= INT_MAX); assert(Rows >= INT_MIN && Rows <= INT_MAX); width = (int)Columns; @@ -1648,6 +1658,9 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, #define XTERM_SETAB_16 \ "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e39%;m" + data->unibi_ext.get_bg = (int)unibi_add_ext_str(ut, "ext.get_bg", + "\x1b]11;?\x07"); + // Terminals with 256-colour SGR support despite what terminfo says. if (unibi_get_num(ut, unibi_max_colors) < 256) { // See http://fedoraproject.org/wiki/Features/256_Color_Terminals diff --git a/src/nvim/ui.c b/src/nvim/ui.c index 16370f2d10..d07ea3179e 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -219,11 +219,6 @@ void ui_schedule_refresh(void) 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) { 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) { abort(); } - if (!ui->ui_ext[kUIMultigrid]) { + if (!ui->ui_ext[kUIMultigrid] && !ui->ui_ext[kUIFloatDebug]) { ui_comp_attach(ui); } @@ -299,7 +294,7 @@ void ui_detach_impl(UI *ui) ui_schedule_refresh(); } - if (!ui->ui_ext[kUIMultigrid]) { + if (!ui->ui_ext[kUIMultigrid] && !ui->ui_ext[kUIFloatDebug]) { ui_comp_detach(ui); } } @@ -310,7 +305,7 @@ void ui_set_ext_option(UI *ui, UIExtension ext, bool active) ui_refresh(); 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]), BOOLEAN_OBJ(active)); } @@ -383,7 +378,7 @@ int ui_current_col(void) void ui_flush(void) { cmdline_ui_flush(); - win_ui_flush(); + win_ui_flush_positions(); msg_ext_ui_flush(); if (pending_cursor_update) { @@ -405,7 +400,6 @@ void ui_flush(void) ui_call_flush(); } - /// Check if current mode has changed. /// May update the shape of the cursor. void ui_cursor_shape(void) @@ -438,7 +432,9 @@ Array ui_array(void) PUT(info, "height", INTEGER_OBJ(ui->height)); PUT(info, "rgb", BOOLEAN_OBJ(ui->rgb)); 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) { ui->inspect(ui, &info); @@ -462,8 +458,14 @@ void ui_grid_resize(handle_T grid_handle, int width, int height, Error *error) return; } - // 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); + if (wp->w_floating) { + if (width != wp->w_width && height != wp->w_height) { + win_config_float(wp, (int)width, (int)height, wp->w_float_config); + } + } 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); + } } diff --git a/src/nvim/ui.h b/src/nvim/ui.h index 490cc930b1..3f6b3babad 100644 --- a/src/nvim/ui.h +++ b/src/nvim/ui.h @@ -20,6 +20,7 @@ typedef enum { kUIMultigrid, kUIHlState, kUITermColors, + kUIFloatDebug, kUIExtCount, } UIExtension; @@ -33,9 +34,9 @@ EXTERN const char *ui_ext_names[] INIT(= { "ext_multigrid", "ext_hlstate", "ext_termcolors", + "_debug_float", }); - typedef struct ui_t UI; enum { diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c index c7ba0306e4..9ad3f851ad 100644 --- a/src/nvim/ui_compositor.c +++ b/src/nvim/ui_compositor.c @@ -17,6 +17,7 @@ #include "nvim/ui.h" #include "nvim/highlight.h" #include "nvim/memory.h" +#include "nvim/popupmnu.h" #include "nvim/ui_compositor.h" #include "nvim/ugrid.h" #include "nvim/screen.h" @@ -55,7 +56,6 @@ void ui_comp_init(void) compositor->rgb = true; compositor->grid_resize = ui_comp_grid_resize; - compositor->grid_clear = ui_comp_grid_clear; compositor->grid_scroll = ui_comp_grid_scroll; compositor->grid_cursor_goto = ui_comp_grid_cursor_goto; 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, /// 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() -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) { - bool moved = (row != grid->comp_row) || (col != grid->comp_col); + moved = (row != grid->comp_row) || (col != grid->comp_col); if (ui_comp_should_draw()) { // 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 @@ -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_col = col; - return moved; - } + } else { + moved = true; #ifndef NDEBUG - for (size_t i = 0; i < kv_size(layers); i++) { - if (kv_A(layers, i) == grid) { - assert(false); + for (size_t i = 0; i < kv_size(layers); i++) { + if (kv_A(layers, i) == grid) { + assert(false); + } } - } #endif - // not found: new grid - kv_push(layers, grid); - grid->comp_row = row; - grid->comp_col = col; - grid->comp_index = kv_size(layers)-1; - return true; + + size_t insert_at = kv_size(layers); + if (kv_A(layers, insert_at-1) == &pum_grid) { + insert_at--; + } + 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-1); + } + 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) @@ -194,6 +216,26 @@ bool ui_comp_set_grid(handle_T handle) 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, 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_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) { // TODO(bfredl): this happens with 'writedelay', refactor? // 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); } +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 /// 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)); // 'pumblend' - if (grid != &default_grid && p_pb) { + if (grid == &pum_grid && p_pb) { for (int i = col-(int)startcol; i < until-startcol; i++) { bool thru = strequal((char *)linebuf[i], " "); // negative space 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(clearcol <= default_grid.Columns); 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); } else { 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 /// /// Don't redraw floats until screen is cleared -void ui_comp_invalidate_screen(void) -{ - valid_screen = false; -} - -static void ui_comp_grid_clear(UI *ui, Integer grid) +void ui_comp_set_screen_valid(bool valid) { - // By design, only first grid uses clearing. - assert(grid == 1); - ui_composed_call_grid_clear(1); - valid_screen = true; + valid_screen = valid; } // TODO(bfredl): These events are somewhat of a hack. multiline messages diff --git a/src/nvim/window.c b/src/nvim/window.c index bb71b12aed..bce4289ab4 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -6,6 +6,7 @@ #include <stdbool.h> #include "nvim/api/private/handle.h" +#include "nvim/api/private/helpers.h" #include "nvim/vim.h" #include "nvim/ascii.h" #include "nvim/window.h" @@ -48,6 +49,7 @@ #include "nvim/terminal.h" #include "nvim/undo.h" #include "nvim/ui.h" +#include "nvim/ui_compositor.h" #include "nvim/os/os.h" @@ -203,14 +205,24 @@ newwindow: wp = wp->w_next; } } else { - if (nchar == 'W') { /* go to previous window */ + if (nchar == 'W') { // go to previous window wp = curwin->w_prev; - if (wp == NULL) - wp = lastwin; /* wrap around */ - } else { /* go to next window */ + if (wp == NULL) { + wp = lastwin; // wrap around + } + while (wp != NULL && wp->w_floating + && !wp->w_float_config.focusable) { + wp = wp->w_prev; + } + } else { // go to next window wp = curwin->w_next; - if (wp == NULL) - wp = firstwin; /* wrap around */ + while (wp != NULL && wp->w_floating + && !wp->w_float_config.focusable) { + wp = wp->w_next; + } + if (wp == NULL) { + wp = firstwin; // wrap around + } } } win_goto(wp); @@ -281,7 +293,7 @@ newwindow: /* cursor to bottom-right window */ case 'b': case Ctrl_B: - win_goto(lastwin); + win_goto(lastwin_nofloating()); break; /* cursor to last accessed (previous) window */ @@ -483,6 +495,22 @@ wingotofile: cmdmod.tab = tabpage_index(curtab) + 1; nchar = xchar; 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: beep_flush(); 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 (!reconf && !has_relative && !has_external) { + api_set_error(err, kErrorTypeValidation, + "One of 'relative' and 'external' must be used"); + return false; + } else if (has_relative) { + out->external = 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 * @@ -566,16 +890,20 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) int wmh1; bool did_set_fraction = false; - if (flags & WSP_TOP) + if (flags & WSP_TOP) { oldwin = firstwin; - else if (flags & WSP_BOT) - oldwin = lastwin; - else + } else if (flags & WSP_BOT || curwin->w_floating) { + // can't split float, use last nonfloating window instead + oldwin = lastwin_nofloating(); + } else { oldwin = curwin; + } - /* add a status line when p_ls == 1 and splitting the first window */ - if (ONE_WINDOW && p_ls == 1 && oldwin->w_status_height == 0) { - if (oldwin->w_height <= p_wmh && new_wp == NULL) { + bool new_in_layout = (new_wp == NULL || new_wp->w_floating); + + // 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)); 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; needed += minwidth; } - if (available < needed && new_wp == NULL) { + if (available < needed && new_in_layout) { EMSG(_(e_noroom)); 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; needed += minheight; } - if (available < needed && new_wp == NULL) { + if (available < needed && new_in_layout) { EMSG(_(e_noroom)); 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 */ 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; 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(); return; } @@ -1282,7 +1619,13 @@ static void win_rotate(int upwards, int count) frame_T *frp; 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(); return; } @@ -1355,16 +1698,27 @@ static void win_rotate(int upwards, int count) */ static void win_totop(int size, int flags) { - int dir; + int dir = 0; int height = curwin->w_height; - if (ONE_WINDOW) { + if (firstwin == curwin && lastwin_nofloating() == curwin) { beep_flush(); return; } - /* Remove the window and frame from the tree of frames. */ - (void)winframe_remove(curwin, &dir, NULL); + if (curwin->w_floating) { + 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); last_status(FALSE); /* may need to remove last status line */ (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 -/// "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 seen_one = false; FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp != aucmd_win) { + if (wp != aucmd_win && (!wp->w_floating || wp == curwin)) { if (seen_one) { return false; } @@ -1811,6 +2165,20 @@ bool one_window(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT 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. /// /// @param win window to close @@ -1882,9 +2250,9 @@ int win_close(win_T *win, bool free_buf) int dir; bool help_window = false; 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")); return FAIL; } @@ -1897,10 +2265,17 @@ int win_close(win_T *win, bool free_buf) EMSG(_("E813: Cannot close autocmd window")); 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")); 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 * 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. * 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 @@ -1949,6 +2332,27 @@ int win_close(win_T *win, bool free_buf) 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. */ 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 && (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 // curwin->w_buffer, otherwise writing ShaDa file may fail. 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 // 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)) { return FAIL; } @@ -2041,12 +2446,15 @@ int win_close(win_T *win, bool free_buf) // using the window. check_cursor(); } - if (p_ea && (*p_ead == 'b' || *p_ead == dir)) { - // If the frame of the closed window contains the new current window, - // only resize that frame. Otherwise resize all windows. - win_equal(curwin, curwin->w_frame->fr_parent == win_frame, dir); - } else { - win_comp_pos(); + + if (!was_floating) { + if (p_ea && (*p_ead == 'b' || *p_ead == dir)) { + // If the frame of the closed window contains the new current window, + // only resize that frame. Otherwise resize all windows. + win_equal(curwin, curwin->w_frame->fr_parent == win_frame, dir); + } else { + win_comp_pos(); + } } if (close_curwin) { @@ -2167,10 +2575,18 @@ win_free_mem ( frame_T *frp; win_T *wp; - /* Remove the window and its frame from the tree of frames. */ - frp = win->w_frame; - wp = winframe_remove(win, dirp, tp); - xfree(frp); + if (!win->w_floating) { + // Remove the window and its frame from the tree of frames. + frp = win->w_frame; + wp = winframe_remove(win, dirp, tp); + xfree(frp); + } else { + if (win_valid(prevwin)) { + wp = prevwin; + } else { + wp = curtab->tp_firstwin; + } + } win_free(win, tp); /* 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) 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) { (void)win_free_mem(aucmd_win, &dummy, NULL); aucmd_win = NULL; @@ -2870,11 +3292,19 @@ close_others ( win_T *nextwp; 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 && !autocmd_busy - ) + ) { MSG(_(m_onlyone)); + } return; } @@ -3116,9 +3546,7 @@ int win_new_tabpage(int after, char_u *filename) 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_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; topframe = tp->tp_topframe; - if (old_curtab != curtab && ui_has(kUIMultigrid)) { + if (old_curtab != 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); } -/// 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) { win_T *next_wp; for (win_T *wp = old_curtab->tp_firstwin; wp; wp = next_wp) { 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; } 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; } } @@ -3577,6 +4020,12 @@ win_goto_ver ( frame_T *foundfr; foundfr = curwin->w_frame; + + if (curwin->w_floating) { + win_goto(prevwin); + return; + } + while (count--) { /* * First go upwards in the tree of frames until we find an upwards or @@ -3636,6 +4085,12 @@ win_goto_hor ( frame_T *foundfr; foundfr = curwin->w_frame; + + if (curwin->w_floating) { + win_goto(prevwin); + return; + } + while (count--) { /* * 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; curbuf = wp->w_buffer; + check_cursor(); if (!virtual_active()) 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) win_setheight(1); - /* set window width to desired minimal value */ - if (curwin->w_width < p_wiw && !curwin->w_p_wfw) + // set window width to desired minimal value + if (curwin->w_width < p_wiw && !curwin->w_p_wfw && !wp->w_floating) { win_setwidth((int)p_wiw); + } 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_cursor.lnum = 1; new_wp->w_scbind_pos = 1; + new_wp->w_floating = 0; /* We won't calculate w_fraction until resizing the window */ new_wp->w_fraction = 0; @@ -4204,6 +4662,13 @@ int win_comp_pos(void) int col = 0; 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; } @@ -4263,8 +4728,6 @@ void win_setheight(int height) */ void win_setheight_win(int height, win_T *win) { - int row; - if (win == curwin) { /* Always keep current window at least one line high, even when * 'winminheight' is zero. */ @@ -4274,21 +4737,28 @@ void win_setheight_win(int height, win_T *win) 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 */ - row = win_comp_pos(); + // recompute the window positions + int row = win_comp_pos(); - /* - * If there is extra space created between the last window and the command - * line, clear it. - */ - if (full_screen && msg_scrolled == 0 && row < cmdline_row) { - grid_fill(&default_grid, row, cmdline_row, 0, (int)Columns, ' ', ' ', 0); + // If there is extra space created between the last window and the command + // line, clear it. + 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); } @@ -4358,14 +4828,16 @@ static void frame_setheight(frame_T *curfrp, int height) if (frp != curfrp) room -= frame_minheight(frp, NULL); } - if (curfrp->fr_width != Columns) + if (curfrp->fr_width != Columns) { room_cmdline = 0; - else { - room_cmdline = Rows - p_ch - (lastwin->w_winrow - + lastwin->w_height + - lastwin->w_status_height); - if (room_cmdline < 0) + } else { + win_T *wp = lastwin_nofloating(); + room_cmdline = Rows - p_ch - (wp->w_winrow + + wp->w_height + + wp->w_status_height); + if (room_cmdline < 0) { room_cmdline = 0; + } } if (height <= room + room_cmdline) { @@ -4470,11 +4942,19 @@ void win_setwidth_win(int width, win_T *wp) if (width == 0) 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); } @@ -5015,6 +5495,7 @@ void win_set_inner_size(win_T *wp) if (!exiting) { scroll_to_fraction(wp, prev_height); } + redraw_win_later(wp, NOT_VALID); // SOME_VALID?? } if (width != wp->w_width_inner) { @@ -5026,6 +5507,7 @@ void win_set_inner_size(win_T *wp) update_topline(); curs_columns(true); // validate w_wrow } + redraw_win_later(wp, NOT_VALID); } if (wp->w_buffer->terminal) { @@ -5039,9 +5521,7 @@ void win_new_width(win_T *wp, int width) wp->w_width = width; 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; } @@ -5379,7 +5859,7 @@ int min_rows(void) /// 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 -/// "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 { // 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; FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { 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) { 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) { if (wp->w_pos_changed && wp->w_grid.chars != NULL) { if (tp == curtab) { - ui_call_win_pos(wp->w_grid.handle, wp->handle, wp->w_winrow, - wp->w_wincol, wp->w_width, wp->w_height); + ui_ext_win_position(wp); } else { ui_call_win_hide(wp->w_grid.handle); } wp->w_pos_changed = false; } } +} +win_T *lastwin_nofloating(void) { + win_T *res = lastwin; + while (res->w_floating) { + res = res->w_prev; + } + return res; } diff --git a/test/functional/api/buffer_spec.lua b/test/functional/api/buffer_spec.lua index d9412f0f13..93599c04f1 100644 --- a/test/functional/api/buffer_spec.lua +++ b/test/functional/api/buffer_spec.lua @@ -1,7 +1,9 @@ local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') local clear, nvim, buffer = helpers.clear, helpers.nvim, helpers.buffer local curbuf, curwin, eq = helpers.curbuf, helpers.curwin, helpers.eq local curbufmeths, ok = helpers.curbufmeths, helpers.ok +local meths = helpers.meths local funcs = helpers.funcs local request = helpers.request local exc_exec = helpers.exc_exec @@ -11,6 +13,7 @@ local NIL = helpers.NIL local meth_pcall = helpers.meth_pcall local command = helpers.command local bufmeths = helpers.bufmeths +local feed = helpers.feed describe('api/buf', function() before_each(clear) @@ -299,6 +302,38 @@ describe('api/buf', function() local retval = exc_exec("call nvim_buf_set_lines(1, 0, 1, v:false, ['test'])") eq(0, retval) end) + + it("set_lines of invisible buffer doesn't move cursor in current window", function() + local screen = Screen.new(20, 5) + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue1}, + [2] = {bold = true}, + }) + screen:attach() + + insert([[ + Who would win? + A real window + with proper text]]) + local buf = meths.create_buf(false,true) + screen:expect([[ + Who would win? | + A real window | + with proper tex^t | + {1:~ }| + | + ]]) + + meths.buf_set_lines(buf, 0, -1, true, {'or some', 'scratchy text'}) + feed('i') -- provoke redraw + screen:expect([[ + Who would win? | + A real window | + with proper tex^t | + {1:~ }| + {2:-- INSERT --} | + ]]) + end) end) describe('get_offset', function() diff --git a/test/functional/api/buffer_updates_spec.lua b/test/functional/api/buffer_updates_spec.lua index b54d9e1f6e..b894d2facd 100644 --- a/test/functional/api/buffer_updates_spec.lua +++ b/test/functional/api/buffer_updates_spec.lua @@ -678,6 +678,32 @@ describe('API: buffer events:', function() expectn('Hello There', {}) end) + it(':edit! (reload) causes detach #9642', function() + local b, tick = editoriginal(true, {'AAA', 'BBB'}) + command('set undoreload=1') + + command('normal! x') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 0, 1, {'AA'}, false}) + + command('edit!') + expectn('nvim_buf_detach_event', {b}) + end) + + it(':enew! does not detach hidden buffer', function() + local b, tick = editoriginal(true, {'AAA', 'BBB'}) + local channel = nvim('get_api_info')[1] + + command('set undoreload=1 hidden') + command('normal! x') + tick = tick + 1 + expectn('nvim_buf_lines_event', {b, tick, 0, 1, {'AA'}, false}) + + command('enew!') + eval('rpcnotify('..channel..', "Hello There")') + expectn('Hello There', {}) + end) + it('stays attached if the buffer is hidden', function() local b, tick = editoriginal(true, {'AAA'}) local channel = nvim('get_api_info')[1] diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index b10076c6da..75b9fb71c9 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -1359,6 +1359,9 @@ describe('API', function() eq({id=1}, meths.get_current_buf()) local screen = Screen.new(20, 4) + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue1}, + }) screen:attach() -- @@ -1373,7 +1376,7 @@ describe('API', function() end -- - -- Visiting a scratch-buffer DOES change its properties. + -- Visiting a scratch-buffer DOES NOT change its properties. -- meths.set_current_buf(edited_buf) screen:expect([[ @@ -1381,12 +1384,19 @@ describe('API', function() {1:~ }| {1:~ }| | - ]], { - [1] = {bold = true, foreground = Screen.colors.Blue1}, - }) - eq('', meths.buf_get_option(edited_buf, 'buftype')) - eq('', meths.buf_get_option(edited_buf, 'bufhidden')) + ]]) + eq('nofile', meths.buf_get_option(edited_buf, 'buftype')) + eq('hide', meths.buf_get_option(edited_buf, 'bufhidden')) eq(false, meths.buf_get_option(edited_buf, 'swapfile')) + + -- scratch buffer can be wiped without error + command('bwipe') + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + | + ]]) end) end) end) diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index 4496e1f644..4ff299cd18 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -286,4 +286,41 @@ describe('API/win', function() ok(not window('is_valid', win)) end) end) + + describe('close', function() + it('can close current window', function() + local oldwin = meths.get_current_win() + command('split') + local newwin = meths.get_current_win() + meths.win_close(newwin,false) + eq({oldwin}, meths.list_wins()) + end) + + it('can close noncurrent window', function() + local oldwin = meths.get_current_win() + command('split') + local newwin = meths.get_current_win() + meths.win_close(oldwin,false) + eq({newwin}, meths.list_wins()) + end) + + it('handles changed buffer', function() + local oldwin = meths.get_current_win() + insert('text') + command('new') + local newwin = meths.get_current_win() + eq({false,"Vim:E37: No write since last change (add ! to override)"}, + meth_pcall(meths.win_close, oldwin,false)) + eq({newwin,oldwin}, meths.list_wins()) + end) + + it('handles changed buffer with force', function() + local oldwin = meths.get_current_win() + insert('text') + command('new') + local newwin = meths.get_current_win() + meths.win_close(oldwin,true) + eq({newwin}, meths.list_wins()) + end) + end) end) diff --git a/test/functional/eval/let_spec.lua b/test/functional/eval/let_spec.lua index 1bd3405698..0cbf40137e 100644 --- a/test/functional/eval/let_spec.lua +++ b/test/functional/eval/let_spec.lua @@ -2,13 +2,16 @@ local helpers = require('test.functional.helpers')(after_each) local eq = helpers.eq local clear = helpers.clear +local command = helpers.command +local eval = helpers.eval local meths = helpers.meths local redir_exec = helpers.redir_exec local source = helpers.source +local nvim_dir = helpers.nvim_dir before_each(clear) -describe(':let command', function() +describe(':let', function() it('correctly lists variables with curly-braces', function() meths.set_var('v', {0}) eq('\nv [0]', redir_exec('let {"v"}')) @@ -42,4 +45,38 @@ describe(':let command', function() call feedkeys(":\e:echo l1 l3\n:echo 42\n:cq\n", "t") ]=]) end) + + it("multibyte env var #8398 #9267", function() + command("let $NVIM_TEST = 'AìaB'") + eq('AìaB', eval('$NVIM_TEST')) + command("let $NVIM_TEST = 'AaあB'") + eq('AaあB', eval('$NVIM_TEST')) + local mbyte = [[\p* .ม .ม .ม .ม่ .ม่ .ม่ ֹ ֹ ֹ .ֹ .ֹ .ֹ ֹֻ ֹֻ ֹֻ + .ֹֻ .ֹֻ .ֹֻ ֹֻ ֹֻ ֹֻ .ֹֻ .ֹֻ .ֹֻ ֹ ֹ ֹ .ֹ .ֹ .ֹ ֹ ֹ ֹ .ֹ .ֹ .ֹ ֹֻ ֹֻ + .ֹֻ .ֹֻ .ֹֻ a a a ca ca ca à à à]] + command("let $NVIM_TEST = '"..mbyte.."'") + eq(mbyte, eval('$NVIM_TEST')) + end) + + it("multibyte env var to child process #8398 #9267", function() + if (not helpers.iswin()) and require('test.helpers').isCI() then + -- Fails on non-Windows CI. Buffering/timing issue? + pending('fails on unix CI', function() end) + end + local cmd_get_child_env = "let g:env_from_child = system(['"..nvim_dir.."/printenv-test', 'NVIM_TEST'])" + command("let $NVIM_TEST = 'AìaB'") + command(cmd_get_child_env) + eq(eval('$NVIM_TEST'), eval('g:env_from_child')) + + command("let $NVIM_TEST = 'AaあB'") + command(cmd_get_child_env) + eq(eval('$NVIM_TEST'), eval('g:env_from_child')) + + local mbyte = [[\p* .ม .ม .ม .ม่ .ม่ .ม่ ֹ ֹ ֹ .ֹ .ֹ .ֹ ֹֻ ֹֻ ֹֻ + .ֹֻ .ֹֻ .ֹֻ ֹֻ ֹֻ ֹֻ .ֹֻ .ֹֻ .ֹֻ ֹ ֹ ֹ .ֹ .ֹ .ֹ ֹ ֹ ֹ .ֹ .ֹ .ֹ ֹֻ ֹֻ + .ֹֻ .ֹֻ .ֹֻ a a a ca ca ca à à à]] + command("let $NVIM_TEST = '"..mbyte.."'") + command(cmd_get_child_env) + eq(eval('$NVIM_TEST'), eval('g:env_from_child')) + end) end) diff --git a/test/functional/fixtures/CMakeLists.txt b/test/functional/fixtures/CMakeLists.txt index 8537ea390f..a7cd214b6b 100644 --- a/test/functional/fixtures/CMakeLists.txt +++ b/test/functional/fixtures/CMakeLists.txt @@ -3,3 +3,7 @@ target_link_libraries(tty-test ${LIBUV_LIBRARIES}) add_executable(shell-test shell-test.c) add_executable(printargs-test printargs-test.c) +add_executable(printenv-test printenv-test.c) +if(WIN32) + set_target_properties(printenv-test PROPERTIES LINK_FLAGS -municode) +endif() diff --git a/test/functional/fixtures/printenv-test.c b/test/functional/fixtures/printenv-test.c new file mode 100644 index 0000000000..5ac076f653 --- /dev/null +++ b/test/functional/fixtures/printenv-test.c @@ -0,0 +1,59 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +#include <stdio.h> + +#ifdef WIN32 +# include <windows.h> +#else +# include <stdlib.h> +#endif + +#ifdef WIN32 +int wmain(int argc, wchar_t **argv) +#else +int main(int argc, char **argv) +#endif +{ + if (argc != 2) { + return 1; + } + +#ifdef WIN32 + wchar_t *value = _wgetenv(argv[1]); + if (value == NULL) { + return 1; + } + int utf8_len = WideCharToMultiByte(CP_UTF8, + 0, + value, + -1, + NULL, + 0, + NULL, + NULL); + if (utf8_len == 0) { + return (int)GetLastError(); + } + char *utf8_value = (char *)calloc((size_t)utf8_len, sizeof(char)); + utf8_len = WideCharToMultiByte(CP_UTF8, + 0, + value, + -1, + utf8_value, + utf8_len, + NULL, + NULL); + fprintf(stderr, "%s", utf8_value); + free(utf8_value); +#else + char *value = getenv(argv[1]); + if (value == NULL) { + fprintf(stderr, "env var not found: %s", argv[1]); + return 1; + } + // Print to stderr to avoid buffering. + fprintf(stderr, "%s", value); +#endif + return 0; +} diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index f1ad2bdb95..a0adb45630 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -269,7 +269,7 @@ describe('TUI', function() end) end) -describe('tui with non-tty file descriptors', function() +describe('TUI with non-tty file descriptors', function() before_each(helpers.clear) after_each(function() @@ -277,7 +277,7 @@ describe('tui with non-tty file descriptors', function() end) it('can handle pipes as stdout and stderr', function() - local screen = thelpers.screen_setup(0, '"'..helpers.nvim_prog + local screen = thelpers.screen_setup(0, '"'..nvim_prog ..' -u NONE -i NONE --cmd \'set noswapfile noshowcmd noruler\' --cmd \'normal iabc\' > /dev/null 2>&1 && cat testF && rm testF"') feed_data(':w testF\n:q\n') screen:expect([[ @@ -292,12 +292,12 @@ describe('tui with non-tty file descriptors', function() end) end) -describe('tui FocusGained/FocusLost', function() +describe('TUI FocusGained/FocusLost', function() local screen before_each(function() helpers.clear() - screen = thelpers.screen_setup(0, '["'..helpers.nvim_prog + screen = thelpers.screen_setup(0, '["'..nvim_prog ..'", "-u", "NONE", "-i", "NONE", "--cmd", "set noswapfile noshowcmd noruler"]') feed_data(":autocmd FocusGained * echo 'gained'\n") feed_data(":autocmd FocusLost * echo 'lost'\n") @@ -459,7 +459,7 @@ end) -- These tests require `thelpers` because --headless/--embed -- does not initialize the TUI. -describe("tui 't_Co' (terminal colors)", function() +describe("TUI 't_Co' (terminal colors)", function() local screen local is_freebsd = (string.lower(uname()) == 'freebsd') @@ -731,7 +731,7 @@ end) -- These tests require `thelpers` because --headless/--embed -- does not initialize the TUI. -describe("tui 'term' option", function() +describe("TUI 'term' option", function() local screen local is_bsd = not not string.find(string.lower(uname()), 'bsd') local is_macos = not not string.find(string.lower(uname()), 'darwin') @@ -783,7 +783,7 @@ end) -- These tests require `thelpers` because --headless/--embed -- does not initialize the TUI. -describe("tui", function() +describe("TUI", function() local screen local logfile = 'Xtest_tui_verbose_log' after_each(function() @@ -826,3 +826,89 @@ describe("tui", function() end) end) + +describe('TUI background color', function() + local screen + + before_each(function() + clear() + screen = thelpers.screen_setup(0, '["'..nvim_prog + ..'", "-u", "NONE", "-i", "NONE", "--cmd", "set noswapfile"]') + end) + + it("triggers OptionSet event on terminal-response", function() + feed_data('\027:autocmd OptionSet background echo "did OptionSet, yay!"\n') + + -- The child Nvim is running asynchronously; wait for it to register the + -- OptionSet handler. + feed_data('\027:autocmd OptionSet\n') + screen:expect({any='--- Autocommands ---'}) + + feed_data('\012') -- CTRL-L: clear the screen + screen:expect([[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] 0,0-1 All}| + | + {3:-- TERMINAL --} | + ]]) + feed_data('\027]11;rgb:ffff/ffff/ffff\007') + screen:expect{any='did OptionSet, yay!'} + end) + + local function assert_bg(color, bg) + it('handles '..color..' as '..bg, function() + feed_data('\027]11;rgb:'..color..'\007:echo &background\n') + screen:expect(string.format([[ + {1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] 0,0-1 All}| + %-5s | + {3:-- TERMINAL --} | + ]], bg)) + end) + end + + assert_bg('0000/0000/0000', 'dark') + assert_bg('ffff/ffff/ffff', 'light') + assert_bg('000/000/000', 'dark') + assert_bg('fff/fff/fff', 'light') + assert_bg('00/00/00', 'dark') + assert_bg('ff/ff/ff', 'light') + assert_bg('0/0/0', 'dark') + assert_bg('f/f/f', 'light') + + assert_bg('f/0/0', 'dark') + assert_bg('0/f/0', 'light') + assert_bg('0/0/f', 'dark') + + assert_bg('1/1/1', 'dark') + assert_bg('2/2/2', 'dark') + assert_bg('3/3/3', 'dark') + assert_bg('4/4/4', 'dark') + assert_bg('5/5/5', 'dark') + assert_bg('6/6/6', 'dark') + assert_bg('7/7/7', 'dark') + assert_bg('8/8/8', 'light') + assert_bg('9/9/9', 'light') + assert_bg('a/a/a', 'light') + assert_bg('b/b/b', 'light') + assert_bg('c/c/c', 'light') + assert_bg('d/d/d', 'light') + assert_bg('e/e/e', 'light') + + assert_bg('0/e/0', 'light') + assert_bg('0/d/0', 'light') + assert_bg('0/c/0', 'dark') + assert_bg('0/b/0', 'dark') + + assert_bg('f/0/f', 'dark') + assert_bg('f/1/f', 'dark') + assert_bg('f/2/f', 'dark') + assert_bg('f/3/f', 'light') + assert_bg('f/4/f', 'light') +end) diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua new file mode 100644 index 0000000000..2ed3606491 --- /dev/null +++ b/test/functional/ui/float_spec.lua @@ -0,0 +1,3061 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local os = require('os') +local clear, feed = helpers.clear, helpers.feed +local command, feed_command = helpers.command, helpers.feed_command +local eval = helpers.eval +local eq = helpers.eq +local meths = helpers.meths +local curbufmeths = helpers.curbufmeths +local funcs = helpers.funcs +local run = helpers.run +local meth_pcall = helpers.meth_pcall + +describe('floating windows', function() + before_each(function() + clear() + end) + local attrs = { + [0] = {bold=true, foreground=Screen.colors.Blue}, + [1] = {background = Screen.colors.LightMagenta}, + [2] = {background = Screen.colors.LightMagenta, bold = true, foreground = Screen.colors.Blue1}, + [3] = {bold = true}, + [4] = {bold = true, reverse = true}, + [5] = {reverse = true}, + [6] = {background = Screen.colors.LightMagenta, bold = true, reverse = true}, + [7] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, + [8] = {bold = true, foreground = Screen.colors.SeaGreen4}, + [9] = {background = Screen.colors.LightGrey, underline = true}, + [10] = {background = Screen.colors.LightGrey, underline = true, bold = true, foreground = Screen.colors.Magenta}, + [11] = {bold = true, foreground = Screen.colors.Magenta}, + [12] = {background = Screen.colors.Red, bold = true, foreground = Screen.colors.Blue1}, + [13] = {background = Screen.colors.WebGray} + } + + local function with_ext_multigrid(multigrid) + local screen + before_each(function() + screen = Screen.new(40,7) + screen:attach({ext_multigrid=multigrid}) + screen:set_default_attr_ids(attrs) + end) + + it('can be created and reconfigured', function() + local buf = meths.create_buf(false,false) + local win = meths.open_win(buf, false, 20, 2, {relative='editor', row=2, col=5}) + meths.win_set_option(win , 'winhl', 'Normal:PMenu') + local expected_pos = { + [3]={{id=1001}, 'NW', 1, 2, 5, true}, + } + + + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + | + ## grid 2 + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {1: }| + {2:~ }| + ]], float_pos=expected_pos} + else + screen:expect([[ + ^ | + {0:~ }| + {0:~ }{1: }{0: }| + {0:~ }{2:~ }{0: }| + {0:~ }| + {0:~ }| + | + ]]) + end + + + meths.win_config(win,0,0,{relative='editor', row=0, col=10}) + expected_pos[3][4] = 0 + expected_pos[3][5] = 10 + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + | + ## grid 2 + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {1: }| + {2:~ }| + ]], float_pos=expected_pos} + else + screen:expect([[ + ^ {1: } | + {0:~ }{2:~ }{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 + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ]]) + else + screen:expect([[ + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + end + end) + + it('API has proper error messages', function() + local buf = meths.create_buf(false,false) + eq({false, "Invalid options key 'bork'"}, + meth_pcall(meths.open_win,buf, false, 20, 2, {bork=true})) + eq({false, "'win' option is only valid with relative='win'"}, + meth_pcall(meths.open_win,buf, false, 20, 2, {relative='editor',row=0,col=0,win=0})) + eq({false, "Only one of 'relative' and 'external' should be used"}, + meth_pcall(meths.open_win,buf, false, 20, 2, {relative='editor',row=0,col=0,external=true})) + eq({false, "Invalid value of 'relative' option"}, + meth_pcall(meths.open_win,buf, false, 20, 2, {relative='shell',row=0,col=0})) + eq({false, "Invalid value of 'anchor' option"}, + meth_pcall(meths.open_win,buf, false, 20, 2, {relative='editor',row=0,col=0,anchor='bottom'})) + eq({false, "All of 'relative', 'row', and 'col' has to be specified at once"}, + meth_pcall(meths.open_win,buf, false, 20, 2, {relative='editor'})) + end) + + it('can be placed relative window or cursor', function() + screen:try_resize(40,9) + meths.buf_set_lines(0, 0, -1, true, {'just some', 'example text'}) + feed('gge') + local oldwin = meths.get_current_win() + command('below split') + if multigrid then + screen:expect([[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + {5:[No Name] [+] }| + [3:----------------------------------------]| + [3:----------------------------------------]| + [3:----------------------------------------]| + {4:[No Name] [+] }| + | + ## grid 2 + just some | + example text | + {0:~ }| + ## grid 3 + jus^t some | + example text | + {0:~ }| + ]]) + else + screen:expect([[ + just some | + example text | + {0:~ }| + {5:[No Name] [+] }| + jus^t some | + example text | + {0:~ }| + {4:[No Name] [+] }| + | + ]]) + end + + local buf = meths.create_buf(false,false) + -- no 'win' arg, relative default window + local win = meths.open_win(buf, false, 20, 2, {relative='win', row=0, col=10}) + meths.win_set_option(win, 'winhl', 'Normal:PMenu') + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + {5:[No Name] [+] }| + [3:----------------------------------------]| + [3:----------------------------------------]| + [3:----------------------------------------]| + {4:[No Name] [+] }| + | + ## grid 2 + just some | + example text | + {0:~ }| + ## grid 3 + jus^t some | + example text | + {0:~ }| + ## grid 4 + {1: }| + {2:~ }| + ]], float_pos={ + [4] = {{id = 1002}, "NW", 3, 0, 10, true} + }} + else + screen:expect([[ + just some | + example text | + {0:~ }| + {5:[No Name] [+] }| + jus^t some {1: } | + example te{2:~ } | + {0:~ }| + {4:[No Name] [+] }| + | + ]]) + end + + meths.win_config(win, -1, -1, {relative='cursor', row=1, col=-2}) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + {5:[No Name] [+] }| + [3:----------------------------------------]| + [3:----------------------------------------]| + [3:----------------------------------------]| + {4:[No Name] [+] }| + | + ## grid 2 + just some | + example text | + {0:~ }| + ## grid 3 + jus^t some | + example text | + {0:~ }| + ## grid 4 + {1: }| + {2:~ }| + ]], float_pos={ + [4] = {{id = 1002}, "NW", 3, 1, 1, true} + }} + else + screen:expect([[ + just some | + example text | + {0:~ }| + {5:[No Name] [+] }| + jus^t some | + e{1: } | + {0:~}{2:~ }{0: }| + {4:[No Name] [+] }| + | + ]]) + end + + meths.win_config(win, -1, -1, {relative='cursor', row=0, col=0, anchor='SW'}) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + {5:[No Name] [+] }| + [3:----------------------------------------]| + [3:----------------------------------------]| + [3:----------------------------------------]| + {4:[No Name] [+] }| + | + ## grid 2 + just some | + example text | + {0:~ }| + ## grid 3 + jus^t some | + example text | + {0:~ }| + ## grid 4 + {1: }| + {2:~ }| + ]], float_pos={ + [4] = {{id = 1002}, "SW", 3, 0, 3, true} + }} + else + screen:expect([[ + just some | + example text | + {0:~ }{1: }{0: }| + {5:[No}{2:~ }{5: }| + jus^t some | + example text | + {0:~ }| + {4:[No Name] [+] }| + | + ]]) + end + + + meths.win_config(win, -1, -1, {relative='win', win=oldwin, row=1, col=10, anchor='NW'}) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + {5:[No Name] [+] }| + [3:----------------------------------------]| + [3:----------------------------------------]| + [3:----------------------------------------]| + {4:[No Name] [+] }| + | + ## grid 2 + just some | + example text | + {0:~ }| + ## grid 3 + jus^t some | + example text | + {0:~ }| + ## grid 4 + {1: }| + {2:~ }| + ]], float_pos={ + [4] = {{id = 1002}, "NW", 2, 1, 10, true} + }} + else + screen:expect([[ + just some | + example te{1: } | + {0:~ }{2:~ }{0: }| + {5:[No Name] [+] }| + jus^t some | + example text | + {0:~ }| + {4:[No Name] [+] }| + | + ]]) + end + + meths.win_config(win, -1, -1, {relative='win', win=oldwin, row=3, col=39, anchor='SE'}) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + {5:[No Name] [+] }| + [3:----------------------------------------]| + [3:----------------------------------------]| + [3:----------------------------------------]| + {4:[No Name] [+] }| + | + ## grid 2 + just some | + example text | + {0:~ }| + ## grid 3 + jus^t some | + example text | + {0:~ }| + ## grid 4 + {1: }| + {2:~ }| + ]], float_pos={ + [4] = {{id = 1002}, "SE", 2, 3, 39, true} + }} + else + screen:expect([[ + just some | + example text {1: } | + {0:~ }{2:~ }{0: }| + {5:[No Name] [+] }| + jus^t some | + example text | + {0:~ }| + {4:[No Name] [+] }| + | + ]]) + end + + meths.win_config(win, -1, -1, {relative='win', win=0, row=0, col=50, anchor='NE'}) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + {5:[No Name] [+] }| + [3:----------------------------------------]| + [3:----------------------------------------]| + [3:----------------------------------------]| + {4:[No Name] [+] }| + | + ## grid 2 + just some | + example text | + {0:~ }| + ## grid 3 + jus^t some | + example text | + {0:~ }| + ## grid 4 + {1: }| + {2:~ }| + ]], float_pos={ + [4] = {{id = 1002}, "NE", 3, 0, 50, true} + }} + else + screen:expect([[ + just some | + example text | + {0:~ }| + {5:[No Name] [+] }| + jus^t some {1: }| + example text {2:~ }| + {0:~ }| + {4:[No Name] [+] }| + | + ]]) + end + end) + + if multigrid then + pending("supports second UI without multigrid", function() + local session2 = helpers.connect(eval('v:servername')) + print(session2:request("nvim_eval", "2+2")) + local screen2 = Screen.new(40,7) + screen2:attach(nil, session2) + screen2:set_default_attr_ids(attrs) + local buf = meths.create_buf(false,false) + local win = meths.open_win(buf, true, 20, 2, {relative='editor', row=2, col=5}) + meths.win_set_option(win, 'winhl', 'Normal:PMenu') + local expected_pos = { + [2]={{id=1001}, 'NW', 1, 2, 5} + } + screen:expect{grid=[[ + ## grid 1 + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ## grid 2 + {1:^ }| + {2:~ }| + ]], float_pos=expected_pos} + screen2:expect([[ + | + {0:~ }| + {0:~ }{1:^ }{0: }| + {0:~ }{2:~ }{0: }| + {0:~ }| + {0:~ }| + | + ]]) + end) + end + + + it('handles resized screen', function() + local buf = meths.create_buf(false,false) + meths.buf_set_lines(buf, 0, -1, true, {'such', 'very', 'float'}) + local win = meths.open_win(buf, false, 15, 4, {relative='editor', row=2, col=10}) + meths.win_set_option(win , 'winhl', 'Normal:PMenu') + local expected_pos = { + [4]={{id=1002}, 'NW', 1, 2, 10, true}, + } + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + | + ## grid 2 + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 4 + {1:such }| + {1:very }| + {1:float }| + {2:~ }| + ]], float_pos=expected_pos} + else + screen:expect([[ + ^ | + {0:~ }| + {0:~ }{1:such }{0: }| + {0:~ }{1:very }{0: }| + {0:~ }{1:float }{0: }| + {0:~ }{2:~ }{0: }| + | + ]]) + end + + screen:try_resize(40,5) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + | + ## grid 2 + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 4 + {1:such }| + {1:very }| + {1:float }| + {2:~ }| + ]], float_pos=expected_pos} + else + screen:expect([[ + ^ {1:such } | + {0:~ }{1:very }{0: }| + {0:~ }{1:float }{0: }| + {0:~ }{2:~ }{0: }| + | + ]]) + end + + screen:try_resize(40,4) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + | + ## grid 2 + ^ | + {0:~ }| + {0:~ }| + ## grid 4 + {1:such }| + {1:very }| + {1:float }| + {2:~ }| + ]], float_pos=expected_pos} + else + screen:expect([[ + ^ {1:such } | + {0:~ }{1:very }{0: }| + {0:~ }{1:float }{0: }| + | + ]]) + end + + screen:try_resize(40,3) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + | + ## grid 2 + ^ | + {0:~ }| + ## grid 4 + {1:such }| + {1:very }| + {1:float }| + {2:~ }| + ]], float_pos=expected_pos} + else + screen:expect([[ + ^ {1:such } | + {0:~ }{1:very }{0: }| + | + ]]) + end + feed('<c-w>wjj') + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + | + ## grid 2 + | + {0:~ }| + ## grid 4 + {1:such }| + {1:very }| + {1:^float }| + {2:~ }| + ]], float_pos=expected_pos} + else + screen:expect([[ + {1:very } | + {0:~ }{1:^float }{0: }| + | + ]]) + end + + screen:try_resize(40,7) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + | + ## grid 2 + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 4 + {1:such }| + {1:very }| + {1:^float }| + {2:~ }| + ]], float_pos=expected_pos} + else + screen:expect([[ + | + {0:~ }| + {0:~ }{1:very }{0: }| + {0:~ }{1:^float }{0: }| + {0:~ }| + {0:~ }| + | + ]]) + end + + meths.win_config(win, -1, 3, {}) + feed('gg') + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + | + ## grid 2 + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 4 + {1:^such }| + {1:very }| + {1:float }| + ]], float_pos=expected_pos} + else + screen:expect([[ + | + {0:~ }| + {0:~ }{1:^such }{0: }| + {0:~ }{1:very }{0: }| + {0:~ }{1:float }{0: }| + {0:~ }| + | + ]]) + end + + screen:try_resize(26,7) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:--------------------------]| + [2:--------------------------]| + [2:--------------------------]| + [2:--------------------------]| + [2:--------------------------]| + [2:--------------------------]| + | + ## grid 2 + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 4 + {1:^such }| + {1:very }| + {1:float }| + ]], float_pos=expected_pos} + else + screen:expect([[ + | + {0:~ }| + {0:~ }{1:^such }{0: }| + {0:~ }{1:very }{0: }| + {0:~ }{1:float }{0: }| + {0:~ }| + | + ]]) + end + + screen:try_resize(25,7) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:-------------------------]| + [2:-------------------------]| + [2:-------------------------]| + [2:-------------------------]| + [2:-------------------------]| + [2:-------------------------]| + | + ## grid 2 + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 4 + {1:^such }| + {1:very }| + {1:float }| + ]], float_pos=expected_pos} + else + screen:expect([[ + | + {0:~ }| + {0:~ }{1:^such }| + {0:~ }{1:very }| + {0:~ }{1:float }| + {0:~ }| + | + ]]) + end + + screen:try_resize(24,7) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:------------------------]| + [2:------------------------]| + [2:------------------------]| + [2:------------------------]| + [2:------------------------]| + [2:------------------------]| + | + ## grid 2 + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 4 + {1:^such }| + {1:very }| + {1:float }| + ]], float_pos=expected_pos} + else + screen:expect([[ + | + {0:~ }| + {0:~ }{1:^such }| + {0:~ }{1:very }| + {0:~ }{1:float }| + {0:~ }| + | + ]]) + end + + screen:try_resize(16,7) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------]| + [2:----------------]| + [2:----------------]| + [2:----------------]| + [2:----------------]| + [2:----------------]| + | + ## grid 2 + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 4 + {1:^such }| + {1:very }| + {1:float }| + ]], float_pos=expected_pos} + else + screen:expect([[ + | + {0:~ }| + {0:~}{1:^such }| + {0:~}{1:very }| + {0:~}{1:float }| + {0:~ }| + | + ]]) + end + + screen:try_resize(15,7) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:---------------]| + [2:---------------]| + [2:---------------]| + [2:---------------]| + [2:---------------]| + [2:---------------]| + | + ## grid 2 + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 4 + {1:^such }| + {1:very }| + {1:float }| + ]], float_pos=expected_pos} + else + screen:expect([[ + | + {0:~ }| + {1:^such }| + {1:very }| + {1:float }| + {0:~ }| + | + ]]) + end + + screen:try_resize(14,7) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:--------------]| + [2:--------------]| + [2:--------------]| + [2:--------------]| + [2:--------------]| + [2:--------------]| + | + ## grid 2 + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 4 + {1:^such }| + {1:very }| + {1:float }| + ]], float_pos=expected_pos} + else + screen:expect([[ + | + {0:~ }| + {1:^such }| + {1:very }| + {1:float }| + {0:~ }| + | + ]]) + end + + screen:try_resize(12,7) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:------------]| + [2:------------]| + [2:------------]| + [2:------------]| + [2:------------]| + [2:------------]| + | + ## grid 2 + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 4 + {1:^such }| + {1:very }| + {1:float }| + ]], float_pos=expected_pos} + else + screen:expect([[ + | + {0:~ }| + {1:^such }| + {1:very }| + {1:float }| + {0:~ }| + | + ]]) + end + + -- Doesn't make much sense, but check nvim doesn't crash + screen:try_resize(1,1) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:------------]| + | + ## grid 2 + | + ## grid 4 + {1:^such }| + {1:very }| + {1:float }| + ]], float_pos=expected_pos} + else + screen:expect([[ + {1:^such }| + | + ]]) + end + + screen:try_resize(40,7) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + | + ## grid 2 + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 4 + {1:^such }| + {1:very }| + {1:float }| + ]], float_pos=expected_pos} + else + screen:expect([[ + | + {0:~ }| + {0:~ }{1:^such }{0: }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + end + end) + + describe('and completion', function() + before_each(function() + local buf = meths.create_buf(false,false) + local win = meths.open_win(buf, true, 12, 4, {relative='editor', row=2, col=5}) + meths.win_set_option(win , 'winhl', 'Normal:ErrorMsg') + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + | + ## grid 2 + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {7:^ }| + {12:~ }| + {12:~ }| + {12:~ }| + ]], float_pos={ + [3] = {{ id = 1001 }, "NW", 1, 2, 5, true}, + }} + else + screen:expect([[ + | + {0:~ }| + {0:~ }{7:^ }{0: }| + {0:~ }{12:~ }{0: }| + {0:~ }{12:~ }{0: }| + {0:~ }{12:~ }{0: }| + | + ]]) + end + end) + + it('with builtin popupmenu', function() + feed('ix ') + funcs.complete(3, {'aa', 'word', 'longtext'}) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + {3:-- INSERT --} | + ## grid 2 + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {7:x aa^ }| + {12:~ }| + {12:~ }| + {12:~ }| + ## grid 4 + {13: aa }| + {1: word }| + {1: longtext }| + ]], float_pos={ + [3] = {{ id = 1001 }, "NW", 1, 2, 5, true}, + [4] = {{ id = -1 }, "NW", 3, 1, 1, false} + }} + else + screen:expect([[ + | + {0:~ }| + {0:~ }{7:x aa^ }{0: }| + {0:~ }{12:~}{13: aa }{0: }| + {0:~ }{12:~}{1: word }{0: }| + {0:~ }{12:~}{1: longtext }{0: }| + {3:-- INSERT --} | + ]]) + end + + feed('<esc>') + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + | + ## grid 2 + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {7:x a^a }| + {12:~ }| + {12:~ }| + {12:~ }| + ]], float_pos={ + [3] = {{ id = 1001 }, "NW", 1, 2, 5, true}, + }} + + else + screen:expect([[ + | + {0:~ }| + {0:~ }{7:x a^a }{0: }| + {0:~ }{12:~ }{0: }| + {0:~ }{12:~ }{0: }| + {0:~ }{12:~ }{0: }| + | + ]]) + end + + feed('<c-w>wi') + funcs.complete(1, {'xx', 'yy', 'zz'}) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + {3:-- INSERT --} | + ## grid 2 + xx^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {7:x aa }| + {12:~ }| + {12:~ }| + {12:~ }| + ## grid 4 + {13:xx }| + {1:yy }| + {1:zz }| + ]], float_pos={ + [3] = {{ id = 1001 }, "NW", 1, 2, 5, true}, + [4] = {{ id = -1 }, "NW", 2, 1, 0, false} + }} + else + screen:expect([[ + xx^ | + {13:xx }{0: }| + {1:yy }{7: }{0: }| + {1:zz }{12: }{0: }| + {0:~ }{12:~ }{0: }| + {0:~ }{12:~ }{0: }| + {3:-- INSERT --} | + ]]) + end + + feed('<c-y>') + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + {3:-- INSERT --} | + ## grid 2 + xx^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {7:x aa }| + {12:~ }| + {12:~ }| + {12:~ }| + ]], float_pos={ + [3] = {{ id = 1001 }, "NW", 1, 2, 5, true}, + }} + else + screen:expect([[ + xx^ | + {0:~ }| + {0:~ }{7:x aa }{0: }| + {0:~ }{12:~ }{0: }| + {0:~ }{12:~ }{0: }| + {0:~ }{12:~ }{0: }| + {3:-- INSERT --} | + ]]) + end + end) + + it('with ext_popupmenu', function() + screen:set_option('ext_popupmenu', true) + feed('ix ') + funcs.complete(3, {'aa', 'word', 'longtext'}) + local items = {{"aa", "", "", ""}, {"word", "", "", ""}, {"longtext", "", "", ""}} + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + {3:-- INSERT --} | + ## grid 2 + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {7:x aa^ }| + {12:~ }| + {12:~ }| + {12:~ }| + ]], float_pos={ + [3] = {{ id = 1001 }, "NW", 1, 2, 5, true}, + }, popupmenu={ + anchor = {0, 2, 3}, items = items, pos = 0 + }} + else + screen:expect{grid=[[ + | + {0:~ }| + {0:~ }{7:x aa^ }{0: }| + {0:~ }{12:~ }{0: }| + {0:~ }{12:~ }{0: }| + {0:~ }{12:~ }{0: }| + {3:-- INSERT --} | + ]], popupmenu={ + anchor = {2, 7}, items = items, pos = 0 + }} + end + + feed('<esc>') + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + | + ## grid 2 + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {7:x a^a }| + {12:~ }| + {12:~ }| + {12:~ }| + ]], float_pos={ + [3] = {{ id = 1001 }, "NW", 1, 2, 5, true}, + }} + else + screen:expect([[ + | + {0:~ }| + {0:~ }{7:x a^a }{0: }| + {0:~ }{12:~ }{0: }| + {0:~ }{12:~ }{0: }| + {0:~ }{12:~ }{0: }| + | + ]]) + end + + feed('<c-w>wi') + funcs.complete(1, {'xx', 'yy', 'zz'}) + items = {{"xx", "", "", ""}, {"yy", "", "", ""}, {"zz", "", "", ""}} + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + {3:-- INSERT --} | + ## grid 2 + xx^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {7:x aa }| + {12:~ }| + {12:~ }| + {12:~ }| + ]], float_pos={ + [3] = {{ id = 1001 }, "NW", 1, 2, 5, true}, + }, popupmenu={ + anchor = {0, 0, 2}, items = items, pos = 0 + }} + else + screen:expect{grid=[[ + xx^ | + {0:~ }| + {0:~ }{7:x aa }{0: }| + {0:~ }{12:~ }{0: }| + {0:~ }{12:~ }{0: }| + {0:~ }{12:~ }{0: }| + {3:-- INSERT --} | + ]], popupmenu={ + anchor = {0, 0}, items = items, pos = 0 + }} + end + + feed('<c-y>') + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + {3:-- INSERT --} | + ## grid 2 + xx^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {7:x aa }| + {12:~ }| + {12:~ }| + {12:~ }| + ]], float_pos={ + [3] = {{ id = 1001 }, "NW", 1, 2, 5, true}, + }} + else + screen:expect([[ + xx^ | + {0:~ }| + {0:~ }{7:x aa }{0: }| + {0:~ }{12:~ }{0: }| + {0:~ }{12:~ }{0: }| + {0:~ }{12:~ }{0: }| + {3:-- INSERT --} | + ]]) + end + end) + + end) + + + describe("handles :wincmd", function() + local win + local expected_pos + before_each(function() + -- the default, but be explicit: + command("set laststatus=1") + command("set hidden") + meths.buf_set_lines(0,0,-1,true,{"x"}) + local buf = meths.create_buf(false,false) + win = meths.open_win(buf, false, 20, 2, {relative='editor', row=2, col=5}) + meths.buf_set_lines(buf,0,-1,true,{"y"}) + meths.win_set_option(win , 'winhl', 'Normal:PMenu') + expected_pos = { + [3]={{id=1001}, 'NW', 1, 2, 5, true} + } + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + | + ## grid 2 + ^x | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {1:y }| + {2:~ }| + ]], float_pos=expected_pos} + else + screen:expect([[ + ^x | + {0:~ }| + {0:~ }{1:y }{0: }| + {0:~ }{2:~ }{0: }| + {0:~ }| + {0:~ }| + | + ]]) + end + end) + + it("w", function() + feed("<c-w>w") + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + | + ## grid 2 + x | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {1:^y }| + {2:~ }| + ]], float_pos=expected_pos} + else + screen:expect([[ + x | + {0:~ }| + {0:~ }{1:^y }{0: }| + {0:~ }{2:~ }{0: }| + {0:~ }| + {0:~ }| + | + ]]) + end + + feed("<c-w>w") + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + | + ## grid 2 + ^x | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {1:y }| + {2:~ }| + ]], float_pos=expected_pos} + else + screen:expect([[ + ^x | + {0:~ }| + {0:~ }{1:y }{0: }| + {0:~ }{2:~ }{0: }| + {0:~ }| + {0:~ }| + | + ]]) + end + end) + + it("w with focusable=false", function() + meths.win_config(win, -1, -1, {focusable=false}) + expected_pos[3][6] = false + feed("<c-w>wi") -- i to provoke redraw + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + {3:-- INSERT --} | + ## grid 2 + ^x | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {1:y }| + {2:~ }| + ]], float_pos=expected_pos} + else + screen:expect([[ + ^x | + {0:~ }| + {0:~ }{1:y }{0: }| + {0:~ }{2:~ }{0: }| + {0:~ }| + {0:~ }| + {3:-- INSERT --} | + ]]) + end + + feed("<esc><c-w>w") + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + | + ## grid 2 + ^x | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {1:y }| + {2:~ }| + ]], float_pos=expected_pos} + else + screen:expect([[ + ^x | + {0:~ }| + {0:~ }{1:y }{0: }| + {0:~ }{2:~ }{0: }| + {0:~ }| + {0:~ }| + | + ]]) + end + end) + + it("W", function() + feed("<c-w>W") + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + | + ## grid 2 + x | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {1:^y }| + {2:~ }| + ]], float_pos=expected_pos} + else + screen:expect([[ + x | + {0:~ }| + {0:~ }{1:^y }{0: }| + {0:~ }{2:~ }{0: }| + {0:~ }| + {0:~ }| + | + ]]) + end + + feed("<c-w>W") + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + | + ## grid 2 + ^x | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {1:y }| + {2:~ }| + ]], float_pos=expected_pos} + else + screen:expect([[ + ^x | + {0:~ }| + {0:~ }{1:y }{0: }| + {0:~ }{2:~ }{0: }| + {0:~ }| + {0:~ }| + | + ]]) + end + end) + + it("focus by mouse", function() + if multigrid then + meths.input_mouse('left', 'press', '', 3, 0, 0) + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + | + ## grid 2 + x | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {1:^y }| + {2:~ }| + ]], float_pos=expected_pos} + else + meths.input_mouse('left', 'press', '', 0, 2, 5) + screen:expect([[ + x | + {0:~ }| + {0:~ }{1:^y }{0: }| + {0:~ }{2:~ }{0: }| + {0:~ }| + {0:~ }| + | + ]]) + end + + if multigrid then + meths.input_mouse('left', 'press', '', 1, 0, 0) + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + | + ## grid 2 + ^x | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {1:y }| + {2:~ }| + ]], float_pos=expected_pos} + else + meths.input_mouse('left', 'press', '', 0, 0, 0) + screen:expect([[ + ^x | + {0:~ }| + {0:~ }{1:y }{0: }| + {0:~ }{2:~ }{0: }| + {0:~ }| + {0:~ }| + | + ]]) + end + end) + + it("focus by mouse (focusable=false)", function() + meths.win_config(win, -1, -1, {focusable=false}) + meths.buf_set_lines(0, -1, -1, true, {"a"}) + expected_pos[3][6] = false + if multigrid then + meths.input_mouse('left', 'press', '', 3, 0, 0) + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + | + ## grid 2 + ^x | + a | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {1:y }| + {2:~ }| + ]], float_pos=expected_pos} + else + meths.input_mouse('left', 'press', '', 0, 2, 5) + screen:expect([[ + x | + ^a | + {0:~ }{1:y }{0: }| + {0:~ }{2:~ }{0: }| + {0:~ }| + {0:~ }| + | + ]]) + end + + if multigrid then + meths.input_mouse('left', 'press', '', 1, 0, 0) + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + | + ## grid 2 + ^x | + a | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {1:y }| + {2:~ }| + ]], float_pos=expected_pos, unchanged=true} + else + meths.input_mouse('left', 'press', '', 0, 0, 0) + screen:expect([[ + ^x | + a | + {0:~ }{1:y }{0: }| + {0:~ }{2:~ }{0: }| + {0:~ }| + {0:~ }| + | + ]]) + end + end) + + + it("j", function() + feed("<c-w>ji") -- INSERT to trigger screen change + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + {3:-- INSERT --} | + ## grid 2 + ^x | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {1:y }| + {2:~ }| + ]], float_pos=expected_pos} + else + screen:expect([[ + ^x | + {0:~ }| + {0:~ }{1:y }{0: }| + {0:~ }{2:~ }{0: }| + {0:~ }| + {0:~ }| + {3:-- INSERT --} | + ]]) + end + + feed("<esc><c-w>w") + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + | + ## grid 2 + x | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {1:^y }| + {2:~ }| + ]], float_pos=expected_pos} + else + screen:expect([[ + x | + {0:~ }| + {0:~ }{1:^y }{0: }| + {0:~ }{2:~ }{0: }| + {0:~ }| + {0:~ }| + | + ]]) + end + + feed("<c-w>j") + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + | + ## grid 2 + ^x | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {1:y }| + {2:~ }| + ]], float_pos=expected_pos} + else + screen:expect([[ + ^x | + {0:~ }| + {0:~ }{1:y }{0: }| + {0:~ }{2:~ }{0: }| + {0:~ }| + {0:~ }| + | + ]]) + end + + end) + + it("s :split (non-float)", function() + feed("<c-w>s") + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [4:----------------------------------------]| + [4:----------------------------------------]| + {4:[No Name] [+] }| + [2:----------------------------------------]| + [2:----------------------------------------]| + {5:[No Name] [+] }| + | + ## grid 2 + x | + {0:~ }| + ## grid 3 + {1:y }| + {2:~ }| + ## grid 4 + ^x | + {0:~ }| + ]], float_pos=expected_pos} + else + screen:expect([[ + ^x | + {0:~ }| + {4:[No N}{1:y }{4: }| + x {2:~ } | + {0:~ }| + {5:[No Name] [+] }| + | + ]]) + end + + feed("<c-w>w") + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [4:----------------------------------------]| + [4:----------------------------------------]| + {5:[No Name] [+] }| + [2:----------------------------------------]| + [2:----------------------------------------]| + {4:[No Name] [+] }| + | + ## grid 2 + ^x | + {0:~ }| + ## grid 3 + {1:y }| + {2:~ }| + ## grid 4 + x | + {0:~ }| + ]], float_pos=expected_pos} + else + screen:expect([[ + x | + {0:~ }| + {5:[No N}{1:y }{5: }| + ^x {2:~ } | + {0:~ }| + {4:[No Name] [+] }| + | + ]]) + end + + feed("<c-w>w") + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [4:----------------------------------------]| + [4:----------------------------------------]| + {5:[No Name] [+] }| + [2:----------------------------------------]| + [2:----------------------------------------]| + {5:[No Name] [+] }| + | + ## grid 2 + x | + {0:~ }| + ## grid 3 + {1:^y }| + {2:~ }| + ## grid 4 + x | + {0:~ }| + ]], float_pos=expected_pos} + else + screen:expect([[ + x | + {0:~ }| + {5:[No N}{1:^y }{5: }| + x {2:~ } | + {0:~ }| + {5:[No Name] [+] }| + | + ]]) + end + + + feed("<c-w>w") + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [4:----------------------------------------]| + [4:----------------------------------------]| + {4:[No Name] [+] }| + [2:----------------------------------------]| + [2:----------------------------------------]| + {5:[No Name] [+] }| + | + ## grid 2 + x | + {0:~ }| + ## grid 3 + {1:y }| + {2:~ }| + ## grid 4 + ^x | + {0:~ }| + ]], float_pos=expected_pos} + else + screen:expect([[ + ^x | + {0:~ }| + {4:[No N}{1:y }{4: }| + x {2:~ } | + {0:~ }| + {5:[No Name] [+] }| + | + ]]) + end + end) + + it("s :split (float)", function() + feed("<c-w>w<c-w>s") + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [4:----------------------------------------]| + [4:----------------------------------------]| + {4:[No Name] [+] }| + [2:----------------------------------------]| + [2:----------------------------------------]| + {5:[No Name] [+] }| + | + ## grid 2 + x | + {0:~ }| + ## grid 3 + {1:y }| + {2:~ }| + ## grid 4 + {1:^y }| + {2:~ }| + ]], float_pos=expected_pos} + else + screen:expect([[ + {1:^y }| + {2:~ }| + {4:[No N}{1:y }{4: }| + x {2:~ } | + {0:~ }| + {5:[No Name] [+] }| + | + ]]) + end + + feed(":set winhighlight=<cr><c-l>") + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [4:----------------------------------------]| + [4:----------------------------------------]| + {4:[No Name] [+] }| + [2:----------------------------------------]| + [2:----------------------------------------]| + {5:[No Name] [+] }| + | + ## grid 2 + x | + {0:~ }| + ## grid 3 + {1:y }| + {2:~ }| + ## grid 4 + ^y | + {0:~ }| + ]], float_pos=expected_pos} + else + screen:expect([[ + ^y | + {0:~ }| + {4:[No N}{1:y }{4: }| + x {2:~ } | + {0:~ }| + {5:[No Name] [+] }| + | + ]]) + end + + + feed("<c-w>j") + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [4:----------------------------------------]| + [4:----------------------------------------]| + {5:[No Name] [+] }| + [2:----------------------------------------]| + [2:----------------------------------------]| + {4:[No Name] [+] }| + | + ## grid 2 + ^x | + {0:~ }| + ## grid 3 + {1:y }| + {2:~ }| + ## grid 4 + y | + {0:~ }| + ]], float_pos=expected_pos} + else + screen:expect([[ + y | + {0:~ }| + {5:[No N}{1:y }{5: }| + ^x {2:~ } | + {0:~ }| + {4:[No Name] [+] }| + | + ]]) + end + + feed("<c-w>ji") + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [4:----------------------------------------]| + [4:----------------------------------------]| + {5:[No Name] [+] }| + [2:----------------------------------------]| + [2:----------------------------------------]| + {4:[No Name] [+] }| + {3:-- INSERT --} | + ## grid 2 + ^x | + {0:~ }| + ## grid 3 + {1:y }| + {2:~ }| + ## grid 4 + y | + {0:~ }| + ]], float_pos=expected_pos} + else + screen:expect([[ + y | + {0:~ }| + {5:[No N}{1:y }{5: }| + ^x {2:~ } | + {0:~ }| + {4:[No Name] [+] }| + {3:-- INSERT --} | + ]]) + end + end) + + it(":new (non-float)", function() + feed(":new<cr>") + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [4:----------------------------------------]| + [4:----------------------------------------]| + {4:[No Name] }| + [2:----------------------------------------]| + [2:----------------------------------------]| + {5:[No Name] [+] }| + :new | + ## grid 2 + x | + {0:~ }| + ## grid 3 + {1:y }| + {2:~ }| + ## grid 4 + ^ | + {0:~ }| + ]], float_pos=expected_pos} + else + screen:expect([[ + ^ | + {0:~ }| + {4:[No N}{1:y }{4: }| + x {2:~ } | + {0:~ }| + {5:[No Name] [+] }| + :new | + ]]) + end + end) + + it(":new (float)", function() + feed("<c-w>w:new<cr>") + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [4:----------------------------------------]| + [4:----------------------------------------]| + {4:[No Name] }| + [2:----------------------------------------]| + [2:----------------------------------------]| + {5:[No Name] [+] }| + :new | + ## grid 2 + x | + {0:~ }| + ## grid 3 + {1:y }| + {2:~ }| + ## grid 4 + ^ | + {0:~ }| + ]], float_pos=expected_pos} + else + screen:expect([[ + ^ | + {0:~ }| + {4:[No N}{1:y }{4: }| + x {2:~ } | + {0:~ }| + {5:[No Name] [+] }| + :new | + ]]) + end + end) + + it("v :vsplit (non-float)", function() + feed("<c-w>v") + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [4:--------------------]{5:│}[2:-------------------]| + [4:--------------------]{5:│}[2:-------------------]| + [4:--------------------]{5:│}[2:-------------------]| + [4:--------------------]{5:│}[2:-------------------]| + [4:--------------------]{5:│}[2:-------------------]| + {4:[No Name] [+] }{5:[No Name] [+] }| + | + ## grid 2 + x | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {1:y }| + {2:~ }| + ## grid 4 + ^x | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ]], float_pos=expected_pos} + else + screen:expect([[ + ^x {5:│}x | + {0:~ }{5:│}{0:~ }| + {0:~ }{1:y }{0: }| + {0:~ }{2:~ }{0: }| + {0:~ }{5:│}{0:~ }| + {4:[No Name] [+] }{5:[No Name] [+] }| + | + ]]) + end + end) + + it(":vnew (non-float)", function() + feed(":vnew<cr>") + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [4:--------------------]{5:│}[2:-------------------]| + [4:--------------------]{5:│}[2:-------------------]| + [4:--------------------]{5:│}[2:-------------------]| + [4:--------------------]{5:│}[2:-------------------]| + [4:--------------------]{5:│}[2:-------------------]| + {4:[No Name] }{5:[No Name] [+] }| + :vnew | + ## grid 2 + x | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {1:y }| + {2:~ }| + ## grid 4 + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ]], float_pos=expected_pos} + else + screen:expect([[ + ^ {5:│}x | + {0:~ }{5:│}{0:~ }| + {0:~ }{1:y }{0: }| + {0:~ }{2:~ }{0: }| + {0:~ }{5:│}{0:~ }| + {4:[No Name] }{5:[No Name] [+] }| + :vnew | + ]]) + end + end) + + it(":vnew (float)", function() + feed("<c-w>w:vnew<cr>") + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [4:--------------------]{5:│}[2:-------------------]| + [4:--------------------]{5:│}[2:-------------------]| + [4:--------------------]{5:│}[2:-------------------]| + [4:--------------------]{5:│}[2:-------------------]| + [4:--------------------]{5:│}[2:-------------------]| + {4:[No Name] }{5:[No Name] [+] }| + :vnew | + ## grid 2 + x | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {1:y }| + {2:~ }| + ## grid 4 + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ]], float_pos=expected_pos} + else + screen:expect([[ + ^ {5:│}x | + {0:~ }{5:│}{0:~ }| + {0:~ }{1:y }{0: }| + {0:~ }{2:~ }{0: }| + {0:~ }{5:│}{0:~ }| + {4:[No Name] }{5:[No Name] [+] }| + :vnew | + ]]) + end + end) + + it("q (:quit) last non-float exits nvim", function() + command('autocmd VimLeave * call rpcrequest(1, "exit")') + -- avoid unsaved change in other buffer + feed("<c-w><c-w>:w Xtest_written2<cr><c-w><c-w>") + -- quit in last non-float + feed(":wq Xtest_written<cr>") + local exited = false + local function on_request(name, args) + eq("exit", name) + eq({}, args) + exited = true + return 0 + end + local function on_setup() + feed(":wq Xtest_written<cr>") + end + run(on_request, nil, on_setup) + os.remove('Xtest_written') + os.remove('Xtest_written2') + eq(exited, true) + end) + + it("o (:only) non-float", function() + feed("<c-w>o") + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + | + ## grid 2 + ^x | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ]]} + else + screen:expect([[ + ^x | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + end + end) + + it("o (:only) float fails", function() + feed("<c-w>w<c-w>o") + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + {4: }| + {7:E5601: Cannot close window, only floatin}| + {7:g window would remain} | + {8:Press ENTER or type command to continue}^ | + ## grid 2 + x | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {1:y }| + {2:~ }| + ]], float_pos=expected_pos} + else + screen:expect([[ + x | + {0:~ }| + {0:~ }{1:y }{0: }| + {4: }| + {7:E5601: Cannot close window, only floatin}| + {7:g window would remain} | + {8:Press ENTER or type command to continue}^ | + ]]) + end + + -- test message clear + feed('<cr>') + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + | + ## grid 2 + x | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {1:^y }| + {2:~ }| + ]], float_pos=expected_pos} + else + screen:expect([[ + x | + {0:~ }| + {0:~ }{1:^y }{0: }| + {0:~ }{2:~ }{0: }| + {0:~ }| + {0:~ }| + | + ]]) + end + end) + + it("o (:only) non-float with split", function() + feed("<c-w>s") + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [4:----------------------------------------]| + [4:----------------------------------------]| + {4:[No Name] [+] }| + [2:----------------------------------------]| + [2:----------------------------------------]| + {5:[No Name] [+] }| + | + ## grid 2 + x | + {0:~ }| + ## grid 3 + {1:y }| + {2:~ }| + ## grid 4 + ^x | + {0:~ }| + ]], float_pos=expected_pos} + else + screen:expect([[ + ^x | + {0:~ }| + {4:[No N}{1:y }{4: }| + x {2:~ } | + {0:~ }| + {5:[No Name] [+] }| + | + ]]) + end + + feed("<c-w>o") + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [4:----------------------------------------]| + [4:----------------------------------------]| + [4:----------------------------------------]| + [4:----------------------------------------]| + [4:----------------------------------------]| + [4:----------------------------------------]| + | + ## grid 4 + ^x | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ]]} + else + screen:expect([[ + ^x | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + end + end) + + it("o (:only) float with split", function() + feed("<c-w>s<c-w>W") + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [4:----------------------------------------]| + [4:----------------------------------------]| + {5:[No Name] [+] }| + [2:----------------------------------------]| + [2:----------------------------------------]| + {5:[No Name] [+] }| + | + ## grid 2 + x | + {0:~ }| + ## grid 3 + {1:^y }| + {2:~ }| + ## grid 4 + x | + {0:~ }| + ]], float_pos=expected_pos} + else + screen:expect([[ + x | + {0:~ }| + {5:[No N}{1:^y }{5: }| + x {2:~ } | + {0:~ }| + {5:[No Name] [+] }| + | + ]]) + end + + feed("<c-w>o") + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [4:----------------------------------------]| + [4:----------------------------------------]| + {5:[No Name] [+] }| + {4: }| + {7:E5601: Cannot close window, only floatin}| + {7:g window would remain} | + {8:Press ENTER or type command to continue}^ | + ## grid 2 + x | + {0:~ }| + ## grid 3 + {1:y }| + {2:~ }| + ## grid 4 + x | + {0:~ }| + ]], float_pos=expected_pos} + else + screen:expect([[ + x | + {0:~ }| + {5:[No N}{1:y }{5: }| + {4: }| + {7:E5601: Cannot close window, only floatin}| + {7:g window would remain} | + {8:Press ENTER or type command to continue}^ | + ]]) + end + end) + + it("J (float)", function() + feed("<c-w>w<c-w>J") + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + {5:[No Name] [+] }| + [3:----------------------------------------]| + [3:----------------------------------------]| + {4:[No Name] [+] }| + | + ## grid 2 + x | + {0:~ }| + ## grid 3 + {1:^y }| + {2:~ }| + ]]} + else + screen:expect([[ + x | + {0:~ }| + {5:[No Name] [+] }| + {1:^y }| + {2:~ }| + {4:[No Name] [+] }| + | + ]]) + end + + if multigrid then + meths.win_config(0,-1,-1,{external=true}) + expected_pos = {[3]={external=true}} + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + {5:[No Name] [+] }| + | + ## grid 2 + x | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {1:^y }| + {2:~ }| + ]], float_pos=expected_pos} + else + eq({false, "UI doesn't support external windows"}, + meth_pcall(meths.win_config, 0,-1,-1,{external=true})) + return + end + + feed("<c-w>J") + if multigrid then + screen:expect([[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + {5:[No Name] [+] }| + [3:----------------------------------------]| + [3:----------------------------------------]| + {4:[No Name] [+] }| + | + ## grid 2 + x | + {0:~ }| + ## grid 3 + {1:^y }| + {2:~ }| + ]]) + end + + end) + + it('movements with nested split layout', function() + command("set hidden") + feed("<c-w>s<c-w>v<c-w>b<c-w>v") + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [5:--------------------]{5:│}[4:-------------------]| + [5:--------------------]{5:│}[4:-------------------]| + {5:[No Name] [+] [No Name] [+] }| + [6:--------------------]{5:│}[2:-------------------]| + [6:--------------------]{5:│}[2:-------------------]| + {4:[No Name] [+] }{5:[No Name] [+] }| + | + ## grid 2 + x | + {0:~ }| + ## grid 3 + {1:y }| + {2:~ }| + ## grid 4 + x | + {0:~ }| + ## grid 5 + x | + {0:~ }| + ## grid 6 + ^x | + {0:~ }| + ]], float_pos=expected_pos} + else + screen:expect([[ + x {5:│}x | + {0:~ }{5:│}{0:~ }| + {5:[No N}{1:y }{5:Name] [+] }| + ^x {2:~ } | + {0:~ }{5:│}{0:~ }| + {4:[No Name] [+] }{5:[No Name] [+] }| + | + ]]) + end + + -- verify that N<c-w>w works + for i = 1,5 do + feed(i.."<c-w>w") + feed_command("enew") + curbufmeths.set_lines(0,-1,true,{tostring(i)}) + end + + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [5:-------------------]{5:│}[4:--------------------]| + [5:-------------------]{5:│}[4:--------------------]| + {5:[No Name] [+] [No Name] [+] }| + [6:-------------------]{5:│}[2:--------------------]| + [6:-------------------]{5:│}[2:--------------------]| + {5:[No Name] [+] [No Name] [+] }| + :enew | + ## grid 2 + 4 | + {0:~ }| + ## grid 3 + ^5 | + {0:~ }| + ## grid 4 + 2 | + {0:~ }| + ## grid 5 + 1 | + {0:~ }| + ## grid 6 + 3 | + {0:~ }| + ]], float_pos=expected_pos} + else + screen:expect([[ + 1 {5:│}2 | + {0:~ }{5:│}{0:~ }| + {5:[No N}^5 {5:ame] [+] }| + 3 {0:~ } | + {0:~ }{5:│}{0:~ }| + {5:[No Name] [+] [No Name] [+] }| + :enew | + ]]) + end + + local movements = { + w={2,3,4,5,1}, + W={5,1,2,3,4}, + h={1,1,3,3,3}, + j={3,3,3,4,4}, + k={1,2,1,1,1}, + l={2,2,4,4,4}, + t={1,1,1,1,1}, + b={4,4,4,4,4}, + } + + for k,v in pairs(movements) do + for i = 1,5 do + feed(i.."<c-w>w") + feed('<c-w>'..k) + local nr = funcs.winnr() + eq(v[i],nr, "when using <c-w>"..k.." from window "..i) + end + end + + for i = 1,5 do + feed(i.."<c-w>w") + for j = 1,5 do + if j ~= i then + feed(j.."<c-w>w") + feed('<c-w>p') + local nr = funcs.winnr() + eq(i,nr, "when using <c-w>p to window "..i.." from window "..j) + end + end + end + + end) + + it(":tabnew and :tabnext", function() + feed(":tabnew<cr>") + if multigrid then + -- grid is not freed, but float is marked as closed (should it rather be "invisible"?) + screen:expect{grid=[[ + ## grid 1 + {9: }{10:2}{9:+ [No Name] }{3: [No Name] }{5: }{9:X}| + [4:----------------------------------------]| + [4:----------------------------------------]| + [4:----------------------------------------]| + [4:----------------------------------------]| + [4:----------------------------------------]| + :tabnew | + ## grid 2 + x | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {1:y }| + {2:~ }| + ## grid 4 + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ]]} + else + screen:expect([[ + {9: }{10:2}{9:+ [No Name] }{3: [No Name] }{5: }{9:X}| + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + :tabnew | + ]]) + end + + feed(":tabnext<cr>") + if multigrid then + screen:expect{grid=[[ + ## grid 1 + {3: }{11:2}{3:+ [No Name] }{9: [No Name] }{5: }{9:X}| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + {4:[No Name] [+] }| + :tabnext | + ## grid 2 + ^x | + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {1:y }| + {2:~ }| + ## grid 4 + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ]], float_pos=expected_pos} + else + screen:expect([[ + {3: }{11:2}{3:+ [No Name] }{9: [No Name] }{5: }{9:X}| + ^x | + {0:~ }{1:y }{0: }| + {0:~ }{2:~ }{0: }| + {0:~ }| + {4:[No Name] [+] }| + :tabnext | + ]]) + end + + feed(":tabnext<cr>") + if multigrid then + screen:expect{grid=[[ + ## grid 1 + {9: }{10:2}{9:+ [No Name] }{3: [No Name] }{5: }{9:X}| + [4:----------------------------------------]| + [4:----------------------------------------]| + [4:----------------------------------------]| + [4:----------------------------------------]| + [4:----------------------------------------]| + :tabnext | + ## grid 2 + x | + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {1:y }| + {2:~ }| + ## grid 4 + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ]]} + else + screen:expect([[ + {9: }{10:2}{9:+ [No Name] }{3: [No Name] }{5: }{9:X}| + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + :tabnext | + ]]) + end + end) + + it(":tabnew and :tabnext (external)", function() + if multigrid then + meths.win_config(win,-1,-1,{external=true}) + expected_pos = {[3]={external=true}} + feed(":tabnew<cr>") + screen:expect{grid=[[ + ## grid 1 + {9: + [No Name] }{3: }{11:2}{3:+ [No Name] }{5: }{9:X}| + [4:----------------------------------------]| + [4:----------------------------------------]| + [4:----------------------------------------]| + [4:----------------------------------------]| + [4:----------------------------------------]| + :tabnew | + ## grid 2 + x | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {1:y }| + {2:~ }| + ## grid 4 + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ]], float_pos=expected_pos} + else + eq({false, "UI doesn't support external windows"}, + meth_pcall(meths.win_config, 0,-1,-1,{external=true})) + end + + feed(":tabnext<cr>") + if multigrid then + screen:expect{grid=[[ + ## grid 1 + {3: }{11:2}{3:+ [No Name] }{9: [No Name] }{5: }{9:X}| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + {4:[No Name] [+] }| + :tabnext | + ## grid 2 + ^x | + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {1:y }| + {2:~ }| + ## grid 4 + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ]], float_pos=expected_pos} + end + + feed(":tabnext<cr>") + if multigrid then + screen:expect{grid=[[ + ## grid 1 + {9: + [No Name] }{3: }{11:2}{3:+ [No Name] }{5: }{9:X}| + [4:----------------------------------------]| + [4:----------------------------------------]| + [4:----------------------------------------]| + [4:----------------------------------------]| + {4:[No Name] }| + :tabnext | + ## grid 2 + x | + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {1:y }| + {2:~ }| + ## grid 4 + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + ]], float_pos=expected_pos} + end + end) + end) + end + + describe('with ext_multigrid', function() + with_ext_multigrid(true) + end) + describe('without ext_multigrid', function() + with_ext_multigrid(false) + end) + +end) + diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua index 2eae549ebd..8b1b77eb81 100644 --- a/test/functional/ui/screen.lua +++ b/test/functional/ui/screen.lua @@ -158,6 +158,7 @@ function Screen.new(width, height) wildmenu_items = nil, wildmenu_selected = nil, win_position = {}, + float_pos = {}, _session = nil, messages = {}, msg_history = {}, @@ -227,10 +228,9 @@ function Screen:attach(options, session) -- simplify test code by doing the same. self._options.rgb = true end - if self._options.ext_multigrid then + if self._options.ext_multigrid or self._options.ext_float then self._options.ext_linegrid = true end - self._session = session end function Screen:detach() @@ -256,7 +256,7 @@ end -- canonical order of ext keys, used to generate asserts local ext_keys = { '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 @@ -642,7 +642,7 @@ function Screen:_handle_grid_resize(grid, width, height) end if self._cursor.grid == grid then - self._cursor.row = 1 + self._cursor.row = 1 -- -1 ? self._cursor.col = 1 end self._grids[grid] = { @@ -676,7 +676,6 @@ function Screen:_reset() self.wildmenu_pos = nil end - function Screen:_handle_mode_info_set(cursor_style_enabled, mode_info) self._cursor_style_enabled = cursor_style_enabled for _, item in pairs(mode_info) do @@ -713,7 +712,6 @@ end function Screen:_handle_grid_destroy(grid) self._grids[grid] = nil if self._options.ext_multigrid then - assert(self.win_position[grid]) self.win_position[grid] = nil end end @@ -734,6 +732,36 @@ function Screen:_handle_grid_cursor_goto(grid, row, col) self._cursor.col = col + 1 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() self._busy = true end @@ -815,20 +843,6 @@ function Screen:_handle_hl_attr_define(id, rgb_attrs, cterm_attrs, info) self._new_attrs = true 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) if self._options.ext_newgrid then return self._attr_table[val][1] @@ -922,8 +936,11 @@ function Screen:_handle_option_set(name, value) self.options[name] = value end -function Screen:_handle_popupmenu_show(items, selected, row, col) - self.popupmenu = {items=items, pos=selected, anchor={row, col}} +function Screen:_handle_popupmenu_show(items, selected, row, col, grid) + if (not self._options.ext_multigrid) and grid == 1 then + grid = nil + end + self.popupmenu = {items=items, pos=selected, anchor={row, col, grid}} end function Screen:_handle_popupmenu_select(selected) @@ -1112,6 +1129,7 @@ function Screen:_extstate_repr(attr_state) showcmd=self:_chunks_repr(self.showcmd, attr_state), ruler=self:_chunks_repr(self.ruler, attr_state), msg_history=msg_history, + float_pos=self.float_pos } end @@ -1146,7 +1164,10 @@ function Screen:redraw_debug(attrs, ignore, timeout) local function notification_cb(method, args) assert(method == 'redraw') 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 self:_redraw(args) self:print_snapshot(attrs, ignore) @@ -1159,7 +1180,7 @@ function Screen:redraw_debug(attrs, ignore, timeout) end 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 = {} for igrid,grid in pairs(self._grids) do if headers then @@ -1227,6 +1248,7 @@ function Screen:print_snapshot(attrs, ignore) io.stdout:write( "]]"..attrstr) for _, k in ipairs(ext_keys) do if ext_state[k] ~= nil then + -- TODO(bfredl): improve formating, remove ext metatables io.stdout:write(", "..k.."="..inspect(ext_state[k])) end end diff --git a/test/functional/ui/wildmode_spec.lua b/test/functional/ui/wildmode_spec.lua index ffe71cfadf..7cd09fb222 100644 --- a/test/functional/ui/wildmode_spec.lua +++ b/test/functional/ui/wildmode_spec.lua @@ -171,19 +171,21 @@ end) describe('command line completion', function() local screen - before_each(function() - clear() screen = Screen.new(40, 5) - screen:attach() - screen:set_default_attr_ids({[1]={bold=true, foreground=Screen.colors.Blue}}) + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue1}, + [2] = {foreground = Screen.colors.Grey0, background = Screen.colors.Yellow}, + [3] = {bold = true, reverse = true}, + }) end) - after_each(function() os.remove('Xtest-functional-viml-compl-dir') end) it('lists directories with empty PATH', function() + clear() + screen:attach() local tmp = funcs.tempname() command('e '.. tmp) command('cd %:h') @@ -198,6 +200,24 @@ describe('command line completion', function() :!Xtest-functional-viml-compl-dir^ | ]]) end) + + it('completes (multibyte) env var names #9655', function() + clear({env={ + ['XTEST_1AaあB']='foo', + ['XTEST_2']='bar', + }}) + screen:attach() + command('set wildmode=full') + command('set wildmenu') + feed(':!echo $XTEST_<tab>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {2:XTEST_1AaあB}{3: XTEST_2 }| + :!echo $XTEST_1AaあB^ | + ]]) + end) end) describe('ui/ext_wildmenu', function() diff --git a/test/functional/viml/completion_spec.lua b/test/functional/viml/completion_spec.lua index 70b4717c32..cd1b312265 100644 --- a/test/functional/viml/completion_spec.lua +++ b/test/functional/viml/completion_spec.lua @@ -466,6 +466,7 @@ describe('completion', function() ]]) expect('August') end) + it("repeats correctly after backspace #2674", function () feed('o<C-x><C-u>Ja') screen:expect([[ @@ -712,6 +713,118 @@ describe('completion', function() end) end) + it("does not indent until an item is selected #8345", function () + -- Indents on "ind", unindents on "unind". + source([[ + function! TestIndent() + let line = getline(v:lnum) + if (line =~ '^\s*ind') + return indent(v:lnum-1) + shiftwidth() + elseif (line =~ '^\s*unind') + return indent(v:lnum-1) - shiftwidth() + else + return indent(v:lnum-1) + endif + endfunction + set indentexpr=TestIndent() + set indentkeys=o,O,!^F,=ind,=unind + set completeopt+=menuone + ]]) + + -- Give some words to complete. + feed("iinc uninc indent unindent<CR>") + + -- Does not indent when "ind" is typed. + feed("in<C-X><C-N>") + -- Completion list is generated incorrectly if we send everything at once + -- via nvim_input(). So wait() before sending <BS>. #8480 + wait() + feed("<BS>d") + + screen:expect([[ + inc uninc indent unindent | + ind^ | + {2:indent }{0: }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {3:-- Keyword Local completion (^N^P) }{4:match 1 of 2} | + ]]) + + -- Indents when the item is selected + feed("<C-Y>") + screen:expect([[ + inc uninc indent unindent | + indent^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {3:-- INSERT --} | + ]]) + -- Indents when completion is exited using ESC. + feed("<CR>in<C-N><BS>d<Esc>") + screen:expect([[ + inc uninc indent unindent | + indent | + in^d | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + -- Works for unindenting too. + feed("ounin<C-X><C-N>") + helpers.wait() + feed("<BS>d") + screen:expect([[ + inc uninc indent unindent | + indent | + ind | + unind^ | + {0:~ }{2: unindent }{0: }| + {0:~ }| + {0:~ }| + {3:-- Keyword Local completion (^N^P) }{4:match 1 of 2} | + ]]) + -- Works when going back and forth. + feed("<BS>c") + screen:expect([[ + inc uninc indent unindent | + indent | + ind | + uninc^ | + {0:~ }{2: uninc }{0: }| + {0:~ }| + {0:~ }| + {3:-- Keyword Local completion (^N^P) }{4:match 1 of 2} | + ]]) + feed("<BS>d") + screen:expect([[ + inc uninc indent unindent | + indent | + ind | + unind^ | + {0:~ }{2: unindent }{0: }| + {0:~ }| + {0:~ }| + {3:-- Keyword Local completion (^N^P) }{4:match 1 of 2} | + ]]) + feed("<C-N><C-N><C-Y><Esc>") + screen:expect([[ + inc uninc indent unindent | + indent | + ind | + uninden^t | + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + end) it('disables folding during completion', function () feed_command("set foldmethod=indent") diff --git a/test/helpers.lua b/test/helpers.lua index 59da274e87..e4c3019adc 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -47,8 +47,8 @@ local check_logs_useless_lines = { ['See README_MISSING_SYSCALL_OR_IOCTL for guidance']=3, } -local function eq(expected, actual, ctx) - return assert.are.same(expected, actual, ctx) +local function eq(expected, actual, context) + return assert.are.same(expected, actual, context) end local function neq(expected, actual, context) return assert.are_not.same(expected, actual, context) @@ -708,7 +708,7 @@ end local function isCI() local is_travis = nil ~= os.getenv('TRAVIS') local is_appveyor = nil ~= os.getenv('APPVEYOR') - local is_quickbuild = nil ~= os.getenv('PR_NUMBER') + local is_quickbuild = nil ~= lfs.attributes('/usr/home/quickbuild') return is_travis or is_appveyor or is_quickbuild end @@ -751,6 +751,7 @@ local module = { hasenv = hasenv, hexdump = hexdump, intchar2lua = intchar2lua, + isCI = isCI, map = map, matches = matches, mergedicts_copy = mergedicts_copy, diff --git a/test/unit/helpers.lua b/test/unit/helpers.lua index f8143a0125..beb25f25db 100644 --- a/test/unit/helpers.lua +++ b/test/unit/helpers.lua @@ -645,16 +645,16 @@ local function itp_child(wr, func) s = s:sub(1, hook_msglen - 2) sc.write(wr, '>' .. s .. (' '):rep(hook_msglen - 2 - #s) .. '\n') end - local err, emsg = pcall(init) - if err then + local status, result = pcall(init) + if status then collectgarbage('stop') child_sethook(wr) - err, emsg = pcall(func) + status, result = pcall(func) debug.sethook() end - emsg = tostring(emsg) sc.write(wr, trace_end_msg) - if not err then + if not status then + local emsg = tostring(result) if #emsg > 99999 then emsg = emsg:sub(1, 99999) end @@ -668,7 +668,7 @@ local function itp_child(wr, func) collectgarbage() sc.write(wr, '$\n') sc.close(wr) - sc.exit(err and 0 or 1) + sc.exit(status and 0 or 1) end local function check_child_err(rd) diff --git a/test/unit/os/env_spec.lua b/test/unit/os/env_spec.lua index c54d5a9b77..c543551607 100644 --- a/test/unit/os/env_spec.lua +++ b/test/unit/os/env_spec.lua @@ -8,17 +8,22 @@ local ffi = helpers.ffi local cstr = helpers.cstr local to_cstr = helpers.to_cstr local NULL = helpers.NULL +local OK = 0 require('lfs') local cimp = cimport('./src/nvim/os/os.h') describe('env.c', function() + local function os_env_exists(name) + return cimp.os_env_exists(to_cstr(name)) + end + local function os_setenv(name, value, override) return cimp.os_setenv(to_cstr(name), to_cstr(value), override) end - local function os_unsetenv(name, _, _) + local function os_unsetenv(name) return cimp.os_unsetenv(to_cstr(name)) end @@ -31,25 +36,44 @@ describe('env.c', function() end end - describe('os_setenv', function() - local OK = 0 + itp('os_env_exists', function() + eq(false, os_env_exists('')) + eq(false, os_env_exists(' ')) + eq(false, os_env_exists('\t')) + eq(false, os_env_exists('\n')) + eq(false, os_env_exists('AaあB <= very weird name...')) - itp('sets an env variable and returns OK', function() + local varname = 'NVIM_UNIT_TEST_os_env_exists' + eq(false, os_env_exists(varname)) + eq(OK, os_setenv(varname, 'foo bar baz ...', 1)) + eq(true, os_env_exists(varname)) + end) + + describe('os_setenv', function() + itp('sets an env var and returns success', function() local name = 'NVIM_UNIT_TEST_SETENV_1N' local value = 'NVIM_UNIT_TEST_SETENV_1V' eq(nil, os.getenv(name)) - eq(OK, (os_setenv(name, value, 1))) + eq(OK, os_setenv(name, value, 1)) eq(value, os.getenv(name)) + + -- Set empty, then set non-empty, then retrieve. + eq(OK, os_setenv(name, '', 1)) + eq('', os.getenv(name)) + eq(OK, os_setenv(name, 'non-empty', 1)) + eq('non-empty', os.getenv(name)) end) - itp("dosn't overwrite an env variable if overwrite is 0", function() + itp("`overwrite` behavior", function() local name = 'NVIM_UNIT_TEST_SETENV_2N' local value = 'NVIM_UNIT_TEST_SETENV_2V' local value_updated = 'NVIM_UNIT_TEST_SETENV_2V_UPDATED' - eq(OK, (os_setenv(name, value, 0))) + eq(OK, os_setenv(name, value, 0)) eq(value, os.getenv(name)) - eq(OK, (os_setenv(name, value_updated, 0))) + eq(OK, os_setenv(name, value_updated, 0)) eq(value, os.getenv(name)) + eq(OK, os_setenv(name, value_updated, 1)) + eq(value_updated, os.getenv(name)) end) end) @@ -93,31 +117,42 @@ describe('env.c', function() end) describe('os_getenv', function() - itp('reads an env variable', function() + itp('reads an env var', function() local name = 'NVIM_UNIT_TEST_GETENV_1N' local value = 'NVIM_UNIT_TEST_GETENV_1V' eq(NULL, os_getenv(name)) -- Use os_setenv because Lua dosen't have setenv. os_setenv(name, value, 1) eq(value, os_getenv(name)) + + -- Get a big value. + local bigval = ('x'):rep(256) + eq(OK, os_setenv(name, bigval, 1)) + eq(bigval, os_getenv(name)) + + -- Set non-empty, then set empty. + eq(OK, os_setenv(name, 'non-empty', 1)) + eq('non-empty', os_getenv(name)) + eq(OK, os_setenv(name, '', 1)) + eq(NULL, os_getenv(name)) end) - itp('returns NULL if the env variable is not found', function() - local name = 'NVIM_UNIT_TEST_GETENV_NOTFOUND' - return eq(NULL, os_getenv(name)) + itp('returns NULL if the env var is not found', function() + eq(NULL, os_getenv('NVIM_UNIT_TEST_GETENV_NOTFOUND')) end) end) - describe('os_unsetenv', function() - itp('unsets environment variable', function() - local name = 'TEST_UNSETENV' - local value = 'TESTVALUE' - os_setenv(name, value, 1) - os_unsetenv(name) - neq(os_getenv(name), value) - -- Depending on the platform the var might be unset or set as '' - assert.True(os_getenv(name) == nil or os_getenv(name) == '') - end) + itp('os_unsetenv', function() + local name = 'TEST_UNSETENV' + local value = 'TESTVALUE' + os_setenv(name, value, 1) + eq(OK, os_unsetenv(name)) + neq(os_getenv(name), value) + -- Depending on the platform the var might be unset or set as '' + assert.True(os_getenv(name) == nil or os_getenv(name) == '') + if os_getenv(name) == nil then + eq(false, os_env_exists(name)) + end end) describe('os_getenvname_at_index', function() diff --git a/test/unit/search_spec.lua b/test/unit/search_spec.lua new file mode 100644 index 0000000000..3c2d485e0e --- /dev/null +++ b/test/unit/search_spec.lua @@ -0,0 +1,33 @@ +local helpers = require("test.unit.helpers")(after_each) +local itp = helpers.gen_itp(it) + +local to_cstr = helpers.to_cstr +local eq = helpers.eq + +local search = helpers.cimport("./src/nvim/search.h") + +itp('pat_has_uppercase', function() + -- works on empty string + eq(false, search.pat_has_uppercase(to_cstr(""))) + + -- works with utf uppercase + eq(false, search.pat_has_uppercase(to_cstr("ä"))) + eq(true, search.pat_has_uppercase(to_cstr("Ä"))) + eq(true, search.pat_has_uppercase(to_cstr("äaÅ"))) + + -- works when pat ends with backslash + eq(false, search.pat_has_uppercase(to_cstr("\\"))) + eq(false, search.pat_has_uppercase(to_cstr("ab$\\"))) + + -- skips escaped characters + eq(false, search.pat_has_uppercase(to_cstr("\\Ab"))) + eq(true, search.pat_has_uppercase(to_cstr("\\AU"))) + + -- skips _X escaped characters + eq(false, search.pat_has_uppercase(to_cstr("\\_Ab"))) + eq(true, search.pat_has_uppercase(to_cstr("\\_AU"))) + + -- skips %X escaped characters + eq(false, search.pat_has_uppercase(to_cstr("aa\\%Ab"))) + eq(true, search.pat_has_uppercase(to_cstr("aab\\%AU"))) +end) diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt index d6c7ed7951..0b90ea52a4 100644 --- a/third-party/CMakeLists.txt +++ b/third-party/CMakeLists.txt @@ -120,11 +120,11 @@ endif() include(ExternalProject) if(WIN32) - set(LIBUV_URL https://github.com/neovim/libuv/archive/0ed7feb71ca949f7a96ccb102481d17ea1bb5933.tar.gz) - set(LIBUV_SHA256 813fe763022f19878557c6fde311b6394fb9180caaaab0dd98d8704732234508) + set(LIBUV_URL https://github.com/neovim/libuv/archive/327f762644ccb964715cb99d08db0f1df43f651e.tar.gz) + set(LIBUV_SHA256 76e4ac06c7c74aeb471342c7f2d4a054af51ff054d399fac9f26e8fd5821dc92) else() - set(LIBUV_URL https://github.com/libuv/libuv/archive/v1.23.2.tar.gz) - set(LIBUV_SHA256 30af979c4f4b8d1b895ae6d115f7400c751542ccb9e656350fc89fda08d4eabd) + set(LIBUV_URL https://github.com/libuv/libuv/archive/v1.26.0.tar.gz) + set(LIBUV_SHA256 e414cf74615b7dae768f0f5667092f1d4975f5067c087bcbe0641e241ebe4693) endif() set(MSGPACK_URL https://github.com/msgpack/msgpack-c/releases/download/cpp-3.0.0/msgpack-3.0.0.tar.gz) |