aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt2
-rw-r--r--config/CMakeLists.txt10
-rw-r--r--config/config.h.in4
-rw-r--r--runtime/doc/eval.txt3
-rw-r--r--runtime/doc/ui.txt18
-rw-r--r--runtime/doc/windows.txt4
-rwxr-xr-xscripts/vim-patch.sh1
-rwxr-xr-xsrc/clint.py38
-rw-r--r--src/nvim/api/buffer.c46
-rw-r--r--src/nvim/api/private/helpers.c4
-rw-r--r--src/nvim/api/ui.c2
-rw-r--r--src/nvim/api/ui_events.in.h9
-rw-r--r--src/nvim/api/vim.c90
-rw-r--r--src/nvim/api/window.c65
-rw-r--r--src/nvim/buffer.c11
-rw-r--r--src/nvim/buffer.h40
-rw-r--r--src/nvim/buffer_defs.h31
-rw-r--r--src/nvim/edit.c8
-rw-r--r--src/nvim/event/socket.c2
-rw-r--r--src/nvim/ex_cmds.c30
-rw-r--r--src/nvim/ex_docmd.c34
-rw-r--r--src/nvim/ex_getln.c10
-rw-r--r--src/nvim/fileio.c19
-rw-r--r--src/nvim/globals.h13
-rw-r--r--src/nvim/grid_defs.h7
-rw-r--r--src/nvim/hashtab.h2
-rw-r--r--src/nvim/lib/khash.h30
-rw-r--r--src/nvim/main.c8
-rw-r--r--src/nvim/map.c32
-rw-r--r--src/nvim/map.h9
-rw-r--r--src/nvim/mbyte.c8
-rw-r--r--src/nvim/memory.c9
-rw-r--r--src/nvim/message.c20
-rw-r--r--src/nvim/misc1.c3
-rw-r--r--src/nvim/mouse.c31
-rw-r--r--src/nvim/normal.c2
-rw-r--r--src/nvim/ops.c3
-rw-r--r--src/nvim/os/env.c172
-rw-r--r--src/nvim/os/fileio.c5
-rw-r--r--src/nvim/os/fs.c128
-rw-r--r--src/nvim/os/fs_defs.h7
-rw-r--r--src/nvim/os/input.c1
-rw-r--r--src/nvim/os/pty_process_unix.c12
-rw-r--r--src/nvim/path.c2
-rw-r--r--src/nvim/popupmnu.c25
-rw-r--r--src/nvim/popupmnu.h3
-rw-r--r--src/nvim/regexp.c39
-rw-r--r--src/nvim/regexp_nfa.c22
-rw-r--r--src/nvim/screen.c486
-rw-r--r--src/nvim/search.c161
-rw-r--r--src/nvim/state.c4
-rw-r--r--src/nvim/terminal.c14
-rw-r--r--src/nvim/testdir/test_findfile.vim16
-rw-r--r--src/nvim/testdir/test_functions.vim12
-rw-r--r--src/nvim/testdir/test_listdict.vim62
-rw-r--r--src/nvim/testdir/test_marks.vim41
-rw-r--r--src/nvim/testdir/test_search.vim61
-rw-r--r--src/nvim/testdir/test_sort.vim71
-rw-r--r--src/nvim/testdir/test_substitute.vim84
-rw-r--r--src/nvim/testdir/test_textobjects.vim25
-rw-r--r--src/nvim/tui/input.c84
-rw-r--r--src/nvim/tui/tui.c23
-rw-r--r--src/nvim/ui.c32
-rw-r--r--src/nvim/ui.h3
-rw-r--r--src/nvim/ui_compositor.c113
-rw-r--r--src/nvim/window.c652
-rw-r--r--test/functional/api/buffer_spec.lua35
-rw-r--r--test/functional/api/buffer_updates_spec.lua26
-rw-r--r--test/functional/api/vim_spec.lua22
-rw-r--r--test/functional/api/window_spec.lua37
-rw-r--r--test/functional/eval/let_spec.lua39
-rw-r--r--test/functional/fixtures/CMakeLists.txt4
-rw-r--r--test/functional/fixtures/printenv-test.c59
-rw-r--r--test/functional/terminal/tui_spec.lua100
-rw-r--r--test/functional/ui/float_spec.lua3061
-rw-r--r--test/functional/ui/screen.lua70
-rw-r--r--test/functional/ui/wildmode_spec.lua30
-rw-r--r--test/functional/viml/completion_spec.lua113
-rw-r--r--test/helpers.lua7
-rw-r--r--test/unit/helpers.lua12
-rw-r--r--test/unit/os/env_spec.lua79
-rw-r--r--test/unit/search_spec.lua33
-rw-r--r--third-party/CMakeLists.txt8
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)