aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/nvim/api/private/defs.h3
-rw-r--r--src/nvim/api/private/helpers.c7
-rw-r--r--src/nvim/api/private/helpers.h6
-rw-r--r--src/nvim/api/vim.c2
-rw-r--r--src/nvim/api/window.c4
-rw-r--r--src/nvim/buffer.c54
-rw-r--r--src/nvim/buffer_defs.h8
-rw-r--r--src/nvim/charset.c39
-rw-r--r--src/nvim/eval.c131
-rw-r--r--src/nvim/eval.lua2
-rw-r--r--src/nvim/ex_cmds.c111
-rw-r--r--src/nvim/ex_docmd.c28
-rw-r--r--src/nvim/ex_getln.c13
-rw-r--r--src/nvim/msgpack_rpc/helpers.c19
-rw-r--r--src/nvim/option.c55
-rw-r--r--src/nvim/option_defs.h75
-rw-r--r--src/nvim/screen.c20
-rw-r--r--src/nvim/syntax.c2
-rw-r--r--src/nvim/testdir/test_alot.vim2
-rw-r--r--src/nvim/testdir/test_expr.vim25
-rw-r--r--src/nvim/testdir/test_expr_utf8.vim38
-rw-r--r--src/nvim/testdir/test_tabpage.vim58
-rw-r--r--src/nvim/tui/tui.c2
-rw-r--r--src/nvim/version.c97
-rw-r--r--src/nvim/window.c56
25 files changed, 608 insertions, 249 deletions
diff --git a/src/nvim/api/private/defs.h b/src/nvim/api/private/defs.h
index 1d5ecd3071..223aab09dc 100644
--- a/src/nvim/api/private/defs.h
+++ b/src/nvim/api/private/defs.h
@@ -91,9 +91,6 @@ typedef enum {
struct object {
ObjectType type;
union {
- Buffer buffer;
- Window window;
- Tabpage tabpage;
Boolean boolean;
Integer integer;
Float floating;
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index c0ee735d1a..bd83b1ff1d 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -18,6 +18,7 @@
#include "nvim/map.h"
#include "nvim/option.h"
#include "nvim/option_defs.h"
+#include "nvim/version.h"
#include "nvim/eval/typval_encode.h"
#include "nvim/lib/kvec.h"
@@ -615,13 +616,14 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err)
case kObjectTypeWindow:
case kObjectTypeTabpage:
case kObjectTypeInteger:
- if (obj.data.integer > INT_MAX || obj.data.integer < INT_MIN) {
+ if (obj.data.integer > VARNUMBER_MAX
+ || obj.data.integer < VARNUMBER_MIN) {
api_set_error(err, Validation, _("Integer value outside range"));
return false;
}
tv->v_type = VAR_NUMBER;
- tv->vval.v_number = (int)obj.data.integer;
+ tv->vval.v_number = (varnumber_T)obj.data.integer;
break;
case kObjectTypeFloat:
@@ -763,6 +765,7 @@ Dictionary api_metadata(void)
static Dictionary metadata = ARRAY_DICT_INIT;
if (!metadata.size) {
+ PUT(metadata, "version", DICTIONARY_OBJ(version_dict()));
init_function_metadata(&metadata);
init_error_type_metadata(&metadata);
init_type_metadata(&metadata);
diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h
index a946e35149..9fe8c351cf 100644
--- a/src/nvim/api/private/helpers.h
+++ b/src/nvim/api/private/helpers.h
@@ -37,15 +37,15 @@
#define BUFFER_OBJ(s) ((Object) { \
.type = kObjectTypeBuffer, \
- .data.buffer = s })
+ .data.integer = s })
#define WINDOW_OBJ(s) ((Object) { \
.type = kObjectTypeWindow, \
- .data.window = s })
+ .data.integer = s })
#define TABPAGE_OBJ(s) ((Object) { \
.type = kObjectTypeTabpage, \
- .data.tabpage = s })
+ .data.integer = s })
#define ARRAY_OBJ(a) ((Object) { \
.type = kObjectTypeArray, \
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 15ec52ebab..491375bd73 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -34,7 +34,7 @@
#endif
/// Executes an ex-command.
-/// On VimL error: Returns the VimL error and updates v:errmsg.
+/// On VimL error: Returns the VimL error; v:errmsg is not updated.
///
/// @param command Ex-command string
/// @param[out] err Error details (including actual VimL error), if any
diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c
index ef881fa0eb..1f555a6a05 100644
--- a/src/nvim/api/window.c
+++ b/src/nvim/api/window.c
@@ -348,7 +348,7 @@ Tabpage nvim_win_get_tabpage(Window window, Error *err)
/// @return Window number
Integer nvim_win_get_number(Window window, Error *err)
{
- Integer rv = 0;
+ int rv = 0;
win_T *win = find_window_by_handle(window, err);
if (!win) {
@@ -356,7 +356,7 @@ Integer nvim_win_get_number(Window window, Error *err)
}
int tabnr;
- win_get_tabwin(window, &tabnr, (int *)&rv);
+ win_get_tabwin(window, &tabnr, &rv);
return rv;
}
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index 5fb011885e..a66fdc1304 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -323,13 +323,14 @@ void close_buffer(win_T *win, buf_T *buf, int action, int abort_if_last)
wipe_buf = true;
}
- if (win_valid(win)) {
- /* Set b_last_cursor when closing the last window for the buffer.
- * Remember the last cursor position and window options of the buffer.
- * This used to be only for the current window, but then options like
- * 'foldmethod' may be lost with a ":only" command. */
- if (buf->b_nwindows == 1)
+ if (win_valid_any_tab(win)) {
+ // Set b_last_cursor when closing the last window for the buffer.
+ // Remember the last cursor position and window options of the buffer.
+ // This used to be only for the current window, but then options like
+ // 'foldmethod' may be lost with a ":only" command.
+ if (buf->b_nwindows == 1) {
set_last_cursor(win);
+ }
buflist_setfpos(buf, win,
win->w_cursor.lnum == 1 ? 0 : win->w_cursor.lnum,
win->w_cursor.col, TRUE);
@@ -402,7 +403,7 @@ void close_buffer(win_T *win, buf_T *buf, int action, int abort_if_last)
buf->b_nwindows = nwindows;
buf_freeall(buf, (del_buf ? BFA_DEL : 0) + (wipe_buf ? BFA_WIPE : 0));
- if (win_valid(win) && win->w_buffer == buf) {
+ if (win_valid_any_tab(win) && win->w_buffer == buf) {
win->w_buffer = NULL; // make sure we don't use the buffer now
}
@@ -478,17 +479,20 @@ void buf_clear_file(buf_T *buf)
buf->b_ml.ml_flags = ML_EMPTY; /* empty buffer */
}
-/*
- * buf_freeall() - free all things allocated for a buffer that are related to
- * the file. flags:
- * BFA_DEL buffer is going to be deleted
- * BFA_WIPE buffer is going to be wiped out
- * BFA_KEEP_UNDO do not free undo information
- */
+/// buf_freeall() - free all things allocated for a buffer that are related to
+/// the file. Careful: get here with "curwin" NULL when exiting.
+///
+/// @param flags BFA_DEL buffer is going to be deleted
+/// BFA_WIPE buffer is going to be wiped out
+/// BFA_KEEP_UNDO do not free undo information
void buf_freeall(buf_T *buf, int flags)
{
bool is_curbuf = (buf == curbuf);
+ int is_curwin = (curwin != NULL && curwin->w_buffer == buf);
+ win_T *the_curwin = curwin;
+ tabpage_T *the_curtab = curtab;
+ // Make sure the buffer isn't closed by autocommands.
buf->b_closing = true;
apply_autocmds(EVENT_BUFUNLOAD, buf->b_fname, buf->b_fname, FALSE, buf);
if (!buf_valid(buf)) /* autocommands may delete the buffer */
@@ -505,8 +509,18 @@ void buf_freeall(buf_T *buf, int flags)
return;
}
buf->b_closing = false;
- if (aborting()) /* autocmds may abort script processing */
+
+ // If the buffer was in curwin and the window has changed, go back to that
+ // window, if it still exists. This avoids that ":edit x" triggering a
+ // "tabnext" BufUnload autocmd leaves a window behind without a buffer.
+ if (is_curwin && curwin != the_curwin && win_valid_any_tab(the_curwin)) {
+ block_autocmds();
+ goto_tabpage_win(the_curtab, the_curwin);
+ unblock_autocmds();
+ }
+ if (aborting()) { // autocmds may abort script processing
return;
+ }
/*
* It's possible that autocommands change curbuf to the one being deleted.
@@ -4509,7 +4523,7 @@ chk_modeline (
char_u *e;
char_u *linecopy; /* local copy of any modeline found */
int prev;
- int vers;
+ intmax_t vers;
int end;
int retval = OK;
char_u *save_sourcing_name;
@@ -4528,7 +4542,10 @@ chk_modeline (
e = s + 4;
else
e = s + 3;
- vers = getdigits_int(&e);
+ if (getdigits_safe(&e, &vers) != OK) {
+ continue;
+ }
+
if (*e == ':'
&& (s[0] != 'V'
|| STRNCMP(skipwhite(e + 1), "set", 3) == 0)
@@ -4536,8 +4553,9 @@ chk_modeline (
|| (VIM_VERSION_100 >= vers && isdigit(s[3]))
|| (VIM_VERSION_100 < vers && s[3] == '<')
|| (VIM_VERSION_100 > vers && s[3] == '>')
- || (VIM_VERSION_100 == vers && s[3] == '=')))
+ || (VIM_VERSION_100 == vers && s[3] == '='))) {
break;
+ }
}
}
prev = *s;
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index ab5987612c..2e6316c74a 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -488,9 +488,9 @@ struct file_buffer {
bool file_id_valid;
FileID file_id;
- bool b_changed; /* 'modified': Set to true if something in the
- file has been changed and not written out. */
- int b_changedtick; /* incremented for each change, also for undo */
+ int b_changed; // 'modified': Set to true if something in the
+ // file has been changed and not written out.
+ int b_changedtick; // incremented for each change, also for undo
bool b_saving; /* Set to true if we are in the middle of
saving the buffer. */
@@ -655,7 +655,7 @@ struct file_buffer {
long b_p_sts; ///< 'softtabstop'
long b_p_sts_nopaste; ///< b_p_sts saved for paste mode
char_u *b_p_sua; ///< 'suffixesadd'
- bool b_p_swf; ///< 'swapfile'
+ int b_p_swf; ///< 'swapfile'
long b_p_smc; ///< 'synmaxcol'
char_u *b_p_syn; ///< 'syntax'
long b_p_ts; ///< 'tabstop'
diff --git a/src/nvim/charset.c b/src/nvim/charset.c
index 78f5d96fc7..61c5b10808 100644
--- a/src/nvim/charset.c
+++ b/src/nvim/charset.c
@@ -1732,6 +1732,26 @@ char_u* skiptowhite_esc(char_u *p) {
return p;
}
+/// Get a number from a string and skip over it, signalling overflows
+///
+/// @param[out] pp A pointer to a pointer to char_u.
+/// It will be advanced past the read number.
+/// @param[out] nr Number read from the string.
+///
+/// @return OK on success, FAIL on error/overflow
+int getdigits_safe(char_u **pp, intmax_t *nr)
+{
+ errno = 0;
+ *nr = strtoimax((char *)(*pp), (char **)pp, 10);
+
+ if ((*nr == INTMAX_MIN || *nr == INTMAX_MAX)
+ && errno == ERANGE) {
+ return FAIL;
+ }
+
+ return OK;
+}
+
/// Get a number from a string and skip over it.
///
/// @param[out] pp A pointer to a pointer to char_u.
@@ -1740,17 +1760,16 @@ char_u* skiptowhite_esc(char_u *p) {
/// @return Number read from the string.
intmax_t getdigits(char_u **pp)
{
- errno = 0;
- intmax_t number = strtoimax((char *)*pp, (char **)pp, 10);
- if (number == INTMAX_MAX || number == INTMAX_MIN) {
- assert(errno != ERANGE);
- }
+ intmax_t number;
+ int ret = getdigits_safe(pp, &number);
+
+ (void)ret; // Avoid "unused variable" warning in Release build
+ assert(ret == OK);
+
return number;
}
-/// Get an int number from a string.
-///
-/// A getdigits wrapper restricted to int values.
+/// Get an int number from a string. Like getdigits(), but restricted to `int`.
int getdigits_int(char_u **pp)
{
intmax_t number = getdigits(pp);
@@ -1760,9 +1779,7 @@ int getdigits_int(char_u **pp)
return (int)number;
}
-/// Get a long number from a string.
-///
-/// A getdigits wrapper restricted to long values.
+/// Get a long number from a string. Like getdigits(), but restricted to `long`.
long getdigits_long(char_u **pp)
{
intmax_t number = getdigits(pp);
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 76f33e7d8c..5d4241c8af 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -10524,16 +10524,10 @@ static void f_glob2regpat(typval_T *argvars, typval_T *rettv, FunPtr fptr)
: file_pat_to_reg_pat(pat, NULL, NULL, false);
}
-/*
- * "has()" function
- */
+/// "has()" function
static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- int i;
- char_u *name;
- int n = FALSE;
- static char *(has_list[]) =
- {
+ static char *(has_list[]) = {
#ifdef UNIX
"unix",
#endif
@@ -10646,36 +10640,42 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr)
NULL
};
- name = get_tv_string(&argvars[0]);
- for (i = 0; has_list[i] != NULL; ++i)
+ bool n = false;
+ char *name = (char *)get_tv_string(&argvars[0]);
+
+ for (int i = 0; has_list[i] != NULL; i++) {
if (STRICMP(name, has_list[i]) == 0) {
- n = TRUE;
+ n = true;
break;
}
+ }
- if (n == FALSE) {
+ if (!n) {
if (STRNICMP(name, "patch", 5) == 0) {
if (name[5] == '-'
- && STRLEN(name) > 11
+ && strlen(name) > 11
&& ascii_isdigit(name[6])
&& ascii_isdigit(name[8])
&& ascii_isdigit(name[10])) {
- int major = atoi((char *)name + 6);
- int minor = atoi((char *)name + 8);
+ int major = atoi(name + 6);
+ int minor = atoi(name + 8);
// Expect "patch-9.9.01234".
n = (major < VIM_VERSION_MAJOR
|| (major == VIM_VERSION_MAJOR
&& (minor < VIM_VERSION_MINOR
|| (minor == VIM_VERSION_MINOR
- && has_patch(atoi((char *)name + 10))))));
+ && has_vim_patch(atoi(name + 10))))));
} else {
- n = has_patch(atoi((char *)name + 5));
+ n = has_vim_patch(atoi(name + 5));
}
+ } else if (STRNICMP(name, "nvim-", 5) == 0) {
+ // Expect "nvim-x.y.z"
+ n = has_nvim_version(name + 5);
} else if (STRICMP(name, "vim_starting") == 0) {
n = (starting != 0);
} else if (STRICMP(name, "multi_byte_encoding") == 0) {
- n = has_mbyte;
+ n = has_mbyte != 0;
#if defined(USE_ICONV) && defined(DYNAMIC_ICONV)
} else if (STRICMP(name, "iconv") == 0) {
n = iconv_enabled(false);
@@ -10685,8 +10685,8 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
- if (n == FALSE && eval_has_provider((char *)name)) {
- n = TRUE;
+ if (!n && eval_has_provider(name)) {
+ n = true;
}
rettv->vval.v_number = n;
@@ -15612,6 +15612,39 @@ static void f_strftime(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
+// "strgetchar()" function
+static void f_strgetchar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char_u *str;
+ int len;
+ int error = false;
+ int charidx;
+
+ rettv->vval.v_number = -1;
+ str = get_tv_string_chk(&argvars[0]);
+ if (str == NULL) {
+ return;
+ }
+ len = (int)STRLEN(str);
+ charidx = get_tv_number_chk(&argvars[1], &error);
+ if (error) {
+ return;
+ }
+
+ {
+ int byteidx = 0;
+
+ while (charidx >= 0 && byteidx < len) {
+ if (charidx == 0) {
+ rettv->vval.v_number = mb_ptr2char(str + byteidx);
+ break;
+ }
+ charidx--;
+ byteidx += mb_cptr2len(str + byteidx);
+ }
+ }
+}
+
/*
* "stridx()" function
*/
@@ -15712,6 +15745,64 @@ static void f_strwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->vval.v_number = (varnumber_T) mb_string2cells(s);
}
+// "strcharpart()" function
+static void f_strcharpart(typval_T *argvars, typval_T *rettv, FunPtr fptr) {
+ char_u *p;
+ int nchar;
+ int nbyte = 0;
+ int charlen;
+ int len = 0;
+ int slen;
+ int error = false;
+
+ p = get_tv_string(&argvars[0]);
+ slen = (int)STRLEN(p);
+
+ nchar = get_tv_number_chk(&argvars[1], &error);
+ if (!error) {
+ if (nchar > 0) {
+ while (nchar > 0 && nbyte < slen) {
+ nbyte += mb_cptr2len(p + nbyte);
+ nchar--;
+ }
+ } else {
+ nbyte = nchar;
+ }
+ }
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ charlen = get_tv_number(&argvars[2]);
+ while (charlen > 0 && nbyte + len < slen) {
+ int off = nbyte + len;
+
+ if (off < 0) {
+ len += 1;
+ } else {
+ len += mb_cptr2len(p + off);
+ }
+ charlen--;
+ }
+ } else {
+ len = slen - nbyte; // default: all bytes that are available.
+ }
+
+ // Only return the overlap between the specified part and the actual
+ // string.
+ if (nbyte < 0) {
+ len += nbyte;
+ nbyte = 0;
+ } else if (nbyte > slen) {
+ nbyte = slen;
+ }
+ if (len < 0) {
+ len = 0;
+ } else if (nbyte + len > slen) {
+ len = slen - nbyte;
+ }
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = vim_strnsave(p + nbyte, len);
+}
+
/*
* "strpart()" function
*/
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index eaaee81533..bea25b36f3 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -268,9 +268,11 @@ return {
sqrt={args=1, func="float_op_wrapper", data="&sqrt"},
str2float={args=1},
str2nr={args={1, 2}},
+ strcharpart={args={2, 3}},
strchars={args={1,2}},
strdisplaywidth={args={1, 2}},
strftime={args={1, 2}},
+ strgetchar={args={2, 2}},
stridx={args={2, 3}},
string={args=1},
strlen={args=1},
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 6205daf0cb..4674460a16 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -2250,28 +2250,28 @@ do_ecmd (
xfree(new_name);
goto theend;
}
- if (buf == curbuf) /* already in new buffer */
- auto_buf = TRUE;
- else {
- if (curbuf == old_curbuf)
+ if (buf == curbuf) { // already in new buffer
+ auto_buf = true;
+ } else {
+ win_T *the_curwin = curwin;
+
+ // Set the w_closing flag to avoid that autocommands close the window.
+ the_curwin->w_closing = true;
+ if (curbuf == old_curbuf) {
buf_copy_options(buf, BCO_ENTER);
+ }
- /* close the link to the current buffer */
- u_sync(FALSE);
+ // Close the link to the current buffer. This will set
+ // curwin->w_buffer to NULL.
+ u_sync(false);
close_buffer(oldwin, curbuf,
- (flags & ECMD_HIDE) || curbuf->terminal ? 0 : DOBUF_UNLOAD, FALSE);
-
- /* Autocommands may open a new window and leave oldwin open
- * which leads to crashes since the above call sets
- * oldwin->w_buffer to NULL. */
- if (curwin != oldwin && oldwin != aucmd_win && win_valid(oldwin)) {
- assert(oldwin);
- if (oldwin->w_buffer == NULL) {
- win_close(oldwin, FALSE);
- }
- }
+ (flags & ECMD_HIDE) || curbuf->terminal ? 0 : DOBUF_UNLOAD,
+ false);
+
+ the_curwin->w_closing = false;
- if (aborting()) { /* autocmds may abort script processing */
+ // autocmds may abort script processing
+ if (aborting() && curwin->w_buffer != NULL) {
xfree(new_name);
goto theend;
}
@@ -2370,10 +2370,12 @@ do_ecmd (
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. */
- u_sync(FALSE);
- if (u_savecommon(0, curbuf->b_ml.ml_line_count + 1, 0, TRUE)
- == FAIL)
+ u_sync(false);
+ if (u_savecommon(0, curbuf->b_ml.ml_line_count + 1, 0, true)
+ == FAIL) {
+ xfree(new_name);
goto theend;
+ }
u_unchanged(curbuf);
buf_freeall(curbuf, BFA_KEEP_UNDO);
@@ -4072,61 +4074,66 @@ void ex_global(exarg_T *eap)
vim_regfree(regmatch.regprog);
}
-/*
- * Execute "cmd" on lines marked with ml_setmarked().
- */
+/// Execute `cmd` on lines marked with ml_setmarked().
void global_exe(char_u *cmd)
{
- linenr_T old_lcount; /* b_ml.ml_line_count before the command */
- buf_T *old_buf = curbuf; /* remember what buffer we started in */
- linenr_T lnum; /* line number according to old situation */
-
- /*
- * Set current position only once for a global command.
- * If global_busy is set, setpcmark() will not do anything.
- * If there is an error, global_busy will be incremented.
- */
+ linenr_T old_lcount; // b_ml.ml_line_count before the command
+ buf_T *old_buf = curbuf; // remember what buffer we started in
+ linenr_T lnum; // line number according to old situation
+ int save_mapped_ctrl_c = mapped_ctrl_c;
+
+ // Set current position only once for a global command.
+ // If global_busy is set, setpcmark() will not do anything.
+ // If there is an error, global_busy will be incremented.
setpcmark();
- /* When the command writes a message, don't overwrite the command. */
- msg_didout = TRUE;
+ // When the command writes a message, don't overwrite the command.
+ msg_didout = true;
+ // Disable CTRL-C mapping, let it interrupt (potentially long output).
+ mapped_ctrl_c = 0;
sub_nsubs = 0;
sub_nlines = 0;
- global_need_beginline = FALSE;
+ global_need_beginline = false;
global_busy = 1;
old_lcount = curbuf->b_ml.ml_line_count;
+
while (!got_int && (lnum = ml_firstmarked()) != 0 && global_busy == 1) {
curwin->w_cursor.lnum = lnum;
curwin->w_cursor.col = 0;
- if (*cmd == NUL || *cmd == '\n')
+ if (*cmd == NUL || *cmd == '\n') {
do_cmdline((char_u *)"p", NULL, NULL, DOCMD_NOWAIT);
- else
+ } else {
do_cmdline(cmd, NULL, NULL, DOCMD_NOWAIT);
+ }
os_breakcheck();
}
+ mapped_ctrl_c = save_mapped_ctrl_c;
global_busy = 0;
- if (global_need_beginline)
+ if (global_need_beginline) {
beginline(BL_WHITE | BL_FIX);
- else
- check_cursor(); /* cursor may be beyond the end of the line */
+ } else {
+ check_cursor(); // cursor may be beyond the end of the line
+ }
- /* the cursor may not have moved in the text but a change in a previous
- * line may move it on the screen */
+ // the cursor may not have moved in the text but a change in a previous
+ // line may move it on the screen
changed_line_abv_curs();
- /* If it looks like no message was written, allow overwriting the
- * command with the report for number of changes. */
- if (msg_col == 0 && msg_scrolled == 0)
- msg_didout = FALSE;
+ // If it looks like no message was written, allow overwriting the
+ // command with the report for number of changes.
+ if (msg_col == 0 && msg_scrolled == 0) {
+ msg_didout = false;
+ }
- /* If substitutes done, report number of substitutes, otherwise report
- * number of extra or deleted lines.
- * Don't report extra or deleted lines in the edge case where the buffer
- * we are in after execution is different from the buffer we started in. */
- if (!do_sub_msg(false) && curbuf == old_buf)
+ // If substitutes done, report number of substitutes, otherwise report
+ // number of extra or deleted lines.
+ // Don't report extra or deleted lines in the edge case where the buffer
+ // we are in after execution is different from the buffer we started in.
+ if (!do_sub_msg(false) && curbuf == old_buf) {
msgmore(curbuf->b_ml.ml_line_count - old_lcount);
+ }
}
#if defined(EXITFREE)
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index 9f83688e1e..5e418bf099 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -1302,8 +1302,7 @@ static char_u * do_one_cmd(char_u **cmdlinep,
* 2. Handle command modifiers.
*/
p = ea.cmd;
- if (ascii_isdigit(*ea.cmd))
- p = skipwhite(skipdigits(ea.cmd));
+ p = skip_range(ea.cmd, NULL);
switch (*p) {
/* When adding an entry, also modify cmd_exists(). */
case 'a': if (!checkforcmd(&ea.cmd, "aboveleft", 3))
@@ -1406,12 +1405,18 @@ static char_u * do_one_cmd(char_u **cmdlinep,
continue;
case 't': if (checkforcmd(&p, "tab", 3)) {
- if (ascii_isdigit(*ea.cmd))
- cmdmod.tab = atoi((char *)ea.cmd) + 1;
- else
- cmdmod.tab = tabpage_index(curtab) + 1;
- ea.cmd = p;
- continue;
+ long tabnr = get_address(&ea, &ea.cmd, ADDR_TABS, ea.skip, false);
+ if (tabnr == MAXLNUM) {
+ cmdmod.tab = tabpage_index(curtab) + 1;
+ } else {
+ if (tabnr < 0 || tabnr > LAST_TAB_NR) {
+ errormsg = (char_u *)_(e_invrange);
+ goto doend;
+ }
+ cmdmod.tab = tabnr + 1;
+ }
+ ea.cmd = p;
+ continue;
}
if (!checkforcmd(&ea.cmd, "topleft", 2))
break;
@@ -1766,11 +1771,8 @@ static char_u * do_one_cmd(char_u **cmdlinep,
if (text_locked() && !(ea.argt & CMDWIN)
&& !IS_USER_CMDIDX(ea.cmdidx)) {
- /* Command not allowed when editing the command line. */
- if (cmdwin_type != 0)
- errormsg = (char_u *)_(e_cmdwin);
- else
- errormsg = (char_u *)_(e_secure);
+ // Command not allowed when editing the command line.
+ errormsg = get_text_locked_msg();
goto doend;
}
/* Disallow editing another buffer when "curbuf_lock" is set.
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 7444eb8a38..e525c949bd 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -1688,10 +1688,15 @@ int text_locked(void) {
*/
void text_locked_msg(void)
{
- if (cmdwin_type != 0)
- EMSG(_(e_cmdwin));
- else
- EMSG(_(e_secure));
+ EMSG(_(get_text_locked_msg()));
+}
+
+char_u * get_text_locked_msg(void) {
+ if (cmdwin_type != 0) {
+ return e_cmdwin;
+ } else {
+ return e_secure;
+ }
}
/*
diff --git a/src/nvim/msgpack_rpc/helpers.c b/src/nvim/msgpack_rpc/helpers.c
index 14e1c2d978..c3a909692f 100644
--- a/src/nvim/msgpack_rpc/helpers.c
+++ b/src/nvim/msgpack_rpc/helpers.c
@@ -21,7 +21,8 @@ static msgpack_zone zone;
static msgpack_sbuffer sbuffer;
#define HANDLE_TYPE_CONVERSION_IMPL(t, lt) \
- bool msgpack_rpc_to_##lt(const msgpack_object *const obj, t *const arg) \
+ bool msgpack_rpc_to_##lt(const msgpack_object *const obj, \
+ Integer *const arg) \
FUNC_ATTR_NONNULL_ALL \
{ \
if (obj->type != MSGPACK_OBJECT_EXT \
@@ -44,12 +45,12 @@ static msgpack_sbuffer sbuffer;
return true; \
} \
\
- void msgpack_rpc_from_##lt(t o, msgpack_packer *res) \
+ void msgpack_rpc_from_##lt(Integer o, msgpack_packer *res) \
FUNC_ATTR_NONNULL_ARG(2) \
{ \
msgpack_packer pac; \
msgpack_packer_init(&pac, &sbuffer, msgpack_sbuffer_write); \
- msgpack_pack_int64(&pac, o); \
+ msgpack_pack_int64(&pac, (handle_T)o); \
msgpack_pack_ext(res, sbuffer.size, kObjectType##t); \
msgpack_pack_ext_body(res, sbuffer.data, sbuffer.size); \
msgpack_sbuffer_clear(&sbuffer); \
@@ -213,17 +214,17 @@ bool msgpack_rpc_to_object(const msgpack_object *const obj, Object *const arg)
switch (cur.mobj->via.ext.type) {
case kObjectTypeBuffer: {
cur.aobj->type = kObjectTypeBuffer;
- ret = msgpack_rpc_to_buffer(cur.mobj, &cur.aobj->data.buffer);
+ ret = msgpack_rpc_to_buffer(cur.mobj, &cur.aobj->data.integer);
break;
}
case kObjectTypeWindow: {
cur.aobj->type = kObjectTypeWindow;
- ret = msgpack_rpc_to_window(cur.mobj, &cur.aobj->data.window);
+ ret = msgpack_rpc_to_window(cur.mobj, &cur.aobj->data.integer);
break;
}
case kObjectTypeTabpage: {
cur.aobj->type = kObjectTypeTabpage;
- ret = msgpack_rpc_to_tabpage(cur.mobj, &cur.aobj->data.tabpage);
+ ret = msgpack_rpc_to_tabpage(cur.mobj, &cur.aobj->data.integer);
break;
}
}
@@ -369,15 +370,15 @@ void msgpack_rpc_from_object(const Object result, msgpack_packer *const res)
break;
}
case kObjectTypeBuffer: {
- msgpack_rpc_from_buffer(cur.aobj->data.buffer, res);
+ msgpack_rpc_from_buffer(cur.aobj->data.integer, res);
break;
}
case kObjectTypeWindow: {
- msgpack_rpc_from_window(cur.aobj->data.window, res);
+ msgpack_rpc_from_window(cur.aobj->data.integer, res);
break;
}
case kObjectTypeTabpage: {
- msgpack_rpc_from_tabpage(cur.aobj->data.tabpage, res);
+ msgpack_rpc_from_tabpage(cur.aobj->data.integer, res);
break;
}
case kObjectTypeArray: {
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 81919c00d2..a255165e32 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -2527,7 +2527,7 @@ did_set_string_option (
else if (varp == &p_sbo) {
if (check_opt_strings(p_sbo, p_scbopt_values, TRUE) != OK)
errmsg = e_invarg;
- } else if (varp == &p_ambw || (bool *)varp == &p_emoji) {
+ } else if (varp == &p_ambw || (int *)varp == &p_emoji) {
// 'ambiwidth'
if (check_opt_strings(p_ambw, p_ambw_values, false) != OK) {
errmsg = e_invarg;
@@ -3706,23 +3706,19 @@ set_bool_option (
}
}
}
- }
-
- /*
- * When 'lisp' option changes include/exclude '-' in
- * keyword characters.
- */
- else if (varp == (char_u *)&(curbuf->b_p_lisp)) {
- (void)buf_init_chartab(curbuf, FALSE); /* ignore errors */
- }
- /* when 'title' changed, may need to change the title; same for 'icon' */
- else if ((int *)varp == &p_title) {
- did_set_title(FALSE);
+ } else if (varp == (char_u *)&(curbuf->b_p_lisp)) {
+ // When 'lisp' option changes include/exclude '-' in
+ // keyword characters.
+ (void)buf_init_chartab(curbuf, false); // ignore errors
+ } else if ((int *)varp == &p_title) {
+ // when 'title' changed, may need to change the title; same for 'icon'
+ did_set_title(false);
} else if ((int *)varp == &p_icon) {
- did_set_title(TRUE);
- } else if ((bool *)varp == &curbuf->b_changed) {
- if (!value)
- save_file_ff(curbuf); /* Buffer is unchanged */
+ did_set_title(true);
+ } else if ((int *)varp == &curbuf->b_changed) {
+ if (!value) {
+ save_file_ff(curbuf); // Buffer is unchanged
+ }
redraw_titles();
modified_was_set = value;
}
@@ -3750,11 +3746,12 @@ set_bool_option (
else if ((int *)varp == &curwin->w_p_wrap) {
if (curwin->w_p_wrap)
curwin->w_leftcol = 0;
- } else if ((bool *)varp == &p_ea) {
- if (p_ea && !old_value)
+ } else if ((int *)varp == &p_ea) {
+ if (p_ea && !old_value) {
win_equal(curwin, false, 0);
- } else if ((bool *)varp == &p_acd) {
- /* Change directories when the 'acd' option is set now. */
+ }
+ } else if ((int *)varp == &p_acd) {
+ // Change directories when the 'acd' option is set now.
do_autochdir();
}
/* 'diff' */
@@ -4513,10 +4510,11 @@ get_option_value (
else {
/* Special case: 'modified' is b_changed, but we also want to consider
* it set when 'ff' or 'fenc' changed. */
- if ((bool *)varp == &curbuf->b_changed)
+ if ((int *)varp == &curbuf->b_changed) {
*numval = curbufIsChanged();
- else
+ } else {
*numval = *(int *)varp;
+ }
}
return 1;
}
@@ -4884,14 +4882,15 @@ showoneopt (
varp = get_varp_scope(p, opt_flags);
- /* for 'modified' we also need to check if 'ff' or 'fenc' changed. */
- if ((p->flags & P_BOOL) && ((bool *)varp == &curbuf->b_changed
- ? !curbufIsChanged() : !*(bool *)varp))
+ // for 'modified' we also need to check if 'ff' or 'fenc' changed.
+ if ((p->flags & P_BOOL) && ((int *)varp == &curbuf->b_changed
+ ? !curbufIsChanged() : !*(int *)varp)) {
MSG_PUTS("no");
- else if ((p->flags & P_BOOL) && *(int *)varp < 0)
+ } else if ((p->flags & P_BOOL) && *(int *)varp < 0) {
MSG_PUTS("--");
- else
+ } else {
MSG_PUTS(" ");
+ }
MSG_PUTS(p->fullname);
if (!(p->flags & P_BOOL)) {
msg_putchar('=');
diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h
index e085b973ea..8af6f2194f 100644
--- a/src/nvim/option_defs.h
+++ b/src/nvim/option_defs.h
@@ -1,8 +1,6 @@
#ifndef NVIM_OPTION_DEFS_H
#define NVIM_OPTION_DEFS_H
-#include <stdbool.h>
-
#include "nvim/types.h"
#include "nvim/macros.h" // For EXTERN
@@ -296,16 +294,16 @@ enum {
* The following are actual variables for the options
*/
-EXTERN long p_aleph; /* 'aleph' */
-EXTERN bool p_acd; /* 'autochdir' */
-EXTERN char_u *p_ambw; /* 'ambiwidth' */
-EXTERN int p_ar; /* 'autoread' */
-EXTERN int p_aw; /* 'autowrite' */
-EXTERN int p_awa; /* 'autowriteall' */
-EXTERN char_u *p_bs; /* 'backspace' */
-EXTERN char_u *p_bg; /* 'background' */
-EXTERN int p_bk; /* 'backup' */
-EXTERN char_u *p_bkc; /* 'backupcopy' */
+EXTERN long p_aleph; // 'aleph'
+EXTERN int p_acd; // 'autochdir'
+EXTERN char_u *p_ambw; // 'ambiwidth'
+EXTERN int p_ar; // 'autoread'
+EXTERN int p_aw; // 'autowrite'
+EXTERN int p_awa; // 'autowriteall'
+EXTERN char_u *p_bs; // 'backspace'
+EXTERN char_u *p_bg; // 'background'
+EXTERN int p_bk; // 'backup'
+EXTERN char_u *p_bkc; // 'backupcopy'
EXTERN unsigned int bkc_flags; ///< flags from 'backupcopy'
#ifdef IN_OPTION_C
static char *(p_bkc_values[]) =
@@ -397,14 +395,15 @@ EXTERN char_u *p_dir; /* 'directory' */
EXTERN char_u *p_dy; /* 'display' */
EXTERN unsigned dy_flags;
#ifdef IN_OPTION_C
-static char *(p_dy_values[]) = {"lastline", "uhex", NULL};
+static char *(p_dy_values[]) = { "lastline", "truncate", "uhex", NULL };
#endif
#define DY_LASTLINE 0x001
-#define DY_UHEX 0x002
+#define DY_TRUNCATE 0x002
+#define DY_UHEX 0x004
EXTERN int p_ed; // 'edcompatible'
-EXTERN bool p_emoji; // 'emoji'
+EXTERN int p_emoji; // 'emoji'
EXTERN char_u *p_ead; // 'eadirection'
-EXTERN bool p_ea; // 'equalalways'
+EXTERN int p_ea; // 'equalalways'
EXTERN char_u *p_ep; // 'equalprg'
EXTERN int p_eb; // 'errorbells'
EXTERN char_u *p_ef; // 'errorfile'
@@ -416,7 +415,7 @@ EXTERN int p_ek; // 'esckeys'
EXTERN int p_exrc; // 'exrc'
EXTERN char_u *p_fencs; // 'fileencodings'
EXTERN char_u *p_ffs; // 'fileformats'
-EXTERN bool p_fic; // 'fileignorecase'
+EXTERN int p_fic; // 'fileignorecase'
EXTERN char_u *p_fcl; // 'foldclose'
EXTERN long p_fdls; // 'foldlevelstart'
EXTERN char_u *p_fdo; // 'foldopen'
@@ -621,7 +620,7 @@ EXTERN long p_titlelen; ///< 'titlelen'
EXTERN char_u *p_titleold; ///< 'titleold'
EXTERN char_u *p_titlestring; ///< 'titlestring'
EXTERN char_u *p_tsr; ///< 'thesaurus'
-EXTERN bool p_tgc; ///< 'termguicolors'
+EXTERN int p_tgc; ///< 'termguicolors'
EXTERN int p_ttimeout; ///< 'ttimeout'
EXTERN long p_ttm; ///< 'ttimeoutlen'
EXTERN char_u *p_udir; ///< 'undodir'
@@ -650,26 +649,26 @@ char_u *p_vfile = (char_u *)""; /* used before options are initialized */
#else
extern char_u *p_vfile; /* 'verbosefile' */
#endif
-EXTERN int p_warn; /* 'warn' */
-EXTERN char_u *p_wop; /* 'wildoptions' */
-EXTERN long p_window; /* 'window' */
-EXTERN char_u *p_wak; /* 'winaltkeys' */
-EXTERN char_u *p_wig; /* 'wildignore' */
-EXTERN char_u *p_ww; /* 'whichwrap' */
-EXTERN long p_wc; /* 'wildchar' */
-EXTERN long p_wcm; /* 'wildcharm' */
-EXTERN bool p_wic; ///< 'wildignorecase'
-EXTERN char_u *p_wim; /* 'wildmode' */
-EXTERN int p_wmnu; /* 'wildmenu' */
-EXTERN long p_wh; /* 'winheight' */
-EXTERN long p_wmh; /* 'winminheight' */
-EXTERN long p_wmw; /* 'winminwidth' */
-EXTERN long p_wiw; /* 'winwidth' */
-EXTERN bool p_ws; /* 'wrapscan' */
-EXTERN int p_write; /* 'write' */
-EXTERN int p_wa; /* 'writeany' */
-EXTERN int p_wb; /* 'writebackup' */
-EXTERN long p_wd; /* 'writedelay' */
+EXTERN int p_warn; // 'warn'
+EXTERN char_u *p_wop; // 'wildoptions'
+EXTERN long p_window; // 'window'
+EXTERN char_u *p_wak; // 'winaltkeys'
+EXTERN char_u *p_wig; // 'wildignore'
+EXTERN char_u *p_ww; // 'whichwrap'
+EXTERN long p_wc; // 'wildchar'
+EXTERN long p_wcm; // 'wildcharm'
+EXTERN int p_wic; // 'wildignorecase'
+EXTERN char_u *p_wim; // 'wildmode'
+EXTERN int p_wmnu; // 'wildmenu'
+EXTERN long p_wh; // 'winheight'
+EXTERN long p_wmh; // 'winminheight'
+EXTERN long p_wmw; // 'winminwidth'
+EXTERN long p_wiw; // 'winwidth'
+EXTERN int p_ws; // 'wrapscan'
+EXTERN int p_write; // 'write'
+EXTERN int p_wa; // 'writeany'
+EXTERN int p_wb; // 'writebackup'
+EXTERN long p_wd; // 'writedelay'
EXTERN int p_force_on; ///< options that cannot be turned off.
EXTERN int p_force_off; ///< options that cannot be turned on.
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index 47dd640e59..3e4d016fe7 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -1401,7 +1401,7 @@ static void win_update(win_T *wp)
&& wp->w_lines[idx].wl_valid
&& wp->w_lines[idx].wl_lnum == lnum
&& lnum > wp->w_topline
- && !(dy_flags & DY_LASTLINE)
+ && !(dy_flags & (DY_LASTLINE | DY_TRUNCATE))
&& srow + wp->w_lines[idx].wl_size > wp->w_height
&& diff_check_fill(wp, lnum) == 0
) {
@@ -1484,10 +1484,20 @@ static void win_update(win_T *wp)
/* Window ends in filler lines. */
wp->w_botline = lnum;
wp->w_filler_rows = wp->w_height - srow;
- } else if (dy_flags & DY_LASTLINE) { /* 'display' has "lastline" */
- /*
- * Last line isn't finished: Display "@@@" at the end.
- */
+ } else if (dy_flags & DY_TRUNCATE) { // 'display' has "truncate"
+ int scr_row = wp->w_winrow + wp->w_height - 1;
+
+ // Last line isn't finished: Display "@@@" in the last screen line.
+ screen_puts_len((char_u *)"@@", 2, scr_row, wp->w_wincol,
+ hl_attr(HLF_AT));
+
+ screen_fill(scr_row, scr_row + 1,
+ (int)wp->w_wincol + 2, (int)W_ENDCOL(wp),
+ '@', ' ', hl_attr(HLF_AT));
+ set_empty_rows(wp, srow);
+ wp->w_botline = lnum;
+ } else if (dy_flags & DY_LASTLINE) { // 'display' has "lastline"
+ // Last line isn't finished: Display "@@@" at the end.
screen_fill(wp->w_winrow + wp->w_height - 1,
wp->w_winrow + wp->w_height,
W_ENDCOL(wp) - 3, W_ENDCOL(wp),
diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c
index 6fd7603629..b49ae9da21 100644
--- a/src/nvim/syntax.c
+++ b/src/nvim/syntax.c
@@ -5537,7 +5537,7 @@ void ex_ownsyntax(exarg_T *eap)
}
}
-int syntax_present(win_T *win)
+bool syntax_present(win_T *win)
{
return win->w_s->b_syn_patterns.ga_len != 0
|| win->w_s->b_syn_clusters.ga_len != 0
diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim
index 036a4c0470..87c1cd2c58 100644
--- a/src/nvim/testdir/test_alot.vim
+++ b/src/nvim/testdir/test_alot.vim
@@ -4,6 +4,8 @@
source test_assign.vim
source test_cursor_func.vim
source test_ex_undo.vim
+source test_expr.vim
+source test_expr_utf8.vim
source test_feedkeys.vim
source test_cmdline.vim
source test_menu.vim
diff --git a/src/nvim/testdir/test_expr.vim b/src/nvim/testdir/test_expr.vim
new file mode 100644
index 0000000000..7ea4ebc7df
--- /dev/null
+++ b/src/nvim/testdir/test_expr.vim
@@ -0,0 +1,25 @@
+" Tests for expressions.
+
+func Test_strgetchar()
+ call assert_equal(char2nr('a'), strgetchar('axb', 0))
+ call assert_equal(char2nr('x'), strgetchar('axb', 1))
+ call assert_equal(char2nr('b'), strgetchar('axb', 2))
+
+ call assert_equal(-1, strgetchar('axb', -1))
+ call assert_equal(-1, strgetchar('axb', 3))
+ call assert_equal(-1, strgetchar('', 0))
+endfunc
+
+func Test_strcharpart()
+ call assert_equal('a', strcharpart('axb', 0, 1))
+ call assert_equal('x', strcharpart('axb', 1, 1))
+ call assert_equal('b', strcharpart('axb', 2, 1))
+ call assert_equal('xb', strcharpart('axb', 1))
+
+ call assert_equal('', strcharpart('axb', 1, 0))
+ call assert_equal('', strcharpart('axb', 1, -1))
+ call assert_equal('', strcharpart('axb', -1, 1))
+ call assert_equal('', strcharpart('axb', -2, 2))
+
+ call assert_equal('a', strcharpart('axb', -1, 2))
+endfunc
diff --git a/src/nvim/testdir/test_expr_utf8.vim b/src/nvim/testdir/test_expr_utf8.vim
new file mode 100644
index 0000000000..7bdcb4f65f
--- /dev/null
+++ b/src/nvim/testdir/test_expr_utf8.vim
@@ -0,0 +1,38 @@
+" Tests for expressions using utf-8.
+if !has('multi_byte')
+ finish
+endif
+scriptencoding utf-8
+
+func Test_strgetchar_utf8()
+ call assert_equal(char2nr('á'), strgetchar('áxb', 0))
+ call assert_equal(char2nr('x'), strgetchar('áxb', 1))
+
+ call assert_equal(char2nr('a'), strgetchar('àxb', 0))
+ call assert_equal(char2nr('̀'), strgetchar('àxb', 1))
+ call assert_equal(char2nr('x'), strgetchar('àxb', 2))
+
+ call assert_equal(char2nr('あ'), strgetchar('あaい', 0))
+ call assert_equal(char2nr('a'), strgetchar('あaい', 1))
+ call assert_equal(char2nr('い'), strgetchar('あaい', 2))
+endfunc
+
+func Test_strcharpart_utf8()
+ call assert_equal('áxb', strcharpart('áxb', 0))
+ call assert_equal('á', strcharpart('áxb', 0, 1))
+ call assert_equal('x', strcharpart('áxb', 1, 1))
+
+ call assert_equal('いうeお', strcharpart('あいうeお', 1))
+ call assert_equal('い', strcharpart('あいうeお', 1, 1))
+ call assert_equal('いう', strcharpart('あいうeお', 1, 2))
+ call assert_equal('いうe', strcharpart('あいうeお', 1, 3))
+ call assert_equal('いうeお', strcharpart('あいうeお', 1, 4))
+ call assert_equal('eお', strcharpart('あいうeお', 3))
+ call assert_equal('e', strcharpart('あいうeお', 3, 1))
+
+ call assert_equal('あ', strcharpart('あいうeお', -3, 4))
+
+ call assert_equal('a', strcharpart('àxb', 0, 1))
+ call assert_equal('̀', strcharpart('àxb', 1, 1))
+ call assert_equal('x', strcharpart('àxb', 2, 1))
+endfunc
diff --git a/src/nvim/testdir/test_tabpage.vim b/src/nvim/testdir/test_tabpage.vim
index e6b85d6e14..0bf7d056de 100644
--- a/src/nvim/testdir/test_tabpage.vim
+++ b/src/nvim/testdir/test_tabpage.vim
@@ -186,4 +186,62 @@ function Test_tabpage_with_autocmd()
bw!
endfunction
+function Test_tabpage_with_tab_modifier()
+ for n in range(4)
+ tabedit
+ endfor
+
+ function s:check_tab(pre_nr, cmd, post_nr)
+ exec 'tabnext ' . a:pre_nr
+ exec a:cmd
+ call assert_equal(a:post_nr, tabpagenr())
+ call assert_equal('help', &filetype)
+ helpclose
+ endfunc
+
+ call s:check_tab(1, 'tab help', 2)
+ call s:check_tab(1, '3tab help', 4)
+ call s:check_tab(1, '.tab help', 2)
+ call s:check_tab(1, '.+1tab help', 3)
+ call s:check_tab(1, '0tab help', 1)
+ call s:check_tab(2, '+tab help', 4)
+ call s:check_tab(2, '+2tab help', 5)
+ call s:check_tab(4, '-tab help', 4)
+ call s:check_tab(4, '-2tab help', 3)
+ call s:check_tab(3, '$tab help', 6)
+ call assert_fails('99tab help', 'E16:')
+ call assert_fails('+99tab help', 'E16:')
+ call assert_fails('-99tab help', 'E16:')
+
+ delfunction s:check_tab
+ tabonly!
+ bw!
+endfunction
+
+func Test_tabnext_on_buf_unload1()
+ " This once caused a crash
+ new
+ tabedit
+ tabfirst
+ au BufUnload <buffer> tabnext
+ q
+
+ while tabpagenr('$') > 1
+ bwipe!
+ endwhile
+endfunc
+
+func Test_tabnext_on_buf_unload2()
+ " This once caused a crash
+ tabedit
+ autocmd BufUnload <buffer> tabnext
+ file x
+ edit y
+
+ while tabpagenr('$') > 1
+ bwipe!
+ endwhile
+endfunc
+
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index f252b00be2..5e30517c5a 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -611,6 +611,7 @@ static void suspend_event(void **argv)
bool enable_mouse = data->mouse_enabled;
tui_terminal_stop(ui);
data->cont_received = false;
+ stream_set_blocking(input_global_fd(), true); // normalize stream (#2598)
kill(0, SIGTSTP);
while (!data->cont_received) {
// poll the event loop until SIGCONT is received
@@ -620,6 +621,7 @@ static void suspend_event(void **argv)
if (enable_mouse) {
tui_mouse_on(ui);
}
+ stream_set_blocking(input_global_fd(), false); // libuv expects this
// resume the main thread
CONTINUE(data->bridge);
}
diff --git a/src/nvim/version.c b/src/nvim/version.c
index 0cb6ea199b..055a997cd4 100644
--- a/src/nvim/version.c
+++ b/src/nvim/version.c
@@ -7,6 +7,7 @@
#include <assert.h>
#include <limits.h>
+#include "nvim/api/private/helpers.h"
#include "nvim/vim.h"
#include "nvim/ascii.h"
#include "nvim/iconv.h"
@@ -129,10 +130,10 @@ static int included_patches[] = {
// 2315,
// 2314,
// 2313,
- // 2312,
+ 2312,
// 2311,
// 2310 NA
- // 2309,
+ 2309,
// 2308 NA
// 2307,
// 2306,
@@ -204,7 +205,7 @@ static int included_patches[] = {
// 2240,
// 2239,
// 2238 NA
- // 2237,
+ 2237,
// 2236,
// 2235,
// 2234 NA
@@ -229,7 +230,7 @@ static int included_patches[] = {
// 2215,
// 2214 NA
2213,
- // 2212,
+ 2212,
// 2211 NA
// 2210 NA
// 2209,
@@ -313,7 +314,7 @@ static int included_patches[] = {
// 2131 NA
// 2130 NA
// 2129 NA
- // 2128,
+ 2128,
// 2127,
// 2126,
// 2125,
@@ -332,7 +333,7 @@ static int included_patches[] = {
2112,
// 2111,
// 2110,
- // 2109,
+ 2109,
// 2108 NA
// 2107,
// 2106,
@@ -660,10 +661,10 @@ static int included_patches[] = {
// 1785,
// 1784 NA
// 1783,
- // 1782,
+ 1782,
// 1781,
// 1780,
- // 1779,
+ 1779,
// 1778 NA
// 1777 NA
// 1776 NA
@@ -701,19 +702,19 @@ static int included_patches[] = {
// 1745 NA
// 1744 NA
// 1743 NA
- // 1742,
- // 1741,
+ 1742,
+ 1741,
1740,
// 1739,
// 1738,
// 1737 NA
// 1736 NA
// 1735,
- // 1734,
+ 1734,
// 1733 NA
1732,
// 1731,
- // 1730,
+ 1730,
// 1729 NA
1728,
// 1727,
@@ -2457,20 +2458,72 @@ static char *(extra_patches[]) = {
NULL
};
-/// Checks whether patch `n` has been included.
+/// Compares a version string to the current Nvim version.
///
-/// @param n The patch number.
+/// @param version Version string like "1.3.42"
///
-/// @return TRUE if patch "n" has been included.
-int has_patch(int n)
+/// @return true if Nvim is at or above the version.
+bool has_nvim_version(char *version_str)
+ FUNC_ATTR_NONNULL_ALL
{
- int i;
- for (i = 0; included_patches[i] != 0; ++i) {
+ char *p = version_str;
+ int major = 0;
+ int minor = 0;
+ int patch = 0;
+
+ if (!ascii_isdigit(*p)) {
+ return false;
+ }
+ major = atoi(p);
+ p = strchr(p, '.'); // Find the next dot.
+
+ if (p) {
+ p++; // Advance past the dot.
+ if (!ascii_isdigit(*p)) {
+ return false;
+ }
+ minor = atoi(p);
+ p = strchr(p, '.');
+ if (p) {
+ p++;
+ if (!ascii_isdigit(*p)) {
+ return false;
+ }
+ patch = atoi(p);
+ }
+ }
+
+ return (major < NVIM_VERSION_MAJOR
+ || (major == NVIM_VERSION_MAJOR
+ && (minor < NVIM_VERSION_MINOR
+ || (minor == NVIM_VERSION_MINOR
+ && patch <= NVIM_VERSION_PATCH))));
+}
+
+/// Checks whether a Vim patch has been included.
+///
+/// @param n Patch number.
+///
+/// @return true if patch `n` has been included.
+bool has_vim_patch(int n)
+{
+ for (int i = 0; included_patches[i] != 0; i++) {
if (included_patches[i] == n) {
- return TRUE;
+ return true;
}
}
- return FALSE;
+ return false;
+}
+
+Dictionary version_dict(void) {
+ Dictionary d = ARRAY_DICT_INIT;
+ PUT(d, "major", INTEGER_OBJ(NVIM_VERSION_MAJOR));
+ PUT(d, "minor", INTEGER_OBJ(NVIM_VERSION_MINOR));
+ PUT(d, "patch", INTEGER_OBJ(NVIM_VERSION_PATCH));
+ PUT(d, "api_level", INTEGER_OBJ(NVIM_API_LEVEL));
+ PUT(d, "api_compatible", INTEGER_OBJ(NVIM_API_LEVEL_COMPAT));
+ PUT(d, "api_prerelease", BOOLEAN_OBJ(NVIM_API_PRERELEASE));
+ return d;
}
void ex_version(exarg_T *eap)
@@ -2529,7 +2582,11 @@ static void list_features(void)
}
} else {
while (msg_col % width) {
+ int old_msg_col = msg_col;
msg_putchar(' ');
+ if (old_msg_col == msg_col) {
+ break; // XXX: Avoid infinite loop.
+ }
}
}
} else {
diff --git a/src/nvim/window.c b/src/nvim/window.c
index 03a2e9a842..9c6a2e26a6 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -1065,6 +1065,23 @@ bool win_valid(win_T *win) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
return false;
}
+/// Check if "win" is a pointer to an existing window in any tabpage.
+///
+/// @param win window to check
+bool win_valid_any_tab(win_T *win) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (win == NULL) {
+ return false;
+ }
+
+ FOR_ALL_TAB_WINDOWS(tp, wp) {
+ if (wp == win) {
+ return true;
+ }
+ }
+ return false;
+}
+
/*
* Return the number of windows.
*/
@@ -1918,7 +1935,7 @@ int win_close(win_T *win, int free_buf)
if (win->w_buffer != NULL) {
win->w_closing = true;
close_buffer(win, win->w_buffer, free_buf ? DOBUF_UNLOAD : 0, true);
- if (win_valid(win)) {
+ if (win_valid_any_tab(win)) {
win->w_closing = false;
}
@@ -1938,11 +1955,19 @@ int win_close(win_T *win, int free_buf)
curwin->w_buffer = curbuf;
getout(0);
}
- /* Autocommands may have closed the window already, or closed the only
- * other window or moved to another tab page. */
- else if (!win_valid(win) || last_window() || curtab != prev_curtab
- || close_last_window_tabpage(win, free_buf, prev_curtab))
+ // Autocommands may have moved to another tab page.
+ if (curtab != prev_curtab && win_valid_any_tab(win)
+ && win->w_buffer == NULL) {
+ // Need to close the window anyway, since the buffer is NULL.
+ win_close_othertab(win, false, prev_curtab);
return FAIL;
+ }
+ // 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()
+ || close_last_window_tabpage(win, free_buf, prev_curtab)) {
+ return FAIL;
+ }
// let terminal buffers know that this window dimensions may be ignored
win->w_closing = true;
@@ -2019,12 +2044,16 @@ void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp)
tabpage_T *ptp = NULL;
int free_tp = FALSE;
- assert(win->w_buffer); // to avoid np dereference warning in next line
- if (win->w_closing || win->w_buffer->b_closing)
- return; /* window is already being closed */
+ // Get here with win->w_buffer == NULL when win_close() detects the tab page
+ // changed.
+ if (win->w_closing || (win->w_buffer != NULL && win->w_buffer->b_closing)) {
+ return; // window is already being closed
+ }
- /* Close the link to the buffer. */
- close_buffer(win, win->w_buffer, free_buf ? DOBUF_UNLOAD : 0, FALSE);
+ if (win->w_buffer != NULL) {
+ // Close the link to the buffer.
+ close_buffer(win, win->w_buffer, free_buf ? DOBUF_UNLOAD : 0, false);
+ }
/* Careful: Autocommands may have closed the tab page or made it the
* current tab page. */
@@ -3213,11 +3242,8 @@ void goto_tabpage(int n)
int i;
if (text_locked()) {
- /* Not allowed when editing the command line. */
- if (cmdwin_type != 0)
- EMSG(_(e_cmdwin));
- else
- EMSG(_(e_secure));
+ // Not allowed when editing the command line.
+ text_locked_msg();
return;
}