aboutsummaryrefslogtreecommitdiff
path: root/src/nvim
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim')
-rw-r--r--src/nvim/api/autocmd.c521
-rw-r--r--src/nvim/api/extmark.c4
-rw-r--r--src/nvim/api/keysets.lua10
-rw-r--r--src/nvim/api/private/helpers.h5
-rw-r--r--src/nvim/autocmd.c12
-rw-r--r--src/nvim/buffer.c28
-rw-r--r--src/nvim/buffer_defs.h2
-rw-r--r--src/nvim/change.c2
-rw-r--r--src/nvim/diff.c2
-rw-r--r--src/nvim/edit.c8
-rw-r--r--src/nvim/eval.c3
-rw-r--r--src/nvim/eval/funcs.c5
-rw-r--r--src/nvim/ex_docmd.c2
-rw-r--r--src/nvim/ex_getln.c144
-rw-r--r--src/nvim/ex_session.c32
-rw-r--r--src/nvim/fileio.c2
-rw-r--r--src/nvim/getchar.c5
-rw-r--r--src/nvim/main.c2
-rw-r--r--src/nvim/message.c25
-rw-r--r--src/nvim/move.c42
-rw-r--r--src/nvim/normal.c10
-rw-r--r--src/nvim/ops.c2
-rw-r--r--src/nvim/option_defs.h4
-rw-r--r--src/nvim/path.c2
-rw-r--r--src/nvim/quickfix.c1
-rw-r--r--src/nvim/regexp.c6
-rw-r--r--src/nvim/regexp_bt.c38
-rw-r--r--src/nvim/regexp_nfa.c30
-rw-r--r--src/nvim/screen.c34
-rw-r--r--src/nvim/spellfile.c3
-rw-r--r--src/nvim/state.c7
-rw-r--r--src/nvim/testdir/test_autocmd.vim30
-rw-r--r--src/nvim/testdir/test_cursorline.vim21
-rw-r--r--src/nvim/testdir/test_display.vim21
-rw-r--r--src/nvim/testdir/test_filechanged.vim1
-rw-r--r--src/nvim/testdir/test_filetype.vim1
-rw-r--r--src/nvim/testdir/test_highlight.vim25
-rw-r--r--src/nvim/testdir/test_messages.vim53
-rw-r--r--src/nvim/testdir/test_mksession.vim43
-rw-r--r--src/nvim/testdir/test_number.vim25
-rw-r--r--src/nvim/testdir/test_quickfix.vim202
-rw-r--r--src/nvim/testdir/test_regexp_latin.vim97
-rw-r--r--src/nvim/testdir/test_sort.vim1
-rw-r--r--src/nvim/tui/tui.c28
-rw-r--r--src/nvim/ui_client.c8
-rw-r--r--src/nvim/window.c130
46 files changed, 1162 insertions, 517 deletions
diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c
index 8a7dd00b2a..57f392f98e 100644
--- a/src/nvim/api/autocmd.c
+++ b/src/nvim/api/autocmd.c
@@ -36,16 +36,42 @@
// Used to delete autocmds from nvim_del_autocmd
static int64_t next_autocmd_id = 1;
-/// Get autocmds that match the requirements passed to {opts}.
+/// Get all autocommands that match the corresponding {opts}.
///
-/// @param opts Optional Parameters:
-/// - event : Name or list of name of events to match against
-/// - group (string|int): Name or id of group to match against
-/// - pattern: Pattern or list of patterns to match against. Cannot be used with {buffer}
-/// - buffer: Buffer number or list of buffer numbers for buffer local autocommands
-/// |autocmd-buflocal|. Cannot be used with {pattern}
+/// These examples will get autocommands matching ALL the given criteria:
+/// <pre>
+/// -- Matches all criteria
+/// autocommands = vim.api.nvim_get_autocmds({
+/// group = "MyGroup",
+/// event = {"BufEnter", "BufWinEnter"},
+/// pattern = {"*.c", "*.h"}
+/// })
+///
+/// -- All commands from one group
+/// autocommands = vim.api.nvim_get_autocmds({
+/// group = "MyGroup",
+/// })
+/// </pre>
+///
+/// NOTE: When multiple patterns or events are provided, it will find all the autocommands that
+/// match any combination of them.
///
-/// @return A list of autocmds that match
+/// @param opts Dictionary with at least one of the following:
+/// - group (string|integer): the autocommand group name or id to match against.
+/// - event (string|array): event or events to match against |autocmd-events|.
+/// - pattern (string|array): pattern or patterns to match against |autocmd-pattern|.
+/// @return Array of autocommands matching the criteria, with each item
+/// containing the following fields:
+/// - id (number): the autocommand id (only when defined with the API).
+/// - group (integer): the autocommand group id.
+/// - desc (string): the autocommand description.
+/// - event (string): the autocommand event.
+/// - command (string): the autocommand command.
+/// - once (boolean): whether the autocommand is only run once.
+/// - pattern (string): the autocommand pattern.
+/// If the autocommand is buffer local |autocmd-buffer-local|:
+/// - buflocal (boolean): true if the autocommand is buffer local.
+/// - buffer (number): the buffer number.
Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err)
FUNC_API_SINCE(9)
{
@@ -301,48 +327,71 @@ cleanup:
return autocmd_list;
}
-/// Create an autocmd.
+/// Create an |autocommand|
///
-/// @param event The event or events to register this autocmd
-/// Required keys:
-/// event: string | ArrayOf(string)
+/// The API allows for two (mutually exclusive) types of actions to be executed when the autocommand
+/// triggers: a callback function (Lua or Vimscript), or a command (like regular autocommands).
///
-/// Examples:
-/// - event: "pat1,pat2,pat3",
-/// - event: "pat1"
-/// - event: { "pat1" }
-/// - event: { "pat1", "pat2", "pat3" }
+/// Example using callback:
+/// <pre>
+/// -- Lua function
+/// local myluafun = function() print("This buffer enters") end
///
-/// @param opts Optional Parameters:
-/// - callback: (string|function)
-/// - (string): The name of the viml function to execute when triggering this autocmd
-/// - (function): The lua function to execute when triggering this autocmd
-/// - NOTE: Cannot be used with {command}
-/// - command: (string) command
-/// - vimscript command
-/// - NOTE: Cannot be used with {callback}
-/// Eg. command = "let g:value_set = v:true"
-/// - pattern: (string|table)
-/// - pattern or patterns to match against
-/// - defaults to "*".
-/// - NOTE: Cannot be used with {buffer}
-/// - buffer: (bufnr)
-/// - create a |autocmd-buflocal| autocmd.
-/// - NOTE: Cannot be used with {pattern}
-/// - group: (string|int) The augroup name or id
-/// - once: (boolean) - See |autocmd-once|
-/// - nested: (boolean) - See |autocmd-nested|
-/// - desc: (string) - Description of the autocmd
+/// -- Vimscript function name (as a string)
+/// local myvimfun = "g:MyVimFunction"
+///
+/// vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, {
+/// pattern = {"*.c", "*.h"},
+/// callback = myluafun, -- Or myvimfun
+/// })
+/// </pre>
///
-/// @returns opaque value to use with nvim_del_autocmd
+/// Example using command:
+/// <pre>
+/// vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, {
+/// pattern = {"*.c", "*.h"},
+/// command = "echo 'Entering a C or C++ file'",
+/// })
+/// </pre>
+///
+/// Example values for pattern:
+/// <pre>
+/// pattern = "*.py"
+/// pattern = { "*.py", "*.pyi" }
+/// </pre>
+///
+/// Examples values for event:
+/// <pre>
+/// "BufPreWrite"
+/// {"CursorHold", "BufPreWrite", "BufPostWrite"}
+/// </pre>
+///
+/// @param event (String|Array) The event or events to register this autocommand
+/// @param opts Dictionary of autocommand options:
+/// - group (string|integer) optional: the autocommand group name or
+/// id to match against.
+/// - pattern (string|array) optional: pattern or patterns to match
+/// against |autocmd-pattern|.
+/// - buffer (integer) optional: buffer number for buffer local autocommands
+/// |autocmd-buflocal|. Cannot be used with {pattern}.
+/// - desc (string) optional: description of the autocommand.
+/// - callback (function|string) optional: Lua function or Vim function (as string) to
+/// execute on event. Cannot be used with {command}
+/// - command (string) optional: Vim command to execute on event. Cannot be used with
+/// {callback}
+/// - once (boolean) optional: defaults to false. Run the autocommand
+/// only once |autocmd-once|.
+/// - nested (boolean) optional: defaults to false. Run nested
+/// autocommands |autocmd-nested|.
+///
+/// @return Integer id of the created autocommand.
+/// @see |autocommand|
+/// @see |nvim_del_autocmd()|
Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autocmd) *opts,
Error *err)
FUNC_API_SINCE(9)
{
int64_t autocmd_id = -1;
-
- const char_u pattern_buflocal[BUFLOCAL_PAT_LEN];
- int au_group = AUGROUP_DEFAULT;
char *desc = NULL;
Array patterns = ARRAY_DICT_INIT;
@@ -352,7 +401,7 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc
Callback cb = CALLBACK_NONE;
- if (!unpack_string_or_array(&event_array, &event, "event", err)) {
+ if (!unpack_string_or_array(&event_array, &event, "event", true, err)) {
goto cleanup;
}
@@ -414,84 +463,13 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc
bool is_once = api_object_to_bool(opts->once, "once", false, err);
bool is_nested = api_object_to_bool(opts->nested, "nested", false, err);
- switch (opts->group.type) {
- case kObjectTypeNil:
- break;
- case kObjectTypeString:
- au_group = augroup_find(opts->group.data.string.data);
- if (au_group == AUGROUP_ERROR) {
- api_set_error(err,
- kErrorTypeValidation,
- "invalid augroup: %s", opts->group.data.string.data);
- goto cleanup;
- }
- break;
- case kObjectTypeInteger:
- au_group = (int)opts->group.data.integer;
- char *name = augroup_name(au_group);
- if (!augroup_exists(name)) {
- api_set_error(err, kErrorTypeValidation, "invalid augroup: %d", au_group);
- goto cleanup;
- }
- break;
- default:
- api_set_error(err, kErrorTypeValidation, "'group' must be a string or an integer.");
+ int au_group = get_augroup_from_object(opts->group, err);
+ if (au_group == AUGROUP_ERROR) {
goto cleanup;
}
- if (opts->pattern.type != kObjectTypeNil && opts->buffer.type != kObjectTypeNil) {
- api_set_error(err, kErrorTypeValidation,
- "cannot pass both: 'pattern' and 'buffer' for the same autocmd");
+ if (!get_patterns_from_pattern_or_buf(&patterns, opts->pattern, opts->buffer, err)) {
goto cleanup;
- } else if (opts->pattern.type != kObjectTypeNil) {
- Object *v = &opts->pattern;
-
- if (v->type == kObjectTypeString) {
- char_u *pat = (char_u *)v->data.string.data;
- size_t patlen = aucmd_pattern_length(pat);
- while (patlen) {
- ADD(patterns, STRING_OBJ(cbuf_to_string((char *)pat, patlen)));
-
- pat = aucmd_next_pattern(pat, patlen);
- patlen = aucmd_pattern_length(pat);
- }
- } else if (v->type == kObjectTypeArray) {
- if (!check_autocmd_string_array(patterns, "pattern", err)) {
- goto cleanup;
- }
-
- Array array = v->data.array;
- for (size_t i = 0; i < array.size; i++) {
- char_u *pat = (char_u *)array.items[i].data.string.data;
- size_t patlen = aucmd_pattern_length(pat);
- while (patlen) {
- ADD(patterns, STRING_OBJ(cbuf_to_string((char *)pat, patlen)));
-
- pat = aucmd_next_pattern(pat, patlen);
- patlen = aucmd_pattern_length(pat);
- }
- }
- } else {
- api_set_error(err,
- kErrorTypeValidation,
- "'pattern' must be a string");
- goto cleanup;
- }
- } else if (opts->buffer.type != kObjectTypeNil) {
- if (opts->buffer.type != kObjectTypeInteger) {
- api_set_error(err,
- kErrorTypeValidation,
- "'buffer' must be an integer");
- goto cleanup;
- }
-
- buf_T *buf = find_buffer_by_handle((Buffer)opts->buffer.data.integer, err);
- if (ERROR_SET(err)) {
- goto cleanup;
- }
-
- snprintf((char *)pattern_buflocal, BUFLOCAL_PAT_LEN, "<buffer=%d>", (int)buf->handle);
- ADD(patterns, STRING_OBJ(cstr_to_string((char *)pattern_buflocal)));
}
if (opts->desc.type != kObjectTypeNil) {
@@ -552,33 +530,126 @@ cleanup:
return autocmd_id;
}
-/// Delete an autocmd by {id}. Autocmds only return IDs when created
-/// via the API. Will not error if called and no autocmds match
-/// the {id}.
+/// Delete an autocommand by id.
///
-/// @param id Integer The ID returned by nvim_create_autocmd
-void nvim_del_autocmd(Integer id)
+/// NOTE: Only autocommands created via the API have an id.
+/// @param id Integer The id returned by nvim_create_autocmd
+/// @see |nvim_create_autocmd()|
+void nvim_del_autocmd(Integer id, Error *err)
FUNC_API_SINCE(9)
{
- autocmd_delete_id(id);
+ if (id <= 0) {
+ api_set_error(err, kErrorTypeException, "Invalid autocmd id");
+ return;
+ }
+ if (!autocmd_delete_id(id)) {
+ api_set_error(err, kErrorTypeException, "Failed to delete autocmd");
+ }
}
-/// Create or get an augroup.
+/// Clear all autocommands that match the corresponding {opts}. To delete
+/// a particular autocmd, see |nvim_del_autocmd|.
+/// @param opts Parameters
+/// - event: (string|table)
+/// Examples:
+/// - event: "pat1"
+/// - event: { "pat1" }
+/// - event: { "pat1", "pat2", "pat3" }
+/// - pattern: (string|table)
+/// - pattern or patterns to match exactly.
+/// - For example, if you have `*.py` as that pattern for the autocmd,
+/// you must pass `*.py` exactly to clear it. `test.py` will not
+/// match the pattern.
+/// - defaults to clearing all patterns.
+/// - NOTE: Cannot be used with {buffer}
+/// - buffer: (bufnr)
+/// - clear only |autocmd-buflocal| autocommands.
+/// - NOTE: Cannot be used with {pattern}
+/// - group: (string|int) The augroup name or id.
+/// - NOTE: If not passed, will only delete autocmds *not* in any group.
///
-/// To get an existing augroup ID, do:
+void nvim_clear_autocmds(Dict(clear_autocmds) *opts, Error *err)
+ FUNC_API_SINCE(9)
+{
+ // TODO(tjdevries): Future improvements:
+ // - once: (boolean) - Only clear autocmds with once. See |autocmd-once|
+ // - nested: (boolean) - Only clear autocmds with nested. See |autocmd-nested|
+ // - group: Allow passing "*" or true or something like that to force doing all
+ // autocmds, regardless of their group.
+
+ Array patterns = ARRAY_DICT_INIT;
+ Array event_array = ARRAY_DICT_INIT;
+
+ if (!unpack_string_or_array(&event_array, &opts->event, "event", false, err)) {
+ goto cleanup;
+ }
+
+ if (opts->pattern.type != kObjectTypeNil && opts->buffer.type != kObjectTypeNil) {
+ api_set_error(err, kErrorTypeValidation,
+ "Cannot use both 'pattern' and 'buffer'");
+ goto cleanup;
+ }
+
+ int au_group = get_augroup_from_object(opts->group, err);
+ if (au_group == AUGROUP_ERROR) {
+ goto cleanup;
+ }
+
+ if (!get_patterns_from_pattern_or_buf(&patterns, opts->pattern, opts->buffer, err)) {
+ goto cleanup;
+ }
+
+ // When we create the autocmds, we want to say that they are all matched, so that's *
+ // but when we clear them, we want to say that we didn't pass a pattern, so that's NUL
+ if (patterns.size == 0) {
+ ADD(patterns, STRING_OBJ(STATIC_CSTR_TO_STRING("")));
+ }
+
+ // If we didn't pass any events, that means clear all events.
+ if (event_array.size == 0) {
+ FOR_ALL_AUEVENTS(event) {
+ FOREACH_ITEM(patterns, pat_object, {
+ char_u *pat = (char_u *)pat_object.data.string.data;
+ if (!clear_autocmd(event, pat, au_group, err)) {
+ goto cleanup;
+ }
+ });
+ }
+ } else {
+ FOREACH_ITEM(event_array, event_str, {
+ GET_ONE_EVENT(event_nr, event_str, cleanup);
+
+ FOREACH_ITEM(patterns, pat_object, {
+ char_u *pat = (char_u *)pat_object.data.string.data;
+ if (!clear_autocmd(event_nr, pat, au_group, err)) {
+ goto cleanup;
+ }
+ });
+ });
+ }
+
+cleanup:
+ api_free_array(event_array);
+ api_free_array(patterns);
+
+ return;
+}
+
+/// Create or get an autocommand group |autocmd-groups|.
+///
+/// To get an existing group id, do:
/// <pre>
-/// local id = vim.api.nvim_create_augroup(name, {
+/// local id = vim.api.nvim_create_augroup("MyGroup", {
/// clear = false
/// })
/// </pre>
///
-/// @param name String: The name of the augroup to create
-/// @param opts Parameters
-/// - clear (bool): Whether to clear existing commands or not.
-/// Defaults to true.
-/// See |autocmd-groups|
-///
-/// @returns opaque value to use with nvim_del_augroup_by_id
+/// @param name String: The name of the group
+/// @param opts Dictionary Parameters
+/// - clear (bool) optional: defaults to true. Clear existing
+/// commands if the group already exists |autocmd-groups|.
+/// @return Integer id of the created group.
+/// @see |autocmd-groups|
Integer nvim_create_augroup(uint64_t channel_id, String name, Dict(create_augroup) *opts,
Error *err)
FUNC_API_SINCE(9)
@@ -604,43 +675,56 @@ Integer nvim_create_augroup(uint64_t channel_id, String name, Dict(create_augrou
return augroup;
}
-/// Delete an augroup by {id}. {id} can only be returned when augroup was
-/// created with |nvim_create_augroup|.
+/// Delete an autocommand group by id.
///
-/// NOTE: behavior differs from augroup-delete.
+/// To get a group id one can use |nvim_get_autocmds()|.
///
-/// When deleting an augroup, autocmds contained by this augroup will also be deleted and cleared.
-/// This augroup will no longer exist
-void nvim_del_augroup_by_id(Integer id)
+/// NOTE: behavior differs from |augroup-delete|. When deleting a group, autocommands contained in
+/// this group will also be deleted and cleared. This group will no longer exist.
+/// @param id Integer The id of the group.
+/// @see |nvim_del_augroup_by_name()|
+/// @see |nvim_create_augroup()|
+void nvim_del_augroup_by_id(Integer id, Error *err)
FUNC_API_SINCE(9)
{
- char *name = augroup_name((int)id);
- augroup_del(name, false);
+ TRY_WRAP({
+ try_start();
+ char *name = augroup_name((int)id);
+ augroup_del(name, false);
+ try_end(err);
+ });
}
-/// Delete an augroup by {name}.
-///
-/// NOTE: behavior differs from augroup-delete.
+/// Delete an autocommand group by name.
///
-/// When deleting an augroup, autocmds contained by this augroup will also be deleted and cleared.
-/// This augroup will no longer exist
-void nvim_del_augroup_by_name(String name)
+/// NOTE: behavior differs from |augroup-delete|. When deleting a group, autocommands contained in
+/// this group will also be deleted and cleared. This group will no longer exist.
+/// @param name String The name of the group.
+/// @see |autocommand-groups|
+void nvim_del_augroup_by_name(String name, Error *err)
FUNC_API_SINCE(9)
{
- augroup_del(name.data, false);
+ TRY_WRAP({
+ try_start();
+ augroup_del(name.data, false);
+ try_end(err);
+ });
}
-/// Do one autocmd.
-///
-/// @param event The event or events to execute
-/// @param opts Optional Parameters:
-/// - buffer (number) - buffer number
-/// - NOTE: Cannot be used with {pattern}
-/// - pattern (string|table) - optional, defaults to "*".
-/// - NOTE: Cannot be used with {buffer}
-/// - group (string|int) - autocmd group name or id
-/// - modeline (boolean) - Default true, see |<nomodeline>|
-void nvim_do_autocmd(Object event, Dict(do_autocmd) *opts, Error *err)
+/// Execute all autocommands for {event} that match the corresponding
+/// {opts} |autocmd-execute|.
+/// @param event (String|Array) The event or events to execute
+/// @param opts Dictionary of autocommand options:
+/// - group (string|integer) optional: the autocommand group name or
+/// id to match against. |autocmd-groups|.
+/// - pattern (string|array) optional: defaults to "*" |autocmd-pattern|. Cannot be used
+/// with {buffer}.
+/// - buffer (integer) optional: buffer number |autocmd-buflocal|. Cannot be used with
+/// {pattern}.
+/// - modeline (bool) optional: defaults to true. Process the
+/// modeline after the autocommands |<nomodeline>|.
+/// @see |:doautocmd|
+void nvim_exec_autocmds(Object event, Dict(exec_autocmds) *opts, Error *err)
FUNC_API_SINCE(9)
{
int au_group = AUGROUP_ALL;
@@ -654,7 +738,7 @@ void nvim_do_autocmd(Object event, Dict(do_autocmd) *opts, Error *err)
Array event_array = ARRAY_DICT_INIT;
- if (!unpack_string_or_array(&event_array, &event, "event", err)) {
+ if (!unpack_string_or_array(&event_array, &event, "event", true, err)) {
goto cleanup;
}
@@ -753,7 +837,7 @@ static bool check_autocmd_string_array(Array arr, char *k, Error *err)
return true;
}
-static bool unpack_string_or_array(Array *array, Object *v, char *k, Error *err)
+static bool unpack_string_or_array(Array *array, Object *v, char *k, bool required, Error *err)
{
if (v->type == kObjectTypeString) {
ADD(*array, copy_object(*v));
@@ -763,10 +847,119 @@ static bool unpack_string_or_array(Array *array, Object *v, char *k, Error *err)
}
*array = copy_array(v->data.array);
} else {
- api_set_error(err,
- kErrorTypeValidation,
- "'%s' must be an array or a string.",
- k);
+ if (required) {
+ api_set_error(err,
+ kErrorTypeValidation,
+ "'%s' must be an array or a string.",
+ k);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+// Returns AUGROUP_ERROR if there was a problem with {group}
+static int get_augroup_from_object(Object group, Error *err)
+{
+ int au_group = AUGROUP_ERROR;
+
+ switch (group.type) {
+ case kObjectTypeNil:
+ return AUGROUP_DEFAULT;
+ case kObjectTypeString:
+ au_group = augroup_find(group.data.string.data);
+ if (au_group == AUGROUP_ERROR) {
+ api_set_error(err,
+ kErrorTypeValidation,
+ "invalid augroup: %s", group.data.string.data);
+
+ return AUGROUP_ERROR;
+ }
+
+ return au_group;
+ case kObjectTypeInteger:
+ au_group = (int)group.data.integer;
+ char *name = augroup_name(au_group);
+ if (!augroup_exists(name)) {
+ api_set_error(err, kErrorTypeValidation, "invalid augroup: %d", au_group);
+ return AUGROUP_ERROR;
+ }
+
+ return au_group;
+ default:
+ api_set_error(err, kErrorTypeValidation, "'group' must be a string or an integer.");
+ return AUGROUP_ERROR;
+ }
+}
+
+static bool get_patterns_from_pattern_or_buf(Array *patterns, Object pattern, Object buffer,
+ Error *err)
+{
+ const char_u pattern_buflocal[BUFLOCAL_PAT_LEN];
+
+ if (pattern.type != kObjectTypeNil && buffer.type != kObjectTypeNil) {
+ api_set_error(err, kErrorTypeValidation,
+ "cannot pass both: 'pattern' and 'buffer' for the same autocmd");
+ return false;
+ } else if (pattern.type != kObjectTypeNil) {
+ Object *v = &pattern;
+
+ if (v->type == kObjectTypeString) {
+ char_u *pat = (char_u *)v->data.string.data;
+ size_t patlen = aucmd_pattern_length(pat);
+ while (patlen) {
+ ADD(*patterns, STRING_OBJ(cbuf_to_string((char *)pat, patlen)));
+
+ pat = aucmd_next_pattern(pat, patlen);
+ patlen = aucmd_pattern_length(pat);
+ }
+ } else if (v->type == kObjectTypeArray) {
+ if (!check_autocmd_string_array(*patterns, "pattern", err)) {
+ return false;
+ }
+
+ Array array = v->data.array;
+ for (size_t i = 0; i < array.size; i++) {
+ char_u *pat = (char_u *)array.items[i].data.string.data;
+ size_t patlen = aucmd_pattern_length(pat);
+ while (patlen) {
+ ADD(*patterns, STRING_OBJ(cbuf_to_string((char *)pat, patlen)));
+
+ pat = aucmd_next_pattern(pat, patlen);
+ patlen = aucmd_pattern_length(pat);
+ }
+ }
+ } else {
+ api_set_error(err,
+ kErrorTypeValidation,
+ "'pattern' must be a string");
+ return false;
+ }
+ } else if (buffer.type != kObjectTypeNil) {
+ if (buffer.type != kObjectTypeInteger) {
+ api_set_error(err,
+ kErrorTypeValidation,
+ "'buffer' must be an integer");
+ return false;
+ }
+
+ buf_T *buf = find_buffer_by_handle((Buffer)buffer.data.integer, err);
+ if (ERROR_SET(err)) {
+ return false;
+ }
+
+ snprintf((char *)pattern_buflocal, BUFLOCAL_PAT_LEN, "<buffer=%d>", (int)buf->handle);
+ ADD(*patterns, STRING_OBJ(cstr_to_string((char *)pattern_buflocal)));
+ }
+
+ return true;
+}
+
+static bool clear_autocmd(event_T event, char_u *pat, int au_group, Error *err)
+{
+ if (do_autocmd_event(event, pat, false, false, (char_u *)"", true, au_group) == FAIL) {
+ api_set_error(err, kErrorTypeException, "Failed to clear autocmd");
return false;
}
diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c
index 797b64e2af..8dca37a321 100644
--- a/src/nvim/api/extmark.c
+++ b/src/nvim/api/extmark.c
@@ -259,9 +259,9 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id,
/// local pos = a.nvim_win_get_cursor(0)
/// local ns = a.nvim_create_namespace('my-plugin')
/// -- Create new extmark at line 1, column 1.
-/// local m1 = a.nvim_buf_set_extmark(0, ns, 0, 0, 0, {})
+/// local m1 = a.nvim_buf_set_extmark(0, ns, 0, 0, {})
/// -- Create new extmark at line 3, column 1.
-/// local m2 = a.nvim_buf_set_extmark(0, ns, 0, 2, 0, {})
+/// local m2 = a.nvim_buf_set_extmark(0, ns, 0, 2, {})
/// -- Get extmarks only from line 3.
/// local ms = a.nvim_buf_get_extmarks(0, ns, {2,0}, {2,0}, {})
/// -- Get all marks in this buffer + namespace.
diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua
index b6264cdfab..8ad4dae928 100644
--- a/src/nvim/api/keysets.lua
+++ b/src/nvim/api/keysets.lua
@@ -123,17 +123,23 @@ return {
"nocombine";
};
-- Autocmds
+ clear_autocmds = {
+ "buffer";
+ "event";
+ "group";
+ "pattern";
+ };
create_autocmd = {
"buffer";
"callback";
"command";
"desc";
"group";
- "once";
"nested";
+ "once";
"pattern";
};
- do_autocmd = {
+ exec_autocmds = {
"buffer";
"group";
"modeline";
diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h
index bc7c2e6a60..650349cde7 100644
--- a/src/nvim/api/private/helpers.h
+++ b/src/nvim/api/private/helpers.h
@@ -140,8 +140,9 @@ typedef struct {
// Useful macro for executing some `code` for each item in an array.
#define FOREACH_ITEM(a, __foreach_item, code) \
- for (size_t __foreach_i = 0; __foreach_i < (a).size; __foreach_i++) { \
- Object __foreach_item = (a).items[__foreach_i]; \
+ for (size_t (__foreach_item ## _index) = 0; (__foreach_item ## _index) < (a).size; \
+ (__foreach_item ## _index)++) { \
+ Object __foreach_item = (a).items[__foreach_item ## _index]; \
code; \
}
diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c
index a36f2c97b5..c0a22d058c 100644
--- a/src/nvim/autocmd.c
+++ b/src/nvim/autocmd.c
@@ -2022,6 +2022,11 @@ char_u *getnextac(int c, void *cookie, int indent, bool do_concat)
verbose_leave_scroll();
}
+ // Make sure to set autocmd_nested before executing
+ // lua code, so that it works properly
+ autocmd_nested = ac->nested;
+ current_sctx = ac->script_ctx;
+
if (ac->exec.type == CALLABLE_CB) {
typval_T argsin = TV_INITIAL_VALUE;
typval_T rettv = TV_INITIAL_VALUE;
@@ -2052,8 +2057,6 @@ char_u *getnextac(int c, void *cookie, int indent, bool do_concat)
if (oneshot) {
aucmd_del(ac);
}
- autocmd_nested = ac->nested;
- current_sctx = ac->script_ctx;
if (ac->last) {
acp->nextcmd = NULL;
} else {
@@ -2347,17 +2350,20 @@ int autocmd_delete_event(int group, event_T event, char_u *pat)
/// Deletes an autocmd by ID.
/// Only autocmds created via the API have IDs associated with them. There
/// is no way to delete a specific autocmd created via :autocmd
-void autocmd_delete_id(int64_t id)
+bool autocmd_delete_id(int64_t id)
{
+ assert(id > 0);
FOR_ALL_AUEVENTS(event) {
FOR_ALL_AUPATS_IN_EVENT(event, ap) {
for (AutoCmd *ac = ap->cmds; ac != NULL; ac = ac->next) {
if (ac->id == id) {
aucmd_del(ac);
+ return true;
}
}
}
}
+ return false;
}
// ===========================================================================
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index f200f16a5f..bf592a626d 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -590,6 +590,10 @@ bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last)
// Remove the buffer from the list.
if (wipe_buf) {
+ // Do not wipe out the buffer if it is used in a window.
+ if (buf->b_nwindows > 0) {
+ return false;
+ }
if (buf->b_sfname != buf->b_ffname) {
XFREE_CLEAR(buf->b_sfname);
} else {
@@ -1284,8 +1288,10 @@ int do_buffer(int action, int start, int dir, int count, int forceit)
while (jumpidx != curwin->w_jumplistidx) {
buf = buflist_findnr(curwin->w_jumplist[jumpidx].fmark.fnum);
if (buf != NULL) {
- if (buf == curbuf || !buf->b_p_bl) {
- buf = NULL; // skip current and unlisted bufs
+ // Skip current and unlisted bufs. Also skip a quickfix
+ // buffer, it might be deleted soon.
+ if (buf == curbuf || !buf->b_p_bl || bt_quickfix(buf)) {
+ buf = NULL;
} else if (buf->b_ml.ml_mfp == NULL) {
// skip unloaded buf, but may keep it for later
if (bp == NULL) {
@@ -1323,7 +1329,7 @@ int do_buffer(int action, int start, int dir, int count, int forceit)
continue;
}
// in non-help buffer, try to skip help buffers, and vv
- if (buf->b_help == curbuf->b_help && buf->b_p_bl) {
+ if (buf->b_help == curbuf->b_help && buf->b_p_bl && !bt_quickfix(buf)) {
if (buf->b_ml.ml_mfp != NULL) { // found loaded buffer
break;
}
@@ -1343,7 +1349,7 @@ int do_buffer(int action, int start, int dir, int count, int forceit)
}
if (buf == NULL) { // No loaded buffer, find listed one
FOR_ALL_BUFFERS(buf2) {
- if (buf2->b_p_bl && buf2 != curbuf) {
+ if (buf2->b_p_bl && buf2 != curbuf && !bt_quickfix(buf2)) {
buf = buf2;
break;
}
@@ -1355,6 +1361,9 @@ int do_buffer(int action, int start, int dir, int count, int forceit)
} else {
buf = curbuf->b_prev;
}
+ if (bt_quickfix(buf)) {
+ buf = NULL;
+ }
}
}
@@ -1486,8 +1495,15 @@ void set_curbuf(buf_T *buf, int action)
// An autocommand may have deleted "buf", already entered it (e.g., when
// it did ":bunload") or aborted the script processing!
// If curwin->w_buffer is null, enter_buffer() will make it valid again
- if ((buf_valid(buf) && buf != curbuf && !aborting()) || curwin->w_buffer == NULL) {
- enter_buffer(buf);
+ bool valid = buf_valid(buf);
+ if ((valid && buf != curbuf && !aborting()) || curwin->w_buffer == NULL) {
+ // If the buffer is not valid but curwin->w_buffer is NULL we must
+ // enter some buffer. Using the last one is hopefully OK.
+ if (!valid) {
+ enter_buffer(lastbuf);
+ } else {
+ enter_buffer(buf);
+ }
if (old_tw != curbuf->b_p_tw) {
check_colorcolumn(curwin);
}
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index 29413281ad..08ca1a6247 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -1211,6 +1211,8 @@ struct window_S {
colnr_T w_old_visual_col; ///< last known start of visual part
colnr_T w_old_curswant; ///< last known value of Curswant
+ linenr_T w_last_cursor_lnum_rnu; ///< cursor lnum when 'rnu' was last redrawn
+
// 'listchars' characters. Defaults set in set_chars_option().
struct {
int eol;
diff --git a/src/nvim/change.c b/src/nvim/change.c
index 6c3dbf72e4..0644b1d601 100644
--- a/src/nvim/change.c
+++ b/src/nvim/change.c
@@ -287,7 +287,7 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, long xtra
}
// Relative numbering may require updating more.
- if (wp->w_p_rnu) {
+ if (wp->w_p_rnu && xtra != 0) {
redraw_later(wp, SOME_VALID);
}
diff --git a/src/nvim/diff.c b/src/nvim/diff.c
index 1b8a9f41e9..a6bbe40999 100644
--- a/src/nvim/diff.c
+++ b/src/nvim/diff.c
@@ -1536,7 +1536,7 @@ static void diff_read(int idx_orig, int idx_new, diffio_T *dio)
long off;
int i;
int notset = true; // block "*dp" not set yet
- diffhunk_T *hunk;
+ diffhunk_T *hunk = NULL; // init to avoid gcc warning
enum {
DIFF_ED,
DIFF_UNIFIED,
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index c087948810..815d57121b 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -1480,8 +1480,6 @@ bool edit(int cmdchar, bool startln, long count)
/// @param ready not busy with something
static void ins_redraw(bool ready)
{
- bool conceal_cursor_moved = false;
-
if (char_avail()) {
return;
}
@@ -1504,7 +1502,6 @@ static void ins_redraw(bool ready)
update_curswant();
ins_apply_autocmds(EVENT_CURSORMOVEDI);
}
- conceal_cursor_moved = true;
curwin->w_last_cursormoved = curwin->w_cursor;
}
@@ -1560,11 +1557,6 @@ static void ins_redraw(bool ready)
curbuf->b_changed_invalid = false;
}
- if (curwin->w_p_cole > 0 && conceal_cursor_line(curwin)
- && conceal_cursor_moved) {
- redrawWinline(curwin, curwin->w_cursor.lnum);
- }
-
pum_check_clear();
if (must_redraw) {
update_screen(0);
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index fbbc543893..6c72c2866e 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -9217,6 +9217,8 @@ dictitem_T *find_var_in_ht(hashtab_T *const ht, int htname, const char *const va
/// Finds the dict (g:, l:, s:, …) and hashtable used for a variable.
///
+/// Assigns SID if s: scope is accessed from Lua or anonymous Vimscript. #15994
+///
/// @param[in] name Variable name, possibly with scope prefix.
/// @param[in] name_len Variable name length.
/// @param[out] varname Will be set to the start of the name without scope
@@ -9304,6 +9306,7 @@ static hashtab_T *find_var_ht_dict(const char *name, const size_t name_len, cons
}
}
if (current_sctx.sc_sid == SID_STR || current_sctx.sc_sid == SID_LUA) {
+ // Create SID if s: scope is accessed from Lua or anon Vimscript. #15994
new_script_item(NULL, &current_sctx.sc_sid);
}
*d = &SCRIPT_SV(current_sctx.sc_sid)->sv_dict;
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 41b419c150..ae9cb3b1e8 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -9466,6 +9466,11 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero)
res = ITEM_COMPARE_FAIL;
} else {
res = tv_get_number_chk(&rettv, &sortinfo->item_compare_func_err);
+ if (res > 0) {
+ res = 1;
+ } else if (res < 0) {
+ res = -1;
+ }
}
if (sortinfo->item_compare_func_err) {
res = ITEM_COMPARE_FAIL; // return value has wrong type
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index 0dbc9d6b14..20325509c4 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -6839,7 +6839,7 @@ void tabpage_close_other(tabpage_T *tp, int forceit)
// Autocommands may delete the tab page under our fingers and we may
// fail to close a window with a modified buffer.
- if (!valid_tabpage(tp) || tp->tp_firstwin == wp) {
+ if (!valid_tabpage(tp) || tp->tp_lastwin == wp) {
break;
}
}
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 84fca137d2..1d02c74c41 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -192,13 +192,12 @@ typedef struct command_line_state {
typedef struct cmdline_info CmdlineInfo;
-/* The current cmdline_info. It is initialized in getcmdline() and after that
- * used by other functions. When invoking getcmdline() recursively it needs
- * to be saved with save_cmdline() and restored with restore_cmdline().
- * TODO: make it local to getcmdline() and pass it around. */
+/// The current cmdline_info. It is initialized in getcmdline() and after that
+/// used by other functions. When invoking getcmdline() recursively it needs
+/// to be saved with save_cmdline() and restored with restore_cmdline().
static struct cmdline_info ccline;
-static int cmd_showtail; // Only show path tail in lists ?
+static int cmd_showtail; // Only show path tail in lists ?
static int new_cmdpos; // position set by set_cmdline_pos()
@@ -732,9 +731,10 @@ static void finish_incsearch_highlighting(int gotesc, incsearch_state_T *s, bool
/// Internal entry point for cmdline mode.
///
-/// caller must use save_cmdline and restore_cmdline. Best is to use
-/// getcmdline or getcmdline_prompt, instead of calling this directly.
-static uint8_t *command_line_enter(int firstc, long count, int indent)
+/// @param count only used for incremental search
+/// @param indent indent for inside conditionals
+/// @param init_ccline clear ccline first
+static uint8_t *command_line_enter(int firstc, long count, int indent, bool init_ccline)
{
// can be invoked recursively, identify each level
static int cmdline_level = 0;
@@ -751,6 +751,20 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)
CommandLineState *s = &state;
s->save_p_icm = vim_strsave(p_icm);
init_incsearch_state(&s->is_state);
+ CmdlineInfo save_ccline;
+ bool did_save_ccline = false;
+
+ if (ccline.cmdbuff != NULL) {
+ // Currently ccline can never be in use if init_ccline is false.
+ // Some changes will be needed if this is no longer the case.
+ assert(init_ccline);
+ // Being called recursively. Since ccline is global, we need to save
+ // the current buffer and restore it when returning.
+ save_cmdline(&save_ccline);
+ did_save_ccline = true;
+ } else if (init_ccline) {
+ memset(&ccline, 0, sizeof(struct cmdline_info));
+ }
if (s->firstc == -1) {
s->firstc = NUL;
@@ -997,6 +1011,13 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)
}
cmdline_level--;
+
+ if (did_save_ccline) {
+ restore_cmdline(&save_ccline);
+ } else {
+ ccline.cmdbuff = NULL;
+ }
+
return p;
}
@@ -1340,15 +1361,9 @@ static int command_line_execute(VimState *state, int key)
s->c = get_expr_register();
if (s->c == '=') {
- // Need to save and restore ccline. And set "textlock"
- // to avoid nasty things like going to another buffer when
- // evaluating an expression.
- CmdlineInfo save_ccline;
- save_cmdline(&save_ccline);
textlock++;
p = get_expr_line();
textlock--;
- restore_cmdline(&save_ccline);
if (p != NULL) {
len = (int)STRLEN(p);
@@ -1889,10 +1904,7 @@ static int command_line_handle_key(CommandLineState *s)
beep_flush();
s->c = ESC;
} else {
- CmdlineInfo save_ccline;
- save_cmdline(&save_ccline);
s->c = get_expr_register();
- restore_cmdline(&save_ccline);
}
}
@@ -2120,7 +2132,7 @@ static int command_line_handle_key(CommandLineState *s)
int len = 0;
int old_firstc;
- xfree(ccline.cmdbuff);
+ XFREE_CLEAR(ccline.cmdbuff);
s->xpc.xp_context = EXPAND_NOTHING;
if (s->hiscnt == hislen) {
p = s->lookfor; // back to the old one
@@ -2403,14 +2415,7 @@ static void abandon_cmdline(void)
/// @param indent indent for inside conditionals
char_u *getcmdline(int firstc, long count, int indent, bool do_concat FUNC_ATTR_UNUSED)
{
- // Be prepared for situations where cmdline can be invoked recursively.
- // That includes cmd mappings, event handlers, as well as update_screen()
- // (custom status line eval), which all may invoke ":normal :".
- CmdlineInfo save_ccline;
- save_cmdline(&save_ccline);
- char_u *retval = command_line_enter(firstc, count, indent);
- restore_cmdline(&save_ccline);
- return retval;
+ return command_line_enter(firstc, count, indent, true);
}
/// Get a command line with a prompt
@@ -2434,8 +2439,14 @@ char *getcmdline_prompt(const char firstc, const char *const prompt, const int a
const int msg_col_save = msg_col;
CmdlineInfo save_ccline;
- save_cmdline(&save_ccline);
-
+ bool did_save_ccline = false;
+ if (ccline.cmdbuff != NULL) {
+ // Save the values of the current cmdline and restore them below.
+ save_cmdline(&save_ccline);
+ did_save_ccline = true;
+ } else {
+ memset(&ccline, 0, sizeof(struct cmdline_info));
+ }
ccline.prompt_id = last_prompt_id++;
ccline.cmdprompt = (char_u *)prompt;
ccline.cmdattr = attr;
@@ -2447,9 +2458,11 @@ char *getcmdline_prompt(const char firstc, const char *const prompt, const int a
int msg_silent_saved = msg_silent;
msg_silent = 0;
- char *const ret = (char *)command_line_enter(firstc, 1L, 0);
+ char *const ret = (char *)command_line_enter(firstc, 1L, 0, false);
- restore_cmdline(&save_ccline);
+ if (did_save_ccline) {
+ restore_cmdline(&save_ccline);
+ }
msg_silent = msg_silent_saved;
// Restore msg_col, the prompt from input() may have changed it.
// But only if called recursively and the commandline is therefore being
@@ -2601,7 +2614,6 @@ bool cmdline_at_end(void)
/*
* Allocate a new command line buffer.
* Assigns the new buffer to ccline.cmdbuff and ccline.cmdbufflen.
- * Returns the new value of ccline.cmdbuff and ccline.cmdbufflen.
*/
static void alloc_cmdbuff(int len)
{
@@ -3367,53 +3379,23 @@ void put_on_cmdline(char_u *str, int len, int redraw)
}
}
-/*
- * Save ccline, because obtaining the "=" register may execute "normal :cmd"
- * and overwrite it. But get_cmdline_str() may need it, thus make it
- * available globally in prev_ccline.
- */
+/// Save ccline, because obtaining the "=" register may execute "normal :cmd"
+/// and overwrite it.
static void save_cmdline(struct cmdline_info *ccp)
{
*ccp = ccline;
+ memset(&ccline, 0, sizeof(struct cmdline_info));
ccline.prev_ccline = ccp;
- ccline.cmdbuff = NULL;
- ccline.cmdprompt = NULL;
- ccline.xpc = NULL;
- ccline.special_char = NUL;
- ccline.level = 0;
+ ccline.cmdbuff = NULL; // signal that ccline is not in use
}
-/*
- * Restore ccline after it has been saved with save_cmdline().
- */
+/// Restore ccline after it has been saved with save_cmdline().
static void restore_cmdline(struct cmdline_info *ccp)
FUNC_ATTR_NONNULL_ALL
{
ccline = *ccp;
}
-/*
- * Save the command line into allocated memory. Returns a pointer to be
- * passed to restore_cmdline_alloc() later.
- */
-char_u *save_cmdline_alloc(void)
- FUNC_ATTR_NONNULL_RET
-{
- struct cmdline_info *p = xmalloc(sizeof(struct cmdline_info));
- save_cmdline(p);
- return (char_u *)p;
-}
-
-/*
- * Restore the command line from the return value of save_cmdline_alloc().
- */
-void restore_cmdline_alloc(char_u *p)
- FUNC_ATTR_NONNULL_ALL
-{
- restore_cmdline((struct cmdline_info *)p);
- xfree(p);
-}
-
/// Paste a yank register into the command line.
/// Used by CTRL-R command in command-line mode.
/// insert_reg() can't be used here, because special characters from the
@@ -3429,7 +3411,6 @@ static bool cmdline_paste(int regname, bool literally, bool remcr)
char_u *arg;
char_u *p;
bool allocated;
- struct cmdline_info save_ccline;
// check for valid regname; also accept special characters for CTRL-R in
// the command line
@@ -3447,13 +3428,11 @@ static bool cmdline_paste(int regname, bool literally, bool remcr)
}
- // Need to save and restore ccline. And set "textlock" to avoid nasty
- // things like going to another buffer when evaluating an expression.
- save_cmdline(&save_ccline);
+ // Need to set "textlock" to avoid nasty things like going to another
+ // buffer when evaluating an expression.
textlock++;
const bool i = get_spec_reg(regname, &arg, &allocated, true);
textlock--;
- restore_cmdline(&save_ccline);
if (i) {
// Got the value of a special register in "arg".
@@ -5307,7 +5286,6 @@ static void *call_user_expand_func(user_expand_func_T user_expand_func, expand_T
typval_T args[4];
char_u *pat = NULL;
const sctx_T save_current_sctx = current_sctx;
- struct cmdline_info save_ccline;
if (xp->xp_arg == NULL || xp->xp_arg[0] == '\0' || xp->xp_line == NULL) {
return NULL;
@@ -5329,15 +5307,10 @@ static void *call_user_expand_func(user_expand_func_T user_expand_func, expand_T
args[1].vval.v_string = xp->xp_line;
args[2].vval.v_number = xp->xp_col;
- // Save the cmdline, we don't know what the function may do.
- save_ccline = ccline;
- ccline.cmdbuff = NULL;
- ccline.cmdprompt = NULL;
current_sctx = xp->xp_script_ctx;
void *const ret = user_expand_func(xp->xp_arg, 3, args);
- ccline = save_ccline;
current_sctx = save_current_sctx;
if (ccline.cmdbuff != NULL) {
ccline.cmdbuff[ccline.cmdlen] = keep;
@@ -5947,10 +5920,8 @@ int get_history_idx(int histype)
}
-/*
- * Get pointer to the command line info to use. cmdline_paste() may clear
- * ccline and put the previous value in prev_ccline.
- */
+/// Get pointer to the command line info to use. save_cmdline() may clear
+/// ccline and put the previous value in ccline.prev_ccline.
static struct cmdline_info *get_ccline_ptr(void)
{
if ((State & CMDLINE) == 0) {
@@ -6350,6 +6321,11 @@ int hist_type2char(int type)
return NUL;
}
+void cmdline_init(void)
+{
+ memset(&ccline, 0, sizeof(struct cmdline_info));
+}
+
/// Open a window on the current command line and history. Allow editing in
/// the window. Returns when the window is closed.
/// Returns:
@@ -6358,7 +6334,6 @@ int hist_type2char(int type)
/// K_IGNORE if editing continues
static int open_cmdwin(void)
{
- struct cmdline_info save_ccline;
bufref_T old_curbuf;
bufref_T bufref;
win_T *old_curwin = curwin;
@@ -6459,9 +6434,6 @@ static int open_cmdwin(void)
}
redraw_later(curwin, SOME_VALID);
- // Save the command line info, can be used recursively.
- save_cmdline(&save_ccline);
-
// No Ex mode here!
exmode_active = false;
@@ -6499,8 +6471,6 @@ static int open_cmdwin(void)
// Restore KeyTyped in case it is modified by autocommands
KeyTyped = save_KeyTyped;
- // Restore the command line info.
- restore_cmdline(&save_ccline);
cmdwin_type = 0;
cmdwin_level = 0;
diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c
index 39ff75d8c2..1235087500 100644
--- a/src/nvim/ex_session.c
+++ b/src/nvim/ex_session.c
@@ -589,12 +589,18 @@ static int makeopens(FILE *fd, char_u *dirnow)
"if expand('%') == '' && !&modified && line('$') <= 1"
" && getline(1) == ''\n"
" let s:wipebuf = bufnr('%')\n"
- "endif\n"
- // Now save the current files, current buffer first.
- "set shortmess=aoO\n") < 0) {
+ "endif\n") < 0) {
return FAIL;
}
+ // save 'shortmess' if not storing options
+ if ((ssop_flags & SSOP_OPTIONS) == 0) {
+ PUTLINE_FAIL("let s:shortmess_save = &shortmess");
+ }
+
+ // Now save the current files, current buffer first.
+ PUTLINE_FAIL("set shortmess=aoO");
+
// Put all buffers into the buffer list.
// Do it very early to preserve buffer order after loading session (which
// can be disrupted by prior `edit` or `tabedit` calls).
@@ -842,15 +848,21 @@ static int makeopens(FILE *fd, char_u *dirnow)
return FAIL;
}
- // Re-apply 'winheight', 'winwidth' and 'shortmess'.
- if (fprintf(fd,
- "set winheight=%" PRId64 " winwidth=%" PRId64
- " shortmess=%s\n",
- (int64_t)p_wh,
- (int64_t)p_wiw,
- p_shm) < 0) {
+ // Re-apply 'winheight' and 'winwidth'.
+ if (fprintf(fd, "set winheight=%" PRId64 " winwidth=%" PRId64 "\n",
+ (int64_t)p_wh, (int64_t)p_wiw) < 0) {
return FAIL;
}
+
+ // Restore 'shortmess'.
+ if (ssop_flags & SSOP_OPTIONS) {
+ if (fprintf(fd, "set shortmess=%s\n", p_shm) < 0) {
+ return FAIL;
+ }
+ } else {
+ PUTLINE_FAIL("let &shortmess = s:shortmess_save");
+ }
+
if (tab_firstwin != NULL && tab_firstwin->w_next != NULL) {
// Restore 'winminheight' and 'winminwidth'.
PUTLINE_FAIL("let &winminheight = s:save_winminheight");
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index fe61a2fc90..7905b29876 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -361,7 +361,7 @@ int readfile(char_u *fname, char_u *sfname, linenr_T from, linenr_T lines_to_ski
filemess(curbuf, fname, (char_u *)_(msg_is_a_directory), 0);
msg_end();
msg_scroll = msg_save;
- return FAIL;
+ return NOTDONE;
}
}
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
index c10172cc52..b12b99b7ee 100644
--- a/src/nvim/getchar.c
+++ b/src/nvim/getchar.c
@@ -4001,7 +4001,6 @@ static char_u *eval_map_expr(mapblock_T *mp, int c)
char_u *res;
char_u *p = NULL;
char_u *expr = NULL;
- char_u *save_cmd;
pos_T save_cursor;
int save_msg_col;
int save_msg_row;
@@ -4013,8 +4012,6 @@ static char_u *eval_map_expr(mapblock_T *mp, int c)
vim_unescape_ks(expr);
}
- save_cmd = save_cmdline_alloc();
-
// Forbid changing text or using ":normal" to avoid most of the bad side
// effects. Also restore the cursor position.
textlock++;
@@ -4045,8 +4042,6 @@ static char_u *eval_map_expr(mapblock_T *mp, int c)
msg_col = save_msg_col;
msg_row = save_msg_row;
- restore_cmdline_alloc(save_cmd);
-
if (p == NULL) {
return NULL;
}
diff --git a/src/nvim/main.c b/src/nvim/main.c
index afb9313cba..6ea1cb0875 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -20,6 +20,7 @@
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds2.h"
#include "nvim/ex_docmd.h"
+#include "nvim/ex_getln.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
#include "nvim/getchar.h"
@@ -156,6 +157,7 @@ bool event_teardown(void)
void early_init(mparm_T *paramp)
{
env_init();
+ cmdline_init();
eval_init(); // init global variables
init_path(argv0 ? argv0 : "nvim");
init_normal_cmds(); // Init the table of Normal mode commands.
diff --git a/src/nvim/message.c b/src/nvim/message.c
index b3fefbc0f4..6708001495 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -327,11 +327,12 @@ bool msg_attr_keep(const char *s, int attr, bool keep, bool multiline)
}
retval = msg_end();
- if (keep && retval && vim_strsize((char_u *)s) < (Rows - cmdline_row - 1)
- * Columns + sc_col) {
+ if (keep && retval && vim_strsize((char_u *)s) < (Rows - cmdline_row - 1) * Columns + sc_col) {
set_keep_msg((char *)s, 0);
}
+ need_fileinfo = false;
+
xfree(buf);
--entered;
return retval;
@@ -382,6 +383,13 @@ void trunc_string(char_u *s, char_u *buf, int room_in, int buflen)
int i;
int n;
+ if (*s == NUL) {
+ if (buflen > 0) {
+ *buf = NUL;
+ }
+ return;
+ }
+
if (room_in < 3) {
room = 0;
}
@@ -1348,6 +1356,7 @@ void msg_start(void)
if (!msg_silent) {
XFREE_CLEAR(keep_msg); // don't display old message now
+ need_fileinfo = false;
}
if (need_clr_eos) {
@@ -1486,6 +1495,10 @@ int msg_outtrans_len_attr(const char_u *msgstr, int len, int attr)
char_u *s;
int mb_l;
int c;
+ int save_got_int = got_int;
+
+ // Only quit when got_int was set in here.
+ got_int = false;
// if MSG_HIST flag set, add message to history
if (attr & MSG_HIST) {
@@ -1503,7 +1516,7 @@ int msg_outtrans_len_attr(const char_u *msgstr, int len, int attr)
* Go over the string. Special characters are translated and printed.
* Normal characters are printed several at a time.
*/
- while (--len >= 0) {
+ while (--len >= 0 && !got_int) {
// Don't include composing chars after the end.
mb_l = utfc_ptr2len_len((char_u *)str, len + 1);
if (mb_l > 1) {
@@ -1542,11 +1555,13 @@ int msg_outtrans_len_attr(const char_u *msgstr, int len, int attr)
}
}
- if (str > plain_start) {
+ if (str > plain_start && !got_int) {
// Print the printable chars at the end.
msg_puts_attr_len(plain_start, str - plain_start, attr);
}
+ got_int |= save_got_int;
+
return retval;
}
@@ -2013,6 +2028,8 @@ void msg_puts_attr_len(const char *const str, const ptrdiff_t len, int attr)
if (!msg_use_printf() || (headless_mode && default_grid.chars)) {
msg_puts_display((const char_u *)str, len, attr, false);
}
+
+ need_fileinfo = false;
}
/// Print a formatted message
diff --git a/src/nvim/move.c b/src/nvim/move.c
index 751e0046bc..5e02e355bf 100644
--- a/src/nvim/move.c
+++ b/src/nvim/move.c
@@ -95,18 +95,41 @@ static void comp_botline(win_T *wp)
win_check_anchored_floats(wp);
}
-// Redraw when w_cline_row changes and 'relativenumber' or 'cursorline' is set.
+/// Redraw when w_cline_row changes and 'relativenumber' or 'cursorline' is set.
+/// Also when concealing is on and 'concealcursor' is not active.
void redraw_for_cursorline(win_T *wp)
FUNC_ATTR_NONNULL_ALL
{
- if ((wp->w_p_rnu || win_cursorline_standout(wp))
- && (wp->w_valid & VALID_CROW) == 0
- && !pum_visible()) {
+ if ((wp->w_valid & VALID_CROW) == 0 && !pum_visible()
+ && (wp->w_p_rnu || win_cursorline_standout(wp))) {
// win_line() will redraw the number column and cursorline only.
redraw_later(wp, VALID);
}
}
+/// Redraw when w_virtcol changes and 'cursorcolumn' is set or 'cursorlineopt'
+/// contains "screenline".
+/// Also when concealing is on and 'concealcursor' is active.
+static void redraw_for_cursorcolumn(win_T *wp)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if ((wp->w_valid & VALID_VIRTCOL) == 0 && !pum_visible()) {
+ if (wp->w_p_cuc) {
+ // When 'cursorcolumn' is set need to redraw with SOME_VALID.
+ redraw_later(wp, SOME_VALID);
+ } else if (wp->w_p_cul && (wp->w_p_culopt_flags & CULOPT_SCRLINE)) {
+ // When 'cursorlineopt' contains "screenline" need to redraw with VALID.
+ redraw_later(wp, VALID);
+ }
+ }
+ // If the cursor moves horizontally when 'concealcursor' is active, then the
+ // current line needs to be redrawn in order to calculate the correct
+ // cursor position.
+ if ((wp->w_valid & VALID_VIRTCOL) == 0 && wp->w_p_cole > 0 && conceal_cursor_line(wp)) {
+ redrawWinline(wp, wp->w_cursor.lnum);
+ }
+}
+
/*
* Update curwin->w_topline and redraw if necessary.
* Used to update the screen before printing a message.
@@ -623,11 +646,8 @@ void validate_virtcol_win(win_T *wp)
check_cursor_moved(wp);
if (!(wp->w_valid & VALID_VIRTCOL)) {
getvvcol(wp, &wp->w_cursor, NULL, &(wp->w_virtcol), NULL);
+ redraw_for_cursorcolumn(wp);
wp->w_valid |= VALID_VIRTCOL;
- if (wp->w_p_cuc
- && !pum_visible()) {
- redraw_later(wp, SOME_VALID);
- }
}
}
@@ -930,11 +950,7 @@ void curs_columns(win_T *wp, int may_scroll)
redraw_later(wp, NOT_VALID);
}
- // Redraw when w_virtcol changes and 'cursorcolumn' is set
- if (wp->w_p_cuc && (wp->w_valid & VALID_VIRTCOL) == 0
- && !pum_visible()) {
- redraw_later(wp, SOME_VALID);
- }
+ redraw_for_cursorcolumn(curwin);
// now w_leftcol is valid, avoid check_cursor_moved() thinking otherwise
wp->w_valid_leftcol = wp->w_leftcol;
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index e773351d63..72e80952ff 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -1288,16 +1288,6 @@ static void normal_redraw(NormalState *s)
update_topline(curwin);
validate_cursor();
- // If the cursor moves horizontally when 'concealcursor' is active, then the
- // current line needs to be redrawn in order to calculate the correct
- // cursor position.
- if (curwin->w_p_cole > 0 && conceal_cursor_line(curwin)) {
- redrawWinline(curwin, curwin->w_cursor.lnum);
- }
-
- // Might need to update for 'cursorline'.
- check_redraw_cursorline();
-
if (VIsual_active) {
update_curbuf(INVERTED); // update inverted part
} else if (must_redraw) {
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index fc66fb5f06..dab6d237bf 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -3893,7 +3893,7 @@ void ex_display(exarg_T *eap)
msg_puts_attr("^J", attr);
n -= 2;
}
- for (p = yb->y_array[j]; *p && (n -= ptr2cells(p)) >= 0; p++) {
+ for (p = yb->y_array[j]; *p != NUL && (n -= ptr2cells(p)) >= 0; p++) { // -V1019
clen = utfc_ptr2len(p);
msg_outtrans_len(p, clen);
p += clen - 1;
diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h
index d88cd6b9b9..98c2b84770 100644
--- a/src/nvim/option_defs.h
+++ b/src/nvim/option_defs.h
@@ -523,11 +523,11 @@ EXTERN long p_mmd; // 'maxmapdepth'
EXTERN long p_mmp; // 'maxmempattern'
EXTERN long p_mis; // 'menuitems'
EXTERN char_u *p_msm; // 'mkspellmem'
-EXTERN long p_mle; // 'modelineexpr'
+EXTERN int p_mle; // 'modelineexpr'
EXTERN long p_mls; // 'modelines'
EXTERN char_u *p_mouse; // 'mouse'
EXTERN char_u *p_mousem; // 'mousemodel'
-EXTERN long p_mousef; // 'mousefocus'
+EXTERN int p_mousef; // 'mousefocus'
EXTERN long p_mouset; // 'mousetime'
EXTERN int p_more; // 'more'
EXTERN char_u *p_opfunc; // 'operatorfunc'
diff --git a/src/nvim/path.c b/src/nvim/path.c
index d3aa5e5bf2..75624778e3 100644
--- a/src/nvim/path.c
+++ b/src/nvim/path.c
@@ -1777,7 +1777,7 @@ int path_with_url(const char *fname)
}
// check body: alpha or dash
- for (p = fname; (isalpha(*p) || (*p == '-')); p++) {}
+ for (p = fname + 1; (isalpha(*p) || (*p == '-')); p++) {}
// check last char is not a dash
if (p[-1] == '-') {
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index 9ee7b1cc8a..c8d9e91fb5 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -2780,6 +2780,7 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, int
// present.
if (qfl_type == QFLT_LOCATION) {
win_T *wp = win_id2wp(prev_winid);
+
if (wp == NULL && curwin->w_llist != qi) {
emsg(_("E924: Current window was closed"));
*opened_window = false;
diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c
index 009a26d4e0..d3c43e530a 100644
--- a/src/nvim/regexp.c
+++ b/src/nvim/regexp.c
@@ -107,8 +107,10 @@ static char_u e_unmatchedpar[] = N_("E55: Unmatched %s)");
static char_u e_z_not_allowed[] = N_("E66: \\z( not allowed here");
static char_u e_z1_not_allowed[] = N_("E67: \\z1 - \\z9 not allowed here");
static char_u e_missing_sb[] = N_("E69: Missing ] after %s%%[");
-static char_u e_empty_sb[] = N_("E70: Empty %s%%[]");
-static char_u e_recursive[] = N_("E956: Cannot use pattern recursively");
+static char_u e_empty_sb[] = N_("E70: Empty %s%%[]");
+static char_u e_recursive[] = N_("E956: Cannot use pattern recursively");
+static char_u e_regexp_number_after_dot_pos_search[]
+ = N_("E1204: No Number allowed after .: '\\%%%c'");
#define NOT_MULTI 0
#define MULTI_ONE 1
diff --git a/src/nvim/regexp_bt.c b/src/nvim/regexp_bt.c
index 7340957903..7d0b9a7a77 100644
--- a/src/nvim/regexp_bt.c
+++ b/src/nvim/regexp_bt.c
@@ -1578,14 +1578,19 @@ static char_u *regatom(int *flagp)
}
default:
- if (ascii_isdigit(c) || c == '<' || c == '>'
- || c == '\'') {
+ if (ascii_isdigit(c) || c == '<' || c == '>' || c == '\'' || c == '.') {
uint32_t n = 0;
int cmp;
+ bool cur = false;
cmp = c;
- if (cmp == '<' || cmp == '>')
+ if (cmp == '<' || cmp == '>') {
c = getchr();
+ }
+ if (no_Magic(c) == '.') {
+ cur = true;
+ c = getchr();
+ }
while (ascii_isdigit(c)) {
n = n * 10 + (uint32_t)(c - '0');
c = getchr();
@@ -1602,14 +1607,31 @@ static char_u *regatom(int *flagp)
}
break;
} else if (c == 'l' || c == 'c' || c == 'v') {
+ if (cur && n) {
+ semsg(_(e_regexp_number_after_dot_pos_search), no_Magic(c));
+ rc_did_emsg = true;
+ return NULL;
+ }
if (c == 'l') {
+ if (cur) {
+ n = curwin->w_cursor.lnum;
+ }
ret = regnode(RE_LNUM);
if (save_prev_at_start) {
at_start = true;
}
} else if (c == 'c') {
+ if (cur) {
+ n = curwin->w_cursor.col;
+ n++;
+ }
ret = regnode(RE_COL);
} else {
+ if (cur) {
+ colnr_T vcol = 0;
+ getvvcol(curwin, &curwin->w_cursor, NULL, NULL, &vcol);
+ n = ++vcol;
+ }
ret = regnode(RE_VCOL);
}
if (ret == JUST_CALC_SIZE) {
@@ -3130,9 +3152,17 @@ static bool regmatch(
{
int mark = OPERAND(scan)[0];
int cmp = OPERAND(scan)[1];
- pos_T *pos;
+ pos_T *pos;
+ size_t col = REG_MULTI ? rex.input - rex.line : 0;
pos = getmark_buf(rex.reg_buf, mark, false);
+
+ // Line may have been freed, get it again.
+ if (REG_MULTI) {
+ rex.line = reg_getline(rex.lnum);
+ rex.input = rex.line + col;
+ }
+
if (pos == NULL // mark doesn't exist
|| pos->lnum <= 0) { // mark isn't set in reg_buf
status = RA_NOMATCH;
diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c
index 7e316624f8..a8d6e9c40b 100644
--- a/src/nvim/regexp_nfa.c
+++ b/src/nvim/regexp_nfa.c
@@ -1639,10 +1639,20 @@ static int nfa_regatom(void)
{
int64_t n = 0;
const int cmp = c;
+ bool cur = false;
- if (c == '<' || c == '>')
+ if (c == '<' || c == '>') {
c = getchr();
+ }
+ if (no_Magic(c) == '.') {
+ cur = true;
+ c = getchr();
+ }
while (ascii_isdigit(c)) {
+ if (cur) {
+ semsg(_(e_regexp_number_after_dot_pos_search), no_Magic(c));
+ return FAIL;
+ }
if (n > (INT32_MAX - (c - '0')) / 10) {
// overflow.
emsg(_(e_value_too_large));
@@ -1655,6 +1665,9 @@ static int nfa_regatom(void)
int32_t limit = INT32_MAX;
if (c == 'l') {
+ if (cur) {
+ n = curwin->w_cursor.lnum;
+ }
// \%{n}l \%{n}<l \%{n}>l
EMIT(cmp == '<' ? NFA_LNUM_LT :
cmp == '>' ? NFA_LNUM_GT : NFA_LNUM);
@@ -1662,10 +1675,19 @@ static int nfa_regatom(void)
at_start = true;
}
} else if (c == 'c') {
+ if (cur) {
+ n = curwin->w_cursor.col;
+ n++;
+ }
// \%{n}c \%{n}<c \%{n}>c
EMIT(cmp == '<' ? NFA_COL_LT :
cmp == '>' ? NFA_COL_GT : NFA_COL);
} else {
+ if (cur) {
+ colnr_T vcol = 0;
+ getvvcol(curwin, &curwin->w_cursor, NULL, NULL, &vcol);
+ n = ++vcol;
+ }
// \%{n}v \%{n}<v \%{n}>v
EMIT(cmp == '<' ? NFA_VCOL_LT :
cmp == '>' ? NFA_VCOL_GT : NFA_VCOL);
@@ -6227,8 +6249,10 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
case NFA_MARK_GT:
case NFA_MARK_LT:
{
- size_t col = rex.input - rex.line;
- pos_T *pos = getmark_buf(rex.reg_buf, t->state->val, false);
+ pos_T *pos;
+ size_t col = REG_MULTI ? rex.input - rex.line : 0;
+
+ pos = getmark_buf(rex.reg_buf, t->state->val, false);
// Line may have been freed, get it again.
if (REG_MULTI) {
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index 5a10543559..f922a50260 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -667,15 +667,11 @@ void conceal_check_cursor_line(void)
/// Whether cursorline is drawn in a special way
///
-/// If true, both old and new cursorline will need
-/// to be redrawn when moving cursor within windows.
-/// TODO(bfredl): VIsual_active shouldn't be needed, but is used to fix a glitch
-/// caused by scrolling.
+/// If true, both old and new cursorline will need to be redrawn when moving cursor within windows.
bool win_cursorline_standout(const win_T *wp)
FUNC_ATTR_NONNULL_ALL
{
- return wp->w_p_cul
- || (wp->w_p_cole > 0 && (VIsual_active || !conceal_cursor_line(wp)));
+ return wp->w_p_cul || (wp->w_p_cole > 0 && !conceal_cursor_line(wp));
}
/*
@@ -938,7 +934,7 @@ static void win_update(win_T *wp, DecorProviders *providers)
if (mod_top != 0
&& wp->w_topline == mod_top
&& (!wp->w_lines[0].wl_valid
- || wp->w_topline <= wp->w_lines[0].wl_lnum)) {
+ || wp->w_topline == wp->w_lines[0].wl_lnum)) {
// w_topline is the first changed line and window is not scrolled,
// the scrolling from changed lines will be done further down.
} else if (wp->w_lines[0].wl_valid
@@ -1577,9 +1573,9 @@ static void win_update(win_T *wp, DecorProviders *providers)
idx++;
lnum += foldinfo.fi_lines + 1;
} else {
- if (wp->w_p_rnu) {
- // 'relativenumber' set: The text doesn't need to be drawn, but
- // the number column nearly always does.
+ if (wp->w_p_rnu && wp->w_last_cursor_lnum_rnu != wp->w_cursor.lnum) {
+ // 'relativenumber' set and cursor moved vertically: The
+ // text doesn't need to be drawn, but the number column does.
foldinfo_T info = fold_info(wp, lnum);
(void)win_line(wp, lnum, srow, wp->w_grid.Rows, true, true,
info, &line_providers);
@@ -1607,6 +1603,8 @@ static void win_update(win_T *wp, DecorProviders *providers)
// update w_last_cursorline.
wp->w_last_cursorline = cursorline_standout ? wp->w_cursor.lnum : 0;
+ wp->w_last_cursor_lnum_rnu = wp->w_p_rnu ? wp->w_cursor.lnum : 0;
+
if (idx > wp->w_lines_valid) {
wp->w_lines_valid = idx;
}
@@ -4333,6 +4331,9 @@ static int draw_virt_text_item(buf_T *buf, int col, VirtText vt, HlMode hl_mode,
break;
}
}
+ if (!*s.p) {
+ continue;
+ }
int attr;
bool through = false;
if (hl_mode == kHlModeCombine) {
@@ -7617,16 +7618,3 @@ win_T *get_win_by_grid_handle(handle_T handle)
}
return NULL;
}
-
-/// Check if the cursor moved and 'cursorline' is set. Mark for a VALID redraw
-/// if needed.
-void check_redraw_cursorline(void)
-{
- // When 'cursorlineopt' is "screenline" need to redraw always.
- if (curwin->w_p_cul
- && (curwin->w_last_cursorline != curwin->w_cursor.lnum
- || (curwin->w_p_culopt_flags & CULOPT_SCRLINE))
- && !char_avail()) {
- redraw_later(curwin, VALID);
- }
-}
diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c
index d7b220b3f6..07058b34fd 100644
--- a/src/nvim/spellfile.c
+++ b/src/nvim/spellfile.c
@@ -5580,6 +5580,9 @@ void spell_add_word(char_u *word, int len, SpellAddType what, int idx, bool undo
while (!vim_fgets(line, MAXWLEN * 2, fd)) {
fpos = fpos_next;
fpos_next = ftell(fd);
+ if (fpos_next < 0) {
+ break; // should never happen
+ }
if (STRNCMP(word, line, len) == 0
&& (line[len] == '/' || line[len] < ' ')) {
// Found duplicate word. Remove it by writing a '#' at
diff --git a/src/nvim/state.c b/src/nvim/state.c
index f9a3aaab7f..3a7636085b 100644
--- a/src/nvim/state.c
+++ b/src/nvim/state.c
@@ -15,6 +15,7 @@
#include "nvim/option.h"
#include "nvim/option_defs.h"
#include "nvim/os/input.h"
+#include "nvim/screen.h"
#include "nvim/state.h"
#include "nvim/ui.h"
#include "nvim/vim.h"
@@ -54,6 +55,12 @@ getkey:
// Event was made available after the last multiqueue_process_events call
key = K_EVENT;
} else {
+ // Duplicate display updating logic in vgetorpeek()
+ if (((State & INSERT) != 0 || p_lz) && (State & CMDLINE) == 0
+ && must_redraw != 0 && !need_wait_return) {
+ update_screen(0);
+ setcursor(); // put cursor back where it belongs
+ }
// Flush screen updates before blocking
ui_flush();
// Call `os_inchar` directly to block for events or user input without
diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim
index c39546b9ea..76c69ad10b 100644
--- a/src/nvim/testdir/test_autocmd.vim
+++ b/src/nvim/testdir/test_autocmd.vim
@@ -2343,6 +2343,19 @@ func Test_throw_in_BufWritePre()
au! throwing
endfunc
+func Test_autocmd_in_try_block()
+ call mkdir('Xdir')
+ au BufEnter * let g:fname = expand('%')
+ try
+ edit Xdir/
+ endtry
+ call assert_match('Xdir', g:fname)
+
+ unlet g:fname
+ au! BufEnter
+ call delete('Xdir', 'rf')
+endfunc
+
func Test_autocmd_CmdWinEnter()
CheckRunVimInTerminal
" There is not cmdwin switch, so
@@ -2598,4 +2611,21 @@ func Test_autocmd_closing_cmdwin()
only
endfunc
+func Test_bufwipeout_changes_window()
+ " This should not crash, but we don't have any expectations about what
+ " happens, changing window in BufWipeout has unpredictable results.
+ tabedit
+ let g:window_id = win_getid()
+ topleft new
+ setlocal bufhidden=wipe
+ autocmd BufWipeout <buffer> call win_gotoid(g:window_id)
+ tabprevious
+ +tabclose
+
+ unlet g:window_id
+ au! BufWipeout
+ %bwipe!
+endfunc
+
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_cursorline.vim b/src/nvim/testdir/test_cursorline.vim
index bf049ec779..7e97df6027 100644
--- a/src/nvim/testdir/test_cursorline.vim
+++ b/src/nvim/testdir/test_cursorline.vim
@@ -293,5 +293,26 @@ func Test_cursorline_callback()
call delete('Xcul_timer')
endfunc
+func Test_cursorline_screenline_update()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, repeat('xyz ', 30))
+ set cursorline cursorlineopt=screenline
+ inoremap <F2> <Cmd>call cursor(1, 1)<CR>
+ END
+ call writefile(lines, 'Xcul_screenline')
+
+ let buf = RunVimInTerminal('-S Xcul_screenline', #{rows: 8})
+ call term_sendkeys(buf, "A")
+ call VerifyScreenDump(buf, 'Test_cursorline_screenline_1', {})
+ call term_sendkeys(buf, "\<F2>")
+ call VerifyScreenDump(buf, 'Test_cursorline_screenline_2', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ call StopVimInTerminal(buf)
+ call delete('Xcul_screenline')
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_display.vim b/src/nvim/testdir/test_display.vim
index 9f74d0a38a..094283a3a3 100644
--- a/src/nvim/testdir/test_display.vim
+++ b/src/nvim/testdir/test_display.vim
@@ -263,6 +263,27 @@ func Test_display_scroll_at_topline()
call StopVimInTerminal(buf)
endfunc
+func Test_display_scroll_update_visual()
+ CheckScreendump
+
+ let lines =<< trim END
+ set scrolloff=0
+ call setline(1, repeat(['foo'], 10))
+ call sign_define('foo', { 'text': '>' })
+ call sign_place(1, 'bar', 'foo', bufnr(), { 'lnum': 2 })
+ call sign_place(2, 'bar', 'foo', bufnr(), { 'lnum': 1 })
+ autocmd CursorMoved * if getcurpos()[1] == 2 | call sign_unplace('bar', { 'id': 1 }) | endif
+ END
+ call writefile(lines, 'XupdateVisual.vim')
+
+ let buf = RunVimInTerminal('-S XupdateVisual.vim', #{rows: 8, cols: 60})
+ call term_sendkeys(buf, "VG7kk")
+ call VerifyScreenDump(buf, 'Test_display_scroll_update_visual', {})
+
+ call StopVimInTerminal(buf)
+ call delete('XupdateVisual.vim')
+endfunc
+
" Test for 'eob' (EndOfBuffer) item in 'fillchars'
func Test_eob_fillchars()
" default value (skipped)
diff --git a/src/nvim/testdir/test_filechanged.vim b/src/nvim/testdir/test_filechanged.vim
index 06ccd6e85f..8e1cb2c3ee 100644
--- a/src/nvim/testdir/test_filechanged.vim
+++ b/src/nvim/testdir/test_filechanged.vim
@@ -142,6 +142,7 @@ endfunc
func Test_FileChangedShell_edit_dialog()
throw 'Skipped: requires a UI to be active'
CheckNotGui
+ CheckUnix " Using low level feedkeys() does not work on MS-Windows.
new Xchanged_r
call setline(1, 'reload this')
diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim
index 839d160e39..dd08d8bd09 100644
--- a/src/nvim/testdir/test_filetype.vim
+++ b/src/nvim/testdir/test_filetype.vim
@@ -381,6 +381,7 @@ let s:filename_checks = {
\ 'opam': ['opam', 'file.opam', 'file.opam.template'],
\ 'openroad': ['file.or'],
\ 'ora': ['file.ora'],
+ \ 'org': ['file.org', 'file.org_archive'],
\ 'pamconf': ['/etc/pam.conf', '/etc/pam.d/file', 'any/etc/pam.conf', 'any/etc/pam.d/file'],
\ 'pamenv': ['/etc/security/pam_env.conf', '/home/user/.pam_environment', '.pam_environment', 'pam_env.conf'],
\ 'papp': ['file.papp', 'file.pxml', 'file.pxsl'],
diff --git a/src/nvim/testdir/test_highlight.vim b/src/nvim/testdir/test_highlight.vim
index aa7b3a225b..6387ec62af 100644
--- a/src/nvim/testdir/test_highlight.vim
+++ b/src/nvim/testdir/test_highlight.vim
@@ -597,6 +597,31 @@ func Test_cursorline_with_visualmode()
call delete('Xtest_cursorline_with_visualmode')
endfunc
+func Test_cursorcolumn_callback()
+ CheckScreendump
+ CheckFeature timers
+
+ let lines =<< trim END
+ call setline(1, ['aaaaa', 'bbbbb', 'ccccc', 'ddddd'])
+ set cursorcolumn
+ call cursor(4, 5)
+
+ func Func(timer)
+ call cursor(1, 1)
+ endfunc
+
+ call timer_start(300, 'Func')
+ END
+ call writefile(lines, 'Xcuc_timer')
+
+ let buf = RunVimInTerminal('-S Xcuc_timer', #{rows: 8})
+ call TermWait(buf, 310)
+ call VerifyScreenDump(buf, 'Test_cursorcolumn_callback_1', {})
+
+ call StopVimInTerminal(buf)
+ call delete('Xcuc_timer')
+endfunc
+
func Test_colorcolumn()
CheckScreendump
diff --git a/src/nvim/testdir/test_messages.vim b/src/nvim/testdir/test_messages.vim
index 9c84d77dd2..82c4cc128b 100644
--- a/src/nvim/testdir/test_messages.vim
+++ b/src/nvim/testdir/test_messages.vim
@@ -2,6 +2,9 @@
source check.vim
source shared.vim
+source term_util.vim
+source view_util.vim
+source screendump.vim
func Test_messages()
let oldmore = &more
@@ -109,6 +112,22 @@ func Test_echospace()
set ruler& showcmd&
endfunc
+func Test_quit_long_message()
+ CheckScreendump
+
+ let content =<< trim END
+ echom range(9999)->join("\x01")
+ END
+ call writefile(content, 'Xtest_quit_message')
+ let buf = RunVimInTerminal('-S Xtest_quit_message', #{rows: 6})
+ call term_sendkeys(buf, "q")
+ call VerifyScreenDump(buf, 'Test_quit_long_message', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('Xtest_quit_message')
+endfunc
+
" this was missing a terminating NUL
func Test_echo_string_partial()
function CountSpaces()
@@ -116,3 +135,37 @@ func Test_echo_string_partial()
call assert_equal("function('CountSpaces', [{'ccccccccccc': ['ab', 'cd'], 'aaaaaaaaaaa': v:false, 'bbbbbbbbbbbb': ''}])", string(function('CountSpaces', [#{aaaaaaaaaaa: v:false, bbbbbbbbbbbb: '', ccccccccccc: ['ab', 'cd']}])))
endfunc
+" Message output was previously overwritten by the fileinfo display, shown
+" when switching buffers. If a buffer is switched to, then a message if
+" echoed, we should show the message, rather than overwriting it with
+" fileinfo.
+func Test_fileinfo_after_echo()
+ CheckScreendump
+
+ let content =<< trim END
+ file a.txt
+
+ hide edit b.txt
+ call setline(1, "hi")
+ setlocal modified
+
+ hide buffer a.txt
+
+ autocmd CursorHold * buf b.txt | w | echo "'b' written"
+ END
+
+ call writefile(content, 'Xtest_fileinfo_after_echo')
+ let buf = RunVimInTerminal('-S Xtest_fileinfo_after_echo', #{rows: 6})
+ call term_sendkeys(buf, ":set updatetime=50\<CR>")
+ call term_sendkeys(buf, "0$")
+ call VerifyScreenDump(buf, 'Test_fileinfo_after_echo', {})
+
+ call term_sendkeys(buf, ":q\<CR>")
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('Xtest_fileinfo_after_echo')
+ call delete('b.txt')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_mksession.vim b/src/nvim/testdir/test_mksession.vim
index 798cb9e54f..5dbe2cd366 100644
--- a/src/nvim/testdir/test_mksession.vim
+++ b/src/nvim/testdir/test_mksession.vim
@@ -795,6 +795,49 @@ func Test_mksession_winminheight()
set sessionoptions&
endfunc
+" Test for mksession with and without options restores shortmess
+func Test_mksession_shortmess()
+ " Without options
+ set sessionoptions-=options
+ split
+ mksession! Xtest_mks.out
+ let found_save = 0
+ let found_restore = 0
+ let lines = readfile('Xtest_mks.out')
+ for line in lines
+ let line = trim(line)
+
+ if line ==# 'let s:shortmess_save = &shortmess'
+ let found_save += 1
+ endif
+
+ if found_save !=# 0 && line ==# 'let &shortmess = s:shortmess_save'
+ let found_restore += 1
+ endif
+ endfor
+ call assert_equal(1, found_save)
+ call assert_equal(1, found_restore)
+ call delete('Xtest_mks.out')
+ close
+ set sessionoptions&
+
+ " With options
+ set sessionoptions+=options
+ split
+ mksession! Xtest_mks.out
+ let found_restore = 0
+ let lines = readfile('Xtest_mks.out')
+ for line in lines
+ if line =~# 's:shortmess_save'
+ let found_restore += 1
+ endif
+ endfor
+ call assert_equal(0, found_restore)
+ call delete('Xtest_mks.out')
+ close
+ set sessionoptions&
+endfunc
+
" Test for mksession with 'compatible' option
func Test_mksession_compatible()
throw 'skipped: Nvim does not support "compatible" option'
diff --git a/src/nvim/testdir/test_number.vim b/src/nvim/testdir/test_number.vim
index dfbdc0bffd..521b0cf706 100644
--- a/src/nvim/testdir/test_number.vim
+++ b/src/nvim/testdir/test_number.vim
@@ -298,6 +298,31 @@ func Test_relativenumber_colors()
call delete('XTest_relnr')
endfunc
+func Test_relativenumber_callback()
+ CheckScreendump
+ CheckFeature timers
+
+ let lines =<< trim END
+ call setline(1, ['aaaaa', 'bbbbb', 'ccccc', 'ddddd'])
+ set relativenumber
+ call cursor(4, 1)
+
+ func Func(timer)
+ call cursor(1, 1)
+ endfunc
+
+ call timer_start(300, 'Func')
+ END
+ call writefile(lines, 'Xrnu_timer')
+
+ let buf = RunVimInTerminal('-S Xrnu_timer', #{rows: 8})
+ call TermWait(buf, 310)
+ call VerifyScreenDump(buf, 'Test_relativenumber_callback_1', {})
+
+ call StopVimInTerminal(buf)
+ call delete('Xrnu_timer')
+endfunc
+
" Test for displaying line numbers with 'rightleft'
func Test_number_rightleft()
CheckFeature rightleft
diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim
index 14d13049d9..5457223677 100644
--- a/src/nvim/testdir/test_quickfix.vim
+++ b/src/nvim/testdir/test_quickfix.vim
@@ -796,101 +796,102 @@ func ReadTestProtocol(name)
endfunc
func Test_locationlist()
- enew
+ enew
- augroup testgroup
- au!
- autocmd BufReadCmd test://* call ReadTestProtocol(expand("<amatch>"))
- augroup END
+ augroup testgroup
+ au!
+ autocmd BufReadCmd test://* call ReadTestProtocol(expand("<amatch>"))
+ augroup END
- let words = [ "foo", "bar", "baz", "quux", "shmoo", "spam", "eggs" ]
+ let words = [ "foo", "bar", "baz", "quux", "shmoo", "spam", "eggs" ]
- let qflist = []
- for word in words
- call add(qflist, {'filename': 'test://' . word . '.txt', 'text': 'file ' . word . '.txt', })
- " NOTE: problem 1:
- " intentionally not setting 'lnum' so that the quickfix entries are not
- " valid
- eval qflist->setloclist(0, ' ')
- endfor
+ let qflist = []
+ for word in words
+ call add(qflist, {'filename': 'test://' . word . '.txt', 'text': 'file ' . word . '.txt', })
+ " NOTE: problem 1:
+ " intentionally not setting 'lnum' so that the quickfix entries are not
+ " valid
+ eval qflist->setloclist(0, ' ')
+ endfor
- " Test A
- lrewind
- enew
- lopen
- 4lnext
- vert split
- wincmd L
- lopen
- wincmd p
- lnext
- let fileName = expand("%")
- wincmd p
- let locationListFileName = substitute(getline(line('.')), '\([^|]*\)|.*', '\1', '')
- let fileName = substitute(fileName, '\\', '/', 'g')
- let locationListFileName = substitute(locationListFileName, '\\', '/', 'g')
- call assert_equal("test://bar.txt", fileName)
- call assert_equal("test://bar.txt", locationListFileName)
+ " Test A
+ lrewind
+ enew
+ lopen
+ 4lnext
+ vert split
+ wincmd L
+ lopen
+ wincmd p
+ lnext
+ let fileName = expand("%")
+ wincmd p
+ let locationListFileName = substitute(getline(line('.')), '\([^|]*\)|.*', '\1', '')
+ let fileName = substitute(fileName, '\\', '/', 'g')
+ let locationListFileName = substitute(locationListFileName, '\\', '/', 'g')
+ call assert_equal("test://bar.txt", fileName)
+ call assert_equal("test://bar.txt", locationListFileName)
- wincmd n | only
+ wincmd n | only
- " Test B:
- lrewind
- lopen
- 2
- exe "normal \<CR>"
- wincmd p
- 3
- exe "normal \<CR>"
- wincmd p
- 4
- exe "normal \<CR>"
- call assert_equal(2, winnr('$'))
- wincmd n | only
+ " Test B:
+ lrewind
+ lopen
+ 2
+ exe "normal \<CR>"
+ wincmd p
+ 3
+ exe "normal \<CR>"
+ wincmd p
+ 4
+ exe "normal \<CR>"
+ call assert_equal(2, winnr('$'))
+ wincmd n | only
- " Test C:
- lrewind
- lopen
- " Let's move the location list window to the top to check whether it (the
- " first window found) will be reused when we try to open new windows:
- wincmd K
- 2
- exe "normal \<CR>"
- wincmd p
- 3
- exe "normal \<CR>"
- wincmd p
- 4
- exe "normal \<CR>"
- 1wincmd w
- call assert_equal('quickfix', &buftype)
- 2wincmd w
- let bufferName = expand("%")
- let bufferName = substitute(bufferName, '\\', '/', 'g')
- call assert_equal('test://quux.txt', bufferName)
+ " Test C:
+ lrewind
+ lopen
+ " Let's move the location list window to the top to check whether it (the
+ " first window found) will be reused when we try to open new windows:
+ wincmd K
+ 2
+ exe "normal \<CR>"
+ wincmd p
+ 3
+ exe "normal \<CR>"
+ wincmd p
+ 4
+ exe "normal \<CR>"
+ 1wincmd w
+ call assert_equal('quickfix', &buftype)
+ 2wincmd w
+ let bufferName = expand("%")
+ let bufferName = substitute(bufferName, '\\', '/', 'g')
+ call assert_equal('test://quux.txt', bufferName)
- wincmd n | only
+ wincmd n | only
- augroup! testgroup
+ augroup! testgroup
endfunc
func Test_locationlist_curwin_was_closed()
- augroup testgroup
- au!
- autocmd BufReadCmd test_curwin.txt call R(expand("<amatch>"))
- augroup END
+ augroup testgroup
+ au!
+ autocmd BufReadCmd test_curwin.txt call R(expand("<amatch>"))
+ augroup END
- func! R(n)
- quit
- endfunc
+ func! R(n)
+ quit
+ endfunc
- new
- let q = []
- call add(q, {'filename': 'test_curwin.txt' })
- call setloclist(0, q)
- call assert_fails('lrewind', 'E924:')
+ new
+ let q = []
+ call add(q, {'filename': 'test_curwin.txt' })
+ call setloclist(0, q)
+ call assert_fails('lrewind', 'E924:')
- augroup! testgroup
+ augroup! testgroup
+ delfunc R
endfunc
func Test_locationlist_cross_tab_jump()
@@ -5489,4 +5490,45 @@ func Test_two_qf_windows()
%bw!
endfunc
+" Weird sequence of commands that caused entering a wiped-out buffer
+func Test_lopen_bwipe()
+ func R()
+ silent! tab lopen
+ e x
+ silent! lfile
+ endfunc
+
+ cal R()
+ cal R()
+ cal R()
+ bw!
+ delfunc R
+endfunc
+
+" Another sequence of commands that caused all buffers to be wiped out
+func Test_lopen_bwipe_all()
+ let lines =<< trim END
+ func R()
+ silent! tab lopen
+ e foo
+ silent! lfile
+ endfunc
+ cal R()
+ exe "norm \<C-W>\<C-V>0"
+ cal R()
+ bwipe
+
+ call writefile(['done'], 'Xresult')
+ qall!
+ END
+ call writefile(lines, 'Xscript')
+ if RunVim([], [], '--clean -n -S Xscript')
+ call assert_equal(['done'], readfile('Xresult'))
+ endif
+
+ call delete('Xscript')
+ call delete('Xresult')
+endfunc
+
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_regexp_latin.vim b/src/nvim/testdir/test_regexp_latin.vim
index a0f5ebfb9f..b733e334de 100644
--- a/src/nvim/testdir/test_regexp_latin.vim
+++ b/src/nvim/testdir/test_regexp_latin.vim
@@ -787,12 +787,109 @@ func Test_regexp_error()
set re&
endfunc
+" Check patterns matching cursor position.
+func s:curpos_test2()
+ new
+ call setline(1, ['1', '2 foobar eins zwei drei vier fünf sechse',
+ \ '3 foobar eins zwei drei vier fünf sechse',
+ \ '4 foobar eins zwei drei vier fünf sechse',
+ \ '5 foobar eins zwei drei vier fünf sechse',
+ \ '6 foobar eins zwei drei vier fünf sechse',
+ \ '7 foobar eins zwei drei vier fünf sechse'])
+ call setpos('.', [0, 2, 10, 0])
+ s/\%.c.*//g
+ call setpos('.', [0, 3, 15, 0])
+ s/\%.l.*//g
+ call setpos('.', [0, 5, 3, 0])
+ s/\%.v.*/_/g
+ call assert_equal(['1',
+ \ '2 foobar ',
+ \ '',
+ \ '4 foobar eins zwei drei vier fünf sechse',
+ \ '5 _',
+ \ '6 foobar eins zwei drei vier fünf sechse',
+ \ '7 foobar eins zwei drei vier fünf sechse'],
+ \ getline(1, '$'))
+ call assert_fails('call search("\\%.1l")', 'E1204:')
+ call assert_fails('call search("\\%.1c")', 'E1204:')
+ call assert_fails('call search("\\%.1v")', 'E1204:')
+ bwipe!
+endfunc
+
+" Check patterns matching before or after cursor position.
+func s:curpos_test3()
+ new
+ call setline(1, ['1', '2 foobar eins zwei drei vier fünf sechse',
+ \ '3 foobar eins zwei drei vier fünf sechse',
+ \ '4 foobar eins zwei drei vier fünf sechse',
+ \ '5 foobar eins zwei drei vier fünf sechse',
+ \ '6 foobar eins zwei drei vier fünf sechse',
+ \ '7 foobar eins zwei drei vier fünf sechse'])
+ call setpos('.', [0, 2, 10, 0])
+ " Note: This removes all columns, except for the column directly in front of
+ " the cursor. Bug????
+ :s/^.*\%<.c//
+ call setpos('.', [0, 3, 10, 0])
+ :s/\%>.c.*$//
+ call setpos('.', [0, 5, 4, 0])
+ " Note: This removes all columns, except for the column directly in front of
+ " the cursor. Bug????
+ :s/^.*\%<.v/_/
+ call setpos('.', [0, 6, 4, 0])
+ :s/\%>.v.*$/_/
+ call assert_equal(['1',
+ \ ' eins zwei drei vier fünf sechse',
+ \ '3 foobar e',
+ \ '4 foobar eins zwei drei vier fünf sechse',
+ \ '_foobar eins zwei drei vier fünf sechse',
+ \ '6 fo_',
+ \ '7 foobar eins zwei drei vier fünf sechse'],
+ \ getline(1, '$'))
+ sil %d
+ call setline(1, ['1', '2 foobar eins zwei drei vier fünf sechse',
+ \ '3 foobar eins zwei drei vier fünf sechse',
+ \ '4 foobar eins zwei drei vier fünf sechse',
+ \ '5 foobar eins zwei drei vier fünf sechse',
+ \ '6 foobar eins zwei drei vier fünf sechse',
+ \ '7 foobar eins zwei drei vier fünf sechse'])
+ call setpos('.', [0, 4, 4, 0])
+ %s/\%<.l.*//
+ call setpos('.', [0, 5, 4, 0])
+ %s/\%>.l.*//
+ call assert_equal(['', '', '',
+ \ '4 foobar eins zwei drei vier fünf sechse',
+ \ '5 foobar eins zwei drei vier fünf sechse',
+ \ '', ''],
+ \ getline(1, '$'))
+ bwipe!
+endfunc
+
+" Test that matching below, at or after the
+" cursor position work
+func Test_matching_pos()
+ for val in range(3)
+ exe "set re=" .. val
+ " Match at cursor position
+ call s:curpos_test2()
+ " Match before or after cursor position
+ call s:curpos_test3()
+ endfor
+ set re&
+endfunc
+
func Test_using_mark_position()
" this was using freed memory
+ " new engine
new
norm O0
call assert_fails("s/\\%')", 'E486:')
bwipe!
+
+ " old engine
+ new
+ norm O0
+ call assert_fails("s/\\%#=1\\%')", 'E486:')
+ bwipe!
endfunc
func Test_using_visual_position()
diff --git a/src/nvim/testdir/test_sort.vim b/src/nvim/testdir/test_sort.vim
index f72368fc59..5d7dd7bfff 100644
--- a/src/nvim/testdir/test_sort.vim
+++ b/src/nvim/testdir/test_sort.vim
@@ -59,6 +59,7 @@ endfunc
func Test_sort_numbers()
call assert_equal([3, 13, 28], sort([13, 28, 3], 'N'))
call assert_equal(['3', '13', '28'], sort(['13', '28', '3'], 'N'))
+ call assert_equal([3997, 4996], sort([4996, 3997], 'Compare1'))
endfunc
func Test_sort_float()
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index 79a6b9ed0e..a67bcf98dc 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -131,6 +131,7 @@ typedef struct {
int get_bg;
int set_underline_style;
int set_underline_color;
+ int enable_extended_keys, disable_extended_keys;
} unibi_ext;
char *space_buf;
} TUIData;
@@ -168,7 +169,7 @@ UI *tui_start(void)
ui->set_title = tui_set_title;
ui->set_icon = tui_set_icon;
ui->screenshot = tui_screenshot;
- ui->option_set= tui_option_set;
+ ui->option_set = tui_option_set;
ui->raw_line = tui_raw_line;
memset(ui->ui_ext, 0, sizeof(ui->ui_ext));
@@ -225,6 +226,8 @@ static void terminfo_start(UI *ui)
data->unibi_ext.reset_cursor_style = -1;
data->unibi_ext.get_bg = -1;
data->unibi_ext.set_underline_color = -1;
+ data->unibi_ext.enable_extended_keys = -1;
+ data->unibi_ext.disable_extended_keys = -1;
data->out_fd = STDOUT_FILENO;
data->out_isatty = os_isatty(data->out_fd);
@@ -308,6 +311,10 @@ static void terminfo_start(UI *ui)
// Enable bracketed paste
unibi_out_ext(ui, data->unibi_ext.enable_bracketed_paste);
+ // Enable extended keys (also known as 'modifyOtherKeys' or CSI u). On terminals that don't
+ // support this, this sequence is ignored.
+ unibi_out_ext(ui, data->unibi_ext.enable_extended_keys);
+
int ret;
uv_loop_init(&data->write_loop);
if (data->out_isatty) {
@@ -365,6 +372,8 @@ static void terminfo_stop(UI *ui)
unibi_out_ext(ui, data->unibi_ext.disable_bracketed_paste);
// Disable focus reporting
unibi_out_ext(ui, data->unibi_ext.disable_focus_reporting);
+ // Disable extended keys
+ unibi_out_ext(ui, data->unibi_ext.disable_extended_keys);
flush_buf(ui);
uv_tty_reset_mode();
uv_close((uv_handle_t *)&data->output_handle, NULL);
@@ -1378,7 +1387,6 @@ static void tui_screenshot(UI *ui, String path)
fclose(f);
}
-
static void tui_option_set(UI *ui, String name, Object value)
{
TUIData *data = ui->data;
@@ -1387,11 +1395,9 @@ static void tui_option_set(UI *ui, String name, Object value)
data->print_attr_id = -1;
invalidate(ui, 0, data->grid.height, 0, data->grid.width);
- }
- if (strequal(name.data, "ttimeout")) {
+ } else if (strequal(name.data, "ttimeout")) {
data->input.ttimeout = value.data.boolean;
- }
- if (strequal(name.data, "ttimeoutlen")) {
+ } else if (strequal(name.data, "ttimeoutlen")) {
data->input.ttimeoutlen = (long)value.data.integer;
}
}
@@ -1944,6 +1950,7 @@ static void augment_terminfo(TUIData *data, const char *term, long vte_version,
|| terminfo_is_term_family(term, "iTerm.app")
|| terminfo_is_term_family(term, "iTerm2.app");
bool alacritty = terminfo_is_term_family(term, "alacritty");
+ bool kitty = terminfo_is_term_family(term, "xterm-kitty");
// None of the following work over SSH; see :help TERM .
bool iterm_pretending_xterm = xterm && iterm_env;
@@ -2067,6 +2074,15 @@ static void augment_terminfo(TUIData *data, const char *term, long vte_version,
data->unibi_ext.set_underline_color = (int)unibi_add_ext_str(ut, "ext.set_underline_color",
"\x1b[58:2::%p1%d:%p2%d:%p3%dm");
}
+
+ if (!kitty) {
+ // Kitty does not support these sequences; it only supports it's own CSI > 1 u which enables the
+ // Kitty keyboard protocol
+ data->unibi_ext.enable_extended_keys = (int)unibi_add_ext_str(ut, "ext.enable_extended_keys",
+ "\x1b[>4;2m");
+ data->unibi_ext.disable_extended_keys = (int)unibi_add_ext_str(ut, "ext.disable_extended_keys",
+ "\x1b[>4;0m");
+ }
}
static void flush_buf(UI *ui)
diff --git a/src/nvim/ui_client.c b/src/nvim/ui_client.c
index e723a30d32..6a7695bf72 100644
--- a/src/nvim/ui_client.c
+++ b/src/nvim/ui_client.c
@@ -151,10 +151,8 @@ void ui_client_event_grid_line(Array args)
Integer startcol = args.items[2].data.integer;
Array cells = args.items[3].data.array;
- Integer endcol, clearcol;
- // TODO(hlpr98): Accomodate other LineFlags when included in grid_line
+ // TODO(hlpr98): Accommodate other LineFlags when included in grid_line
LineFlags lineflags = 0;
- endcol = startcol;
size_t j = 0;
int cur_attr = 0;
@@ -203,8 +201,8 @@ void ui_client_event_grid_line(Array args)
}
}
- endcol = startcol + (int)j;
- clearcol = endcol + clear_width;
+ Integer endcol = startcol + (int)j;
+ Integer clearcol = endcol + clear_width;
clear_attr = cur_attr;
ui_call_raw_line(grid, row, startcol, endcol, clearcol, clear_attr, lineflags,
diff --git a/src/nvim/window.c b/src/nvim/window.c
index fa71a08b94..632e9b3f44 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -971,7 +971,6 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
int before;
int minheight;
int wmh1;
- int hsep_height;
bool did_set_fraction = false;
// aucmd_win should always remain floating
@@ -1084,7 +1083,6 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
}
}
} else {
- hsep_height = STATUS_HEIGHT;
layout = FR_COL;
/*
@@ -1093,7 +1091,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
*/
// Current window requires at least 1 space.
wmh1 = p_wmh == 0 ? 1 : p_wmh;
- needed = wmh1 + hsep_height;
+ needed = wmh1 + STATUS_HEIGHT;
if (flags & WSP_ROOM) {
needed += p_wh - wmh1;
}
@@ -1135,15 +1133,15 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
new_size = oldwin_height / 2;
}
- if (new_size > available - minheight - hsep_height) {
- new_size = available - minheight - hsep_height;
+ if (new_size > available - minheight - STATUS_HEIGHT) {
+ new_size = available - minheight - STATUS_HEIGHT;
}
if (new_size < wmh1) {
new_size = wmh1;
}
// if it doesn't fit in the current window, need win_equal()
- if (oldwin_height - new_size - hsep_height < p_wmh) {
+ if (oldwin_height - new_size - STATUS_HEIGHT < p_wmh) {
do_equal = true;
}
@@ -1156,7 +1154,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
set_fraction(oldwin);
did_set_fraction = true;
- win_setheight_win(oldwin->w_height + new_size + hsep_height,
+ win_setheight_win(oldwin->w_height + new_size + STATUS_HEIGHT,
oldwin);
oldwin_height = oldwin->w_height;
if (need_status) {
@@ -1173,7 +1171,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
while (frp != NULL) {
if (frp->fr_win != oldwin && frp->fr_win != NULL
&& (frp->fr_win->w_height > new_size
- || frp->fr_win->w_height > oldwin_height - new_size - hsep_height)) {
+ || frp->fr_win->w_height > oldwin_height - new_size - STATUS_HEIGHT)) {
do_equal = true;
break;
}
@@ -2021,7 +2019,6 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int
int room = 0;
int new_size;
int has_next_curwin = 0;
- int hsep_height;
bool hnc;
if (topfr->fr_layout == FR_LEAF) {
@@ -2167,7 +2164,6 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int
totwincount -= wincount;
}
} else { // topfr->fr_layout == FR_COL
- hsep_height = STATUS_HEIGHT;
topfr->fr_width = width;
topfr->fr_height = height;
@@ -2182,7 +2178,7 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int
} else {
extra_sep = 0;
}
- totwincount = (n + extra_sep) / (p_wmh + hsep_height);
+ totwincount = (n + extra_sep) / (p_wmh + STATUS_HEIGHT);
has_next_curwin = frame_has_win(topfr, next_curwin);
/*
@@ -2217,7 +2213,7 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int
} else {
// These windows don't use up room.
totwincount -= (n + (fr->fr_next == NULL
- ? extra_sep : 0)) / (p_wmh + hsep_height);
+ ? extra_sep : 0)) / (p_wmh + STATUS_HEIGHT);
}
room -= new_size - n;
if (room < 0) {
@@ -2263,7 +2259,7 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int
// Compute the maximum number of windows vert. in "fr".
n = frame_minheight(fr, NOWIN);
wincount = (n + (fr->fr_next == NULL ? extra_sep : 0))
- / (p_wmh + hsep_height);
+ / (p_wmh + STATUS_HEIGHT);
m = frame_minheight(fr, next_curwin);
if (has_next_curwin) {
hnc = frame_has_win(fr, next_curwin);
@@ -2350,6 +2346,30 @@ void entering_window(win_T *const win)
}
}
+void win_init_empty(win_T *wp)
+{
+ redraw_later(wp, NOT_VALID);
+ wp->w_lines_valid = 0;
+ wp->w_cursor.lnum = 1;
+ wp->w_curswant = wp->w_cursor.col = 0;
+ wp->w_cursor.coladd = 0;
+ wp->w_pcmark.lnum = 1; // pcmark not cleared but set to line 1
+ wp->w_pcmark.col = 0;
+ wp->w_prev_pcmark.lnum = 0;
+ wp->w_prev_pcmark.col = 0;
+ wp->w_topline = 1;
+ wp->w_topfill = 0;
+ wp->w_botline = 2;
+ wp->w_s = &wp->w_buffer->b_s;
+}
+
+/// Init the current window "curwin".
+/// Called when a new file is being edited.
+void curwin_init(void)
+{
+ win_init_empty(curwin);
+}
+
/// Closes all windows for buffer `buf` unless there is only one non-floating window.
///
/// @param keep_curwin don't close `curwin`
@@ -2867,6 +2887,13 @@ void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp)
for (ptp = first_tabpage; ptp != NULL && ptp != tp; ptp = ptp->tp_next) {
}
if (ptp == NULL || tp == curtab) {
+ // If the buffer was removed from the window we have to give it any
+ // buffer.
+ if (win_valid_any_tab(win) && win->w_buffer == NULL) {
+ win->w_buffer = firstbuf;
+ firstbuf->b_nwindows++;
+ win_init_empty(win);
+ }
return;
}
@@ -3793,33 +3820,6 @@ void close_others(int message, int forceit)
}
}
-
-/*
- * Init the current window "curwin".
- * Called when a new file is being edited.
- */
-void curwin_init(void)
-{
- win_init_empty(curwin);
-}
-
-void win_init_empty(win_T *wp)
-{
- redraw_later(wp, NOT_VALID);
- wp->w_lines_valid = 0;
- wp->w_cursor.lnum = 1;
- wp->w_curswant = wp->w_cursor.col = 0;
- wp->w_cursor.coladd = 0;
- wp->w_pcmark.lnum = 1; // pcmark not cleared but set to line 1
- wp->w_pcmark.col = 0;
- wp->w_prev_pcmark.lnum = 0;
- wp->w_prev_pcmark.col = 0;
- wp->w_topline = 1;
- wp->w_topfill = 0;
- wp->w_botline = 2;
- wp->w_s = &wp->w_buffer->b_s;
-}
-
/*
* Allocate the first window and put an empty buffer in it.
* Called from main().
@@ -5857,11 +5857,11 @@ void win_drag_status_line(win_T *dragwin, int offset)
} else { // drag down
up = false;
// Only dragging the last status line can reduce p_ch.
- room = Rows - cmdline_row - global_stl_height();
+ room = Rows - cmdline_row;
if (curfr->fr_next == NULL) {
room -= 1;
} else {
- room -= p_ch;
+ room -= p_ch + global_stl_height();
}
if (room < 0) {
room = 0;
@@ -6510,38 +6510,32 @@ void last_status(bool morewin)
}
// Look for resizable frames and take lines from them to make room for the statusline
-static void resize_frame_for_status(frame_T *fr, int resize_amount)
+static void resize_frame_for_status(frame_T *fr)
{
// Find a frame to take a line from.
frame_T *fp = fr;
win_T *wp = fr->fr_win;
- int n;
- while (resize_amount > 0) {
- while (fp->fr_height <= frame_minheight(fp, NULL)) {
- if (fp == topframe) {
- emsg(_(e_noroom));
- return;
- }
- // In a column of frames: go to frame above. If already at
- // the top or in a row of frames: go to parent.
- if (fp->fr_parent->fr_layout == FR_COL && fp->fr_prev != NULL) {
- fp = fp->fr_prev;
- } else {
- fp = fp->fr_parent;
- }
+ while (fp->fr_height <= frame_minheight(fp, NULL)) {
+ if (fp == topframe) {
+ emsg(_(e_noroom));
+ return;
}
- n = MIN(fp->fr_height - frame_minheight(fp, NULL), resize_amount);
- resize_amount -= n;
-
- if (fp != fr) {
- frame_new_height(fp, fp->fr_height - n, false, false);
- frame_fix_height(wp);
- (void)win_comp_pos();
+ // In a column of frames: go to frame above. If already at
+ // the top or in a row of frames: go to parent.
+ if (fp->fr_parent->fr_layout == FR_COL && fp->fr_prev != NULL) {
+ fp = fp->fr_prev;
} else {
- win_new_height(wp, wp->w_height - n);
+ fp = fp->fr_parent;
}
}
+ if (fp != fr) {
+ frame_new_height(fp, fp->fr_height - 1, false, false);
+ frame_fix_height(wp);
+ (void)win_comp_pos();
+ } else {
+ win_new_height(wp, wp->w_height - 1);
+ }
}
static void last_status_rec(frame_T *fr, bool statusline, bool is_stl_global)
@@ -6562,15 +6556,12 @@ static void last_status_rec(frame_T *fr, bool statusline, bool is_stl_global)
} else if (wp->w_status_height == 0 && !is_stl_global && statusline) {
// Add statusline to window if needed
wp->w_status_height = STATUS_HEIGHT;
- resize_frame_for_status(fr, STATUS_HEIGHT);
+ resize_frame_for_status(fr);
comp_col();
}
} else if (wp->w_status_height != 0 && is_stl_global) {
// If statusline is global and the window has a statusline, replace it with a horizontal
// separator
- if (STATUS_HEIGHT - 1 != 0) {
- win_new_height(wp, wp->w_height + STATUS_HEIGHT - 1);
- }
wp->w_status_height = 0;
wp->w_hsep_height = 1;
comp_col();
@@ -6578,7 +6569,6 @@ static void last_status_rec(frame_T *fr, bool statusline, bool is_stl_global)
// If statusline isn't global and the window doesn't have a statusline, re-add it
wp->w_status_height = STATUS_HEIGHT;
wp->w_hsep_height = 0;
- resize_frame_for_status(fr, STATUS_HEIGHT - 1);
comp_col();
}
redraw_all_later(SOME_VALID);