aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/api-docs-check.yml4
-rw-r--r--.github/workflows/api-docs.yml3
-rw-r--r--CMakeLists.txt24
-rw-r--r--runtime/doc/recover.txt2
-rw-r--r--runtime/ftplugin/cpp.vim1
-rwxr-xr-xsrc/nvim/CMakeLists.txt7
-rw-r--r--src/nvim/api/vim.c2
-rw-r--r--src/nvim/api/win_config.c4
-rw-r--r--src/nvim/api/window.c1
-rw-r--r--src/nvim/buffer.c27
-rw-r--r--src/nvim/buffer_defs.h1
-rw-r--r--src/nvim/eval.c19
-rw-r--r--src/nvim/eval/funcs.c267
-rw-r--r--src/nvim/eval/userfunc.c183
-rw-r--r--src/nvim/event/process.c2
-rw-r--r--src/nvim/event/rstream.c18
-rw-r--r--src/nvim/ex_docmd.c3
-rw-r--r--src/nvim/ex_session.c50
-rw-r--r--src/nvim/extmark.c30
-rw-r--r--src/nvim/file_search.c101
-rw-r--r--src/nvim/fileio.c156
-rw-r--r--src/nvim/getchar.c8
-rw-r--r--src/nvim/highlight_group.c1172
-rw-r--r--src/nvim/keymap.c15
-rw-r--r--src/nvim/log.c1
-rw-r--r--src/nvim/main.c1
-rw-r--r--src/nvim/match.c1181
-rw-r--r--src/nvim/match.h12
-rw-r--r--src/nvim/memline.c357
-rw-r--r--src/nvim/move.c22
-rw-r--r--src/nvim/normal.c8
-rw-r--r--src/nvim/option.c5
-rw-r--r--src/nvim/os/input.c1
-rw-r--r--src/nvim/os/signal.c1
-rw-r--r--src/nvim/quickfix.c6
-rw-r--r--src/nvim/screen.c43
-rw-r--r--src/nvim/sign.c367
-rw-r--r--src/nvim/sign.h1
-rw-r--r--src/nvim/testdir/test_cursorline.vim26
-rw-r--r--src/nvim/testdir/test_eval_stuff.vim2
-rw-r--r--src/nvim/testdir/test_regex_char_classes.vim104
-rw-r--r--src/nvim/testdir/test_substitute.vim56
-rw-r--r--src/nvim/tui/tui.c26
-rw-r--r--src/nvim/ui.c7
-rw-r--r--src/nvim/ui_client.c1
-rw-r--r--src/nvim/window.c103
-rw-r--r--test/functional/autocmd/autocmd_spec.lua19
-rw-r--r--test/functional/ex_cmds/swapfile_preserve_recover_spec.lua3
-rw-r--r--test/functional/legacy/eval_spec.lua2
-rw-r--r--test/functional/ui/decorations_spec.lua21
-rw-r--r--test/functional/ui/float_spec.lua302
-rw-r--r--test/functional/ui/highlight_spec.lua40
-rw-r--r--test/functional/ui/input_spec.lua45
53 files changed, 2629 insertions, 2234 deletions
diff --git a/.github/workflows/api-docs-check.yml b/.github/workflows/api-docs-check.yml
index 8ae6e6ff92..46922391f5 100644
--- a/.github/workflows/api-docs-check.yml
+++ b/.github/workflows/api-docs-check.yml
@@ -6,12 +6,14 @@ on:
- 'marvim/api-doc-update**'
paths:
- 'src/nvim/api/*.[ch]'
- - 'src/nvim/**.lua'
- 'runtime/lua/**.lua'
jobs:
call-regen-api-docs:
if: github.event.pull_request.draft == false
+ permissions:
+ contents: write
+ pull-requests: write
uses: ./.github/workflows/api-docs.yml
with:
check_only: true
diff --git a/.github/workflows/api-docs.yml b/.github/workflows/api-docs.yml
index 36ac087c4a..561524f64a 100644
--- a/.github/workflows/api-docs.yml
+++ b/.github/workflows/api-docs.yml
@@ -6,7 +6,6 @@ on:
push:
paths:
- 'src/nvim/api/*.[ch]'
- - 'src/nvim/**.lua'
- 'runtime/lua/**.lua'
branches:
- 'master'
@@ -60,7 +59,7 @@ jobs:
exit 1
- name: Automatic PR
- if: ${{ steps.docs.outputs.UPDATED_DOCS != 0 }}
+ if: ${{ steps.docs.outputs.UPDATED_DOCS != 0 && !inputs.check_only }}
run: |
git add -u
git commit -m 'docs: regenerate [skip ci]'
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 767bd797bc..2aed7d8b48 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -284,6 +284,9 @@ if(MSVC)
else()
add_compile_options(-Wall -Wextra -pedantic -Wno-unused-parameter
-Wstrict-prototypes -std=gnu99 -Wshadow -Wconversion
+ -Wdouble-promotion
+ -Wmissing-noreturn
+ -Wmissing-format-attribute
-Wmissing-prototypes)
check_c_compiler_flag(-Wimplicit-fallthrough HAVE_WIMPLICIT_FALLTHROUGH_FLAG)
@@ -291,6 +294,27 @@ else()
add_compile_options(-Wimplicit-fallthrough)
endif()
+ # Clang doesn't have -Wsuggest-attribute so check for each one.
+ check_c_compiler_flag(-Wsuggest-attribute=pure HAVE_WSUGGEST_ATTRIBUTE_PURE)
+ if(HAVE_WSUGGEST_ATTRIBUTE_PURE)
+ add_compile_options(-Wsuggest-attribute=pure)
+ endif()
+
+ check_c_compiler_flag(-Wsuggest-attribute=const HAVE_WSUGGEST_ATTRIBUTE_CONST)
+ if(HAVE_WSUGGEST_ATTRIBUTE_CONST)
+ add_compile_options(-Wsuggest-attribute=const)
+ endif()
+
+ check_c_compiler_flag(-Wsuggest-attribute=malloc HAVE_WSUGGEST_ATTRIBUTE_MALLOC)
+ if(HAVE_WSUGGEST_ATTRIBUTE_MALLOC)
+ add_compile_options(-Wsuggest-attribute=malloc)
+ endif()
+
+ check_c_compiler_flag(-Wsuggest-attribute=cold HAVE_WSUGGEST_ATTRIBUTE_COLD)
+ if(HAVE_WSUGGEST_ATTRIBUTE_COLD)
+ add_compile_options(-Wsuggest-attribute=cold)
+ endif()
+
# On FreeBSD 64 math.h uses unguarded C11 extension, which taints clang
# 3.4.1 used there.
if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" AND CMAKE_C_COMPILER_ID MATCHES "Clang")
diff --git a/runtime/doc/recover.txt b/runtime/doc/recover.txt
index 9ef5a37452..d9aaa757ad 100644
--- a/runtime/doc/recover.txt
+++ b/runtime/doc/recover.txt
@@ -108,7 +108,7 @@ command:
*:pre* *:preserve* *E313* *E314*
:pre[serve] Write all text for the current buffer into its swap
file. The original file is no longer needed for
- recovery. This sets a flag in the current buffer.
+ recovery.
A Vim swap file can be recognized by the first six characters: "b0VIM ".
After that comes the version number, e.g., "3.0".
diff --git a/runtime/ftplugin/cpp.vim b/runtime/ftplugin/cpp.vim
index f9d31cbec3..58c4e4b24a 100644
--- a/runtime/ftplugin/cpp.vim
+++ b/runtime/ftplugin/cpp.vim
@@ -10,6 +10,7 @@ endif
" Behaves mostly just like C
runtime! ftplugin/c.vim ftplugin/c_*.vim ftplugin/c/*.vim
+runtime! ftplugin/c.lua ftplugin/c_*.lua ftplugin/c/*.lua
" C++ uses templates with <things>
" Disabled, because it gives an error for typing an unmatched ">".
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt
index 8e17f94abc..b7f9292de1 100755
--- a/src/nvim/CMakeLists.txt
+++ b/src/nvim/CMakeLists.txt
@@ -182,12 +182,15 @@ foreach(sfile ${CONV_SOURCES})
message(FATAL_ERROR "${sfile} doesn't exist (it was added to CONV_SOURCES)")
endif()
endforeach()
-# xdiff, mpack, lua-cjson: inlined external project, we don't maintain it. #9306
-list(APPEND CONV_SOURCES ${EXTERNAL_SOURCES})
if(NOT MSVC)
set_source_files_properties(
${CONV_SOURCES} PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-conversion")
+
+ # xdiff, mpack, lua-cjson: inlined external project, we don't maintain it. #9306
+ set_source_files_properties(
+ ${EXTERNAL_SOURCES} PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-conversion -Wno-missing-noreturn -Wno-missing-format-attribute -Wno-double-promotion")
+
# gperf generates ANSI-C with incorrect linkage, ignore it.
check_c_compiler_flag(-Wstatic-in-inline HAS_WSTATIC_IN_INLINE)
if(HAS_WSTATIC_IN_INLINE)
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index bdeac1a9f4..7c7ada55a2 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -1328,7 +1328,7 @@ Boolean nvim_paste(String data, Boolean crlf, Integer phase, Error *err)
if (!cancel && !(State & CMDLINE)) { // Dot-repeat.
for (size_t i = 0; i < lines.size; i++) {
String s = lines.items[i].data.string;
- assert(data.size <= INT_MAX);
+ assert(s.size <= INT_MAX);
AppendToRedobuffLit((char_u *)s.data, (int)s.size);
// readfile()-style: "\n" is indicated by presence of N+1 item.
if (i + 1 < lines.size) {
diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c
index 42e880dc19..5e37596884 100644
--- a/src/nvim/api/win_config.c
+++ b/src/nvim/api/win_config.c
@@ -150,7 +150,7 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(float_config) *config, E
if (!parse_float_config(config, &fconfig, false, true, err)) {
return 0;
}
- win_T *wp = win_new_float(NULL, fconfig, err);
+ win_T *wp = win_new_float(NULL, false, fconfig, err);
if (!wp) {
return 0;
}
@@ -200,7 +200,7 @@ void nvim_win_set_config(Window window, Dict(float_config) *config, Error *err)
return;
}
if (new_float) {
- if (!win_new_float(win, fconfig, err)) {
+ if (!win_new_float(win, false, fconfig, err)) {
return;
}
redraw_later(win, NOT_VALID);
diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c
index be43708604..fd33a82be3 100644
--- a/src/nvim/api/window.c
+++ b/src/nvim/api/window.c
@@ -119,7 +119,6 @@ void nvim_win_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err)
update_topline_win(win);
redraw_later(win, VALID);
- redraw_for_cursorline(win);
win->w_redr_status = true;
}
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index 1293edb1da..f200f16a5f 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -1042,8 +1042,24 @@ static int empty_curbuf(int close_others, int forceit, int action)
set_bufref(&bufref, buf);
if (close_others) {
- // Close any other windows on this buffer, then make it empty.
- close_windows(buf, true);
+ bool can_close_all_others = true;
+ if (curwin->w_floating) {
+ // Closing all other windows with this buffer may leave only floating windows.
+ can_close_all_others = false;
+ for (win_T *wp = firstwin; !wp->w_floating; wp = wp->w_next) {
+ if (wp->w_buffer != curbuf) {
+ // Found another non-floating window with a different (probably unlisted) buffer.
+ // Closing all other windows with this buffer is fine in this case.
+ can_close_all_others = true;
+ break;
+ }
+ }
+ }
+ // If it is fine to close all other windows with this buffer, keep the current window and
+ // close any other windows with this buffer, then make it empty.
+ // Otherwise close_windows() will refuse to close the last non-floating window, so allow it
+ // to close the current window instead.
+ close_windows(buf, can_close_all_others);
}
setpcmark();
@@ -1224,11 +1240,12 @@ int do_buffer(int action, int start, int dir, int count, int forceit)
}
// If the deleted buffer is the current one, close the current window
- // (unless it's the only window). Repeat this so long as we end up in
- // a window with this buffer.
+ // (unless it's the only non-floating window).
+ // When the autocommand window is involved win_close() may need to print an error message.
+ // Repeat this so long as we end up in a window with this buffer.
while (buf == curbuf
&& !(curwin->w_closing || curwin->w_buffer->b_locked > 0)
- && (!ONE_WINDOW || first_tabpage->tp_next != NULL)) {
+ && (lastwin == aucmd_win || !last_window(curwin))) {
if (win_close(curwin, false, false) == FAIL) {
break;
}
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index f9541a55a3..29413281ad 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -90,7 +90,6 @@ typedef struct {
#define BF_NEW_W 0x20 // Warned for BF_NEW and file created
#define BF_READERR 0x40 // got errors while reading the file
#define BF_DUMMY 0x80 // dummy buffer, only used internally
-#define BF_PRESERVED 0x100 // ":preserve" was used
#define BF_SYN_SET 0x200 // 'syntax' option was set
// Mask to check for flags that prevent normal writing
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 7f937a3137..fbbc543893 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -3976,18 +3976,11 @@ static int eval6(char_u **arg, typval_T *rettv, int evaluate, int want_string)
f1 = f1 * f2;
} else if (op == '/') {
// Division by zero triggers error from AddressSanitizer
- f1 = (f2 == 0
- ? (
+ f1 = (f2 == 0 ? (
#ifdef NAN
- f1 == 0
- ? NAN
- :
+ f1 == 0 ? (float_T)NAN :
#endif
- (f1 > 0
- ? INFINITY
- : -INFINITY)
- )
- : f1 / f2);
+ (f1 > 0 ? (float_T)INFINITY : (float_T)-INFINITY)) : f1 / f2);
} else {
emsg(_("E804: Cannot use '%' with Float"));
return FAIL;
@@ -5842,15 +5835,15 @@ size_t string2float(const char *const text, float_T *const ret_value)
// MS-Windows does not deal with "inf" and "nan" properly
if (STRNICMP(text, "inf", 3) == 0) {
- *ret_value = INFINITY;
+ *ret_value = (float_T)INFINITY;
return 3;
}
if (STRNICMP(text, "-inf", 3) == 0) {
- *ret_value = -INFINITY;
+ *ret_value = (float_T)-INFINITY;
return 4;
}
if (STRNICMP(text, "nan", 3) == 0) {
- *ret_value = NAN;
+ *ret_value = (float_T)NAN;
return 3;
}
*ret_value = strtod(text, &s);
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 7ee32ec8cd..41b419c150 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -39,6 +39,7 @@
#include "nvim/lua/executor.h"
#include "nvim/macros.h"
#include "nvim/mark.h"
+#include "nvim/match.h"
#include "nvim/math.h"
#include "nvim/memline.h"
#include "nvim/mouse.h"
@@ -9251,272 +9252,6 @@ static void f_shiftwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->vval.v_number = get_sw_value(curbuf);
}
-/// "sign_define()" function
-static void f_sign_define(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *name;
-
- if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_UNKNOWN) {
- // Define multiple signs
- tv_list_alloc_ret(rettv, kListLenMayKnow);
-
- sign_define_multiple(argvars[0].vval.v_list, rettv->vval.v_list);
- return;
- }
-
- // Define a single sign
- rettv->vval.v_number = -1;
-
- name = tv_get_string_chk(&argvars[0]);
- if (name == NULL) {
- return;
- }
-
- if (argvars[1].v_type != VAR_UNKNOWN && argvars[1].v_type != VAR_DICT) {
- emsg(_(e_dictreq));
- return;
- }
-
- rettv->vval.v_number = sign_define_from_dict(name,
- argvars[1].v_type ==
- VAR_DICT ? argvars[1].vval.v_dict : NULL);
-}
-
-/// "sign_getdefined()" function
-static void f_sign_getdefined(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *name = NULL;
-
- tv_list_alloc_ret(rettv, 0);
-
- if (argvars[0].v_type != VAR_UNKNOWN) {
- name = tv_get_string(&argvars[0]);
- }
-
- sign_getlist((const char_u *)name, rettv->vval.v_list);
-}
-
-/// "sign_getplaced()" function
-static void f_sign_getplaced(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- buf_T *buf = NULL;
- dict_T *dict;
- dictitem_T *di;
- linenr_T lnum = 0;
- int sign_id = 0;
- const char *group = NULL;
- bool notanum = false;
-
- tv_list_alloc_ret(rettv, 0);
-
- if (argvars[0].v_type != VAR_UNKNOWN) {
- // get signs placed in the specified buffer
- buf = get_buf_arg(&argvars[0]);
- if (buf == NULL) {
- return;
- }
-
- if (argvars[1].v_type != VAR_UNKNOWN) {
- if (argvars[1].v_type != VAR_DICT
- || ((dict = argvars[1].vval.v_dict) == NULL)) {
- emsg(_(e_dictreq));
- return;
- }
- if ((di = tv_dict_find(dict, "lnum", -1)) != NULL) {
- // get signs placed at this line
- lnum = (linenr_T)tv_get_number_chk(&di->di_tv, &notanum);
- if (notanum) {
- return;
- }
- (void)lnum;
- lnum = tv_get_lnum(&di->di_tv);
- }
- if ((di = tv_dict_find(dict, "id", -1)) != NULL) {
- // get sign placed with this identifier
- sign_id = (int)tv_get_number_chk(&di->di_tv, &notanum);
- if (notanum) {
- return;
- }
- }
- if ((di = tv_dict_find(dict, "group", -1)) != NULL) {
- group = tv_get_string_chk(&di->di_tv);
- if (group == NULL) {
- return;
- }
- if (*group == '\0') { // empty string means global group
- group = NULL;
- }
- }
- }
- }
-
- sign_get_placed(buf, lnum, sign_id, (const char_u *)group,
- rettv->vval.v_list);
-}
-
-/// "sign_jump()" function
-static void f_sign_jump(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int sign_id;
- char *sign_group = NULL;
- buf_T *buf;
- bool notanum = false;
-
- rettv->vval.v_number = -1;
-
- // Sign identifier
- sign_id = (int)tv_get_number_chk(&argvars[0], &notanum);
- if (notanum) {
- return;
- }
- if (sign_id <= 0) {
- emsg(_(e_invarg));
- return;
- }
-
- // Sign group
- const char *sign_group_chk = tv_get_string_chk(&argvars[1]);
- if (sign_group_chk == NULL) {
- return;
- }
- if (sign_group_chk[0] == '\0') {
- sign_group = NULL; // global sign group
- } else {
- sign_group = xstrdup(sign_group_chk);
- }
-
- // Buffer to place the sign
- buf = get_buf_arg(&argvars[2]);
- if (buf == NULL) {
- goto cleanup;
- }
-
- rettv->vval.v_number = sign_jump(sign_id, (char_u *)sign_group, buf);
-
-cleanup:
- xfree(sign_group);
-}
-
-/// "sign_place()" function
-static void f_sign_place(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- dict_T *dict = NULL;
-
- rettv->vval.v_number = -1;
-
- if (argvars[4].v_type != VAR_UNKNOWN
- && (argvars[4].v_type != VAR_DICT
- || ((dict = argvars[4].vval.v_dict) == NULL))) {
- emsg(_(e_dictreq));
- return;
- }
-
- rettv->vval.v_number = sign_place_from_dict(&argvars[0], &argvars[1], &argvars[2], &argvars[3],
- dict);
-}
-
-/// "sign_placelist()" function. Place multiple signs.
-static void f_sign_placelist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int sign_id;
-
- tv_list_alloc_ret(rettv, kListLenMayKnow);
-
- if (argvars[0].v_type != VAR_LIST) {
- emsg(_(e_listreq));
- return;
- }
-
- // Process the List of sign attributes
- TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, {
- sign_id = -1;
- if (TV_LIST_ITEM_TV(li)->v_type == VAR_DICT) {
- sign_id = sign_place_from_dict(NULL, NULL, NULL, NULL, TV_LIST_ITEM_TV(li)->vval.v_dict);
- } else {
- emsg(_(e_dictreq));
- }
- tv_list_append_number(rettv->vval.v_list, sign_id);
- });
-}
-
-/// "sign_undefine()" function
-static void f_sign_undefine(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *name;
-
- if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_UNKNOWN) {
- // Undefine multiple signs
- tv_list_alloc_ret(rettv, kListLenMayKnow);
-
- sign_undefine_multiple(argvars[0].vval.v_list, rettv->vval.v_list);
- return;
- }
-
- rettv->vval.v_number = -1;
-
- if (argvars[0].v_type == VAR_UNKNOWN) {
- // Free all the signs
- free_signs();
- rettv->vval.v_number = 0;
- } else {
- // Free only the specified sign
- name = tv_get_string_chk(&argvars[0]);
- if (name == NULL) {
- return;
- }
-
- if (sign_undefine_by_name((const char_u *)name) == OK) {
- rettv->vval.v_number = 0;
- }
- }
-}
-
-/// "sign_unplace()" function
-static void f_sign_unplace(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- dict_T *dict = NULL;
-
- rettv->vval.v_number = -1;
-
- if (argvars[0].v_type != VAR_STRING) {
- emsg(_(e_invarg));
- return;
- }
-
- if (argvars[1].v_type != VAR_UNKNOWN) {
- if (argvars[1].v_type != VAR_DICT) {
- emsg(_(e_dictreq));
- return;
- }
- dict = argvars[1].vval.v_dict;
- }
-
- rettv->vval.v_number = sign_unplace_from_dict(&argvars[0], dict);
-}
-
-/// "sign_unplacelist()" function
-static void f_sign_unplacelist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int retval;
-
- tv_list_alloc_ret(rettv, kListLenMayKnow);
-
- if (argvars[0].v_type != VAR_LIST) {
- emsg(_(e_listreq));
- return;
- }
-
- TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, {
- retval = -1;
- if (TV_LIST_ITEM_TV(li)->v_type == VAR_DICT) {
- retval = sign_unplace_from_dict(NULL, TV_LIST_ITEM_TV(li)->vval.v_dict);
- } else {
- emsg(_(e_dictreq));
- }
- tv_list_append_number(rettv->vval.v_list, retval);
- });
-}
-
/// "simplify()" function
static void f_simplify(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c
index 471c4092fe..eb5c6e503a 100644
--- a/src/nvim/eval/userfunc.c
+++ b/src/nvim/eval/userfunc.c
@@ -201,7 +201,7 @@ static void register_closure(ufunc_T *fp)
}
-/// Get a name for a lambda. Returned in static memory.
+/// @return a name for a lambda. Returned in static memory.
char_u *get_lambda_name(void)
{
static char_u name[30];
@@ -544,7 +544,8 @@ static char_u *fname_trans_sid(const char_u *const name, char_u *const fname_buf
}
/// Find a function by name, return pointer to it in ufuncs.
-/// @return NULL for unknown function.
+///
+/// @return NULL for unknown function.
ufunc_T *find_func(const char_u *name)
{
hashitem_T *hi;
@@ -556,11 +557,9 @@ ufunc_T *find_func(const char_u *name)
return NULL;
}
-/*
- * Copy the function name of "fp" to buffer "buf".
- * "buf" must be able to hold the function name plus three bytes.
- * Takes care of script-local function names.
- */
+/// Copy the function name of "fp" to buffer "buf".
+/// "buf" must be able to hold the function name plus three bytes.
+/// Takes care of script-local function names.
static void cat_func_name(char_u *buf, ufunc_T *fp)
{
if (fp->uf_name[0] == K_SPECIAL) {
@@ -571,9 +570,7 @@ static void cat_func_name(char_u *buf, ufunc_T *fp)
}
}
-/*
- * Add a number variable "name" to dict "dp" with value "nr".
- */
+/// Add a number variable "name" to dict "dp" with value "nr".
static void add_nr_var(dict_T *dp, dictitem_T *v, char *name, varnumber_T nr)
{
#ifndef __clang_analyzer__
@@ -586,7 +583,7 @@ static void add_nr_var(dict_T *dp, dictitem_T *v, char *name, varnumber_T nr)
v->di_tv.vval.v_number = nr;
}
-// Free "fc"
+/// Free "fc"
static void free_funccal(funccall_T *fc)
{
for (int i = 0; i < fc->fc_funcs.ga_len; i++) {
@@ -606,9 +603,9 @@ static void free_funccal(funccall_T *fc)
xfree(fc);
}
-// Free "fc" and what it contains.
-// Can be called only when "fc" is kept beyond the period of it called,
-// i.e. after cleanup_function_call(fc).
+/// Free "fc" and what it contains.
+/// Can be called only when "fc" is kept beyond the period of it called,
+/// i.e. after cleanup_function_call(fc).
static void free_funccal_contents(funccall_T *fc)
{
// Free all l: variables.
@@ -757,7 +754,7 @@ static void func_clear_items(ufunc_T *fp)
/// Free all things that a function contains. Does not free the function
/// itself, use func_free() for that.
///
-/// param[in] force When true, we are exiting.
+/// @param[in] force When true, we are exiting.
static void func_clear(ufunc_T *fp, bool force)
{
if (fp->uf_cleared) {
@@ -773,7 +770,7 @@ static void func_clear(ufunc_T *fp, bool force)
/// Free a function and remove it from the list of functions. Does not free
/// what a function contains, call func_clear() first.
///
-/// param[in] fp The function to free.
+/// @param[in] fp The function to free.
static void func_free(ufunc_T *fp)
{
// only remove it when not done already, otherwise we would remove a newer
@@ -786,7 +783,7 @@ static void func_free(ufunc_T *fp)
/// Free all things that a function contains and free the function itself.
///
-/// param[in] force When true, we are exiting.
+/// @param[in] force When true, we are exiting.
static void func_clear_free(ufunc_T *fp, bool force)
{
func_clear(fp, force);
@@ -795,13 +792,13 @@ static void func_clear_free(ufunc_T *fp, bool force)
/// Call a user function
///
-/// @param fp Function to call.
-/// @param[in] argcount Number of arguments.
-/// @param argvars Arguments.
-/// @param[out] rettv Return value.
-/// @param[in] firstline First line of range.
-/// @param[in] lastline Last line of range.
-/// @param selfdict Dictionary for "self" for dictionary functions.
+/// @param fp Function to call.
+/// @param[in] argcount Number of arguments.
+/// @param argvars Arguments.
+/// @param[out] rettv Return value.
+/// @param[in] firstline First line of range.
+/// @param[in] lastline Last line of range.
+/// @param selfdict Dictionary for "self" for dictionary functions.
void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rettv,
linenr_T firstline, linenr_T lastline, dict_T *selfdict)
FUNC_ATTR_NONNULL_ARG(1, 3, 4)
@@ -1230,8 +1227,8 @@ static bool func_name_refcount(char_u *name)
static funccal_entry_T *funccal_stack = NULL;
-// Save the current function call pointer, and set it to NULL.
-// Used when executing autocommands and for ":source".
+/// Save the current function call pointer, and set it to NULL.
+/// Used when executing autocommands and for ":source".
void save_funccal(funccal_entry_T *entry)
{
entry->top_funccal = current_funccal;
@@ -1384,8 +1381,8 @@ func_call_skip_call:
return r;
}
-// Give an error message for the result of a function.
-// Nothing if "error" is FCERR_NONE.
+/// Give an error message for the result of a function.
+/// Nothing if "error" is FCERR_NONE.
static void user_func_error(int error, const char_u *name)
FUNC_ATTR_NONNULL_ALL
{
@@ -1902,9 +1899,7 @@ theend:
return name;
}
-/*
- * ":function"
- */
+/// ":function"
void ex_function(exarg_T *eap)
{
char_u *theline;
@@ -2595,11 +2590,9 @@ ret_free:
}
} // NOLINT(readability/fn_size)
-/*
- * Return 5 if "p" starts with "<SID>" or "<SNR>" (ignoring case).
- * Return 2 if "p" starts with "s:".
- * Return 0 otherwise.
- */
+/// @return 5 if "p" starts with "<SID>" or "<SNR>" (ignoring case).
+/// 2 if "p" starts with "s:".
+/// 0 otherwise.
int eval_fname_script(const char *const p)
{
// Use mb_strnicmp() because in Turkish comparing the "I" may not work with
@@ -2625,10 +2618,10 @@ bool translated_function_exists(const char *name)
/// Check whether function with the given name exists
///
-/// @param[in] name Function name.
-/// @param[in] no_deref Whether to dereference a Funcref.
+/// @param[in] name Function name.
+/// @param[in] no_deref Whether to dereference a Funcref.
///
-/// @return True if it exists, false otherwise.
+/// @return true if it exists, false otherwise.
bool function_exists(const char *const name, bool no_deref)
{
const char_u *nm = (const char_u *)name;
@@ -2651,10 +2644,8 @@ bool function_exists(const char *const name, bool no_deref)
return n;
}
-/*
- * Function given to ExpandGeneric() to obtain the list of user defined
- * function names.
- */
+/// Function given to ExpandGeneric() to obtain the list of user defined
+/// function names.
char_u *get_user_func_name(expand_T *xp, int idx)
{
static size_t done;
@@ -2771,10 +2762,8 @@ void ex_delfunction(exarg_T *eap)
}
}
-/*
- * Unreference a Function: decrement the reference count and free it when it
- * becomes zero.
- */
+/// Unreference a Function: decrement the reference count and free it when it
+/// becomes zero.
void func_unref(char_u *name)
{
ufunc_T *fp = NULL;
@@ -2868,9 +2857,7 @@ static int can_free_funccal(funccall_T *fc, int copyID)
&& fc->fc_copyID != copyID;
}
-/*
- * ":return [expr]"
- */
+/// ":return [expr]"
void ex_return(exarg_T *eap)
{
char_u *arg = eap->arg;
@@ -2921,9 +2908,7 @@ void ex_return(exarg_T *eap)
// TODO(ZyX-I): move to eval/ex_cmds
-/*
- * ":1,25call func(arg1, arg2)" function call.
- */
+/// ":1,25call func(arg1, arg2)" function call.
void ex_call(exarg_T *eap)
{
char_u *arg = eap->arg;
@@ -3050,14 +3035,16 @@ end:
xfree(tofree);
}
-/*
- * Return from a function. Possibly makes the return pending. Also called
- * for a pending return at the ":endtry" or after returning from an extra
- * do_cmdline(). "reanimate" is used in the latter case. "is_cmd" is set
- * when called due to a ":return" command. "rettv" may point to a typval_T
- * with the return rettv. Returns TRUE when the return can be carried out,
- * FALSE when the return gets pending.
- */
+/// Return from a function. Possibly makes the return pending. Also called
+/// for a pending return at the ":endtry" or after returning from an extra
+/// do_cmdline(). "reanimate" is used in the latter case.
+///
+/// @param reanimate used after returning from an extra do_cmdline().
+/// @param is_cmd set when called due to a ":return" command.
+/// @param rettv may point to a typval_T with the return rettv.
+///
+/// @return TRUE when the return can be carried out,
+/// FALSE when the return gets pending.
int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv)
{
int idx;
@@ -3068,12 +3055,10 @@ int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv)
current_funccal->returned = false;
}
- //
// Cleanup (and deactivate) conditionals, but stop when a try conditional
// not in its finally clause (which then is to be executed next) is found.
// In this case, make the ":return" pending for execution at the ":endtry".
// Otherwise, return normally.
- //
idx = cleanup_conditionals(eap->cstack, 0, true);
if (idx >= 0) {
cstack->cs_pending[idx] = CSTP_RETURN;
@@ -3126,10 +3111,8 @@ int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv)
return idx < 0;
}
-/*
- * Generate a return command for producing the value of "rettv". The result
- * is an allocated string. Used by report_pending() for verbose messages.
- */
+/// Generate a return command for producing the value of "rettv". The result
+/// is an allocated string. Used by report_pending() for verbose messages.
char_u *get_return_cmd(void *rettv)
{
char_u *s = NULL;
@@ -3151,11 +3134,10 @@ char_u *get_return_cmd(void *rettv)
return vim_strsave(IObuff);
}
-/*
- * Get next function line.
- * Called by do_cmdline() to get the next line.
- * Returns allocated string, or NULL for end of function.
- */
+/// Get next function line.
+/// Called by do_cmdline() to get the next line.
+///
+/// @return allocated string, or NULL for end of function.
char_u *get_func_line(int c, void *cookie, int indent, bool do_concat)
{
funccall_T *fcp = (funccall_T *)cookie;
@@ -3206,10 +3188,8 @@ char_u *get_func_line(int c, void *cookie, int indent, bool do_concat)
return retval;
}
-/*
- * Return TRUE if the currently active function should be ended, because a
- * return was encountered or an error occurred. Used inside a ":while".
- */
+/// @return TRUE if the currently active function should be ended, because a
+/// return was encountered or an error occurred. Used inside a ":while".
int func_has_ended(void *cookie)
{
funccall_T *fcp = (funccall_T *)cookie;
@@ -3220,9 +3200,7 @@ int func_has_ended(void *cookie)
|| fcp->returned;
}
-/*
- * return TRUE if cookie indicates a function which "abort"s on errors.
- */
+/// @return TRUE if cookie indicates a function which "abort"s on errors.
int func_has_abort(void *cookie)
{
return ((funccall_T *)cookie)->func->uf_flags & FC_ABORT;
@@ -3289,41 +3267,31 @@ void make_partial(dict_T *const selfdict, typval_T *const rettv)
}
}
-/*
- * Return the name of the executed function.
- */
+/// @return the name of the executed function.
char_u *func_name(void *cookie)
{
return ((funccall_T *)cookie)->func->uf_name;
}
-/*
- * Return the address holding the next breakpoint line for a funccall cookie.
- */
+/// @return the address holding the next breakpoint line for a funccall cookie.
linenr_T *func_breakpoint(void *cookie)
{
return &((funccall_T *)cookie)->breakpoint;
}
-/*
- * Return the address holding the debug tick for a funccall cookie.
- */
+/// @return the address holding the debug tick for a funccall cookie.
int *func_dbg_tick(void *cookie)
{
return &((funccall_T *)cookie)->dbg_tick;
}
-/*
- * Return the nesting level for a funccall cookie.
- */
+/// @return the nesting level for a funccall cookie.
int func_level(void *cookie)
{
return ((funccall_T *)cookie)->level;
}
-/*
- * Return TRUE when a function was ended by a ":return" command.
- */
+/// @return TRUE when a function was ended by a ":return" command.
int current_func_returned(void)
{
return current_funccal->returned;
@@ -3372,8 +3340,8 @@ funccall_T *get_funccal(void)
return funccal;
}
-/// Return the hashtable used for local variables in the current funccal.
-/// Return NULL if there is no current funccal.
+/// @return hashtable used for local variables in the current funccal or
+/// NULL if there is no current funccal.
hashtab_T *get_funccal_local_ht(void)
{
if (current_funccal == NULL) {
@@ -3382,8 +3350,8 @@ hashtab_T *get_funccal_local_ht(void)
return &get_funccal()->l_vars.dv_hashtab;
}
-/// Return the l: scope variable.
-/// Return NULL if there is no current funccal.
+/// @return the l: scope variable or
+/// NULL if there is no current funccal.
dictitem_T *get_funccal_local_var(void)
{
if (current_funccal == NULL) {
@@ -3392,8 +3360,8 @@ dictitem_T *get_funccal_local_var(void)
return (dictitem_T *)&get_funccal()->l_vars_var;
}
-/// Return the hashtable used for argument in the current funccal.
-/// Return NULL if there is no current funccal.
+/// @return the hashtable used for argument in the current funccal or
+/// NULL if there is no current funccal.
hashtab_T *get_funccal_args_ht(void)
{
if (current_funccal == NULL) {
@@ -3402,8 +3370,8 @@ hashtab_T *get_funccal_args_ht(void)
return &get_funccal()->l_avars.dv_hashtab;
}
-/// Return the a: scope variable.
-/// Return NULL if there is no current funccal.
+/// @return the a: scope variable or
+/// NULL if there is no current funccal.
dictitem_T *get_funccal_args_var(void)
{
if (current_funccal == NULL) {
@@ -3412,9 +3380,7 @@ dictitem_T *get_funccal_args_var(void)
return (dictitem_T *)&current_funccal->l_avars_var;
}
-/*
- * List function variables, if there is a function.
- */
+/// List function variables, if there is a function.
void list_func_vars(int *first)
{
if (current_funccal != NULL) {
@@ -3423,9 +3389,8 @@ void list_func_vars(int *first)
}
}
-/// If "ht" is the hashtable for local variables in the current funccal, return
-/// the dict that contains it.
-/// Otherwise return NULL.
+/// @return if "ht" is the hashtable for local variables in the current
+/// funccal, return the dict that contains it. Otherwise return NULL.
dict_T *get_current_funccal_dict(hashtab_T *ht)
{
if (current_funccal != NULL && ht == &current_funccal->l_vars.dv_hashtab) {
@@ -3589,7 +3554,7 @@ bool set_ref_in_func_args(int copyID)
/// "list_stack" is used to add lists to be marked. Can be NULL.
/// "ht_stack" is used to add hashtabs to be marked. Can be NULL.
///
-/// @return true if setting references failed somehow.
+/// @return true if setting references failed somehow.
bool set_ref_in_func(char_u *name, ufunc_T *fp_in, int copyID)
{
ufunc_T *fp = fp_in;
diff --git a/src/nvim/event/process.c b/src/nvim/event/process.c
index dae4dad16d..653fffae1c 100644
--- a/src/nvim/event/process.c
+++ b/src/nvim/event/process.c
@@ -237,7 +237,7 @@ void process_stop(Process *proc) FUNC_ATTR_NONNULL_ALL
KILL_TIMEOUT_MS, 0);
}
-// Frees process-owned resources.
+/// Frees process-owned resources.
void process_free(Process *proc) FUNC_ATTR_NONNULL_ALL
{
if (proc->argv != NULL) {
diff --git a/src/nvim/event/rstream.c b/src/nvim/event/rstream.c
index e51689543f..26b5ce3b75 100644
--- a/src/nvim/event/rstream.c
+++ b/src/nvim/event/rstream.c
@@ -85,7 +85,7 @@ static void on_rbuffer_nonfull(RBuffer *buf, void *data)
// Callbacks used by libuv
-// Called by libuv to allocate memory for reading.
+/// Called by libuv to allocate memory for reading.
static void alloc_cb(uv_handle_t *handle, size_t suggested, uv_buf_t *buf)
{
Stream *stream = handle->data;
@@ -95,9 +95,9 @@ static void alloc_cb(uv_handle_t *handle, size_t suggested, uv_buf_t *buf)
buf->len = UV_BUF_LEN(write_count);
}
-// Callback invoked by libuv after it copies the data into the buffer provided
-// by `alloc_cb`. This is also called on EOF or when `alloc_cb` returns a
-// 0-length buffer.
+/// Callback invoked by libuv after it copies the data into the buffer provided
+/// by `alloc_cb`. This is also called on EOF or when `alloc_cb` returns a
+/// 0-length buffer.
static void read_cb(uv_stream_t *uvstream, ssize_t cnt, const uv_buf_t *buf)
{
Stream *stream = uvstream->data;
@@ -134,11 +134,11 @@ static void read_cb(uv_stream_t *uvstream, ssize_t cnt, const uv_buf_t *buf)
invoke_read_cb(stream, nread, false);
}
-// Called by the by the 'idle' handle to emulate a reading event
-//
-// Idle callbacks are invoked once per event loop:
-// - to perform some very low priority activity.
-// - to keep the loop "alive" (so there is always an event to process)
+/// Called by the by the 'idle' handle to emulate a reading event
+///
+/// Idle callbacks are invoked once per event loop:
+/// - to perform some very low priority activity.
+/// - to keep the loop "alive" (so there is always an event to process)
static void fread_idle_cb(uv_idle_t *handle)
{
uv_fs_t req;
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index a5732a006d..0dbc9d6b14 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -44,6 +44,7 @@
#include "nvim/keymap.h"
#include "nvim/lua/executor.h"
#include "nvim/main.h"
+#include "nvim/match.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
@@ -6628,6 +6629,7 @@ static void ex_quit(exarg_T *eap)
/// ":cquit".
static void ex_cquit(exarg_T *eap)
+ FUNC_ATTR_NORETURN
{
// this does not always pass on the exit code to the Manx compiler. why?
getout(eap->addr_count > 0 ? (int)eap->line2 : EXIT_FAILURE);
@@ -7154,7 +7156,6 @@ void alist_slash_adjust(void)
/// ":preserve".
static void ex_preserve(exarg_T *eap)
{
- curbuf->b_flags |= BF_PRESERVED;
ml_preserve(curbuf, true, true);
}
diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c
index 0e2b7c0ece..39ff75d8c2 100644
--- a/src/nvim/ex_session.c
+++ b/src/nvim/ex_session.c
@@ -98,10 +98,11 @@ static int ses_winsizes(FILE *fd, int restore_size, win_T *tab_firstwin)
return OK;
}
-// Write commands to "fd" to recursively create windows for frame "fr",
-// horizontally and vertically split.
-// After the commands the last window in the frame is the current window.
-// Returns FAIL when writing the commands to "fd" fails.
+/// Write commands to "fd" to recursively create windows for frame "fr",
+/// horizontally and vertically split.
+/// After the commands the last window in the frame is the current window.
+///
+/// @return FAIL when writing the commands to "fd" fails.
static int ses_win_rec(FILE *fd, frame_T *fr)
{
frame_T *frc;
@@ -144,8 +145,9 @@ static int ses_win_rec(FILE *fd, frame_T *fr)
return OK;
}
-// Skip frames that don't contain windows we want to save in the Session.
-// Returns NULL when there none.
+/// Skip frames that don't contain windows we want to save in the Session.
+///
+/// @return NULL when there none.
static frame_T *ses_skipframe(frame_T *fr)
{
frame_T *frc;
@@ -158,8 +160,8 @@ static frame_T *ses_skipframe(frame_T *fr)
return frc;
}
-// Return true if frame "fr" has a window somewhere that we want to save in
-// the Session.
+/// @return true if frame "fr" has a window somewhere that we want to save in
+/// the Session.
static bool ses_do_frame(const frame_T *fr)
FUNC_ATTR_NONNULL_ARG(1)
{
@@ -176,7 +178,7 @@ static bool ses_do_frame(const frame_T *fr)
return false;
}
-/// Return non-zero if window "wp" is to be stored in the Session.
+/// @return non-zero if window "wp" is to be stored in the Session.
static int ses_do_win(win_T *wp)
{
if (wp->w_buffer->b_fname == NULL
@@ -229,7 +231,7 @@ static int ses_arglist(FILE *fd, char *cmd, garray_T *gap, int fullname, unsigne
return OK;
}
-/// Gets the buffer name for `buf`.
+/// @return the buffer name for `buf`.
static char *ses_get_fname(buf_T *buf, unsigned *flagp)
{
// Use the short file name if the current directory is known at the time
@@ -249,7 +251,8 @@ static char *ses_get_fname(buf_T *buf, unsigned *flagp)
/// Write a buffer name to the session file.
/// Also ends the line, if "add_eol" is true.
-/// Returns FAIL if writing fails.
+///
+/// @return FAIL if writing fails.
static int ses_fname(FILE *fd, buf_T *buf, unsigned *flagp, bool add_eol)
{
char *name = ses_get_fname(buf, flagp);
@@ -260,11 +263,11 @@ static int ses_fname(FILE *fd, buf_T *buf, unsigned *flagp, bool add_eol)
return OK;
}
-// Escapes a filename for session writing.
-// Takes care of "slash" flag in 'sessionoptions' and escapes special
-// characters.
-//
-// Returns allocated string or NULL.
+/// Escapes a filename for session writing.
+/// Takes care of "slash" flag in 'sessionoptions' and escapes special
+/// characters.
+///
+/// @return allocated string or NULL.
static char *ses_escape_fname(char *name, unsigned *flagp)
{
char *p;
@@ -283,10 +286,11 @@ static char *ses_escape_fname(char *name, unsigned *flagp)
return p;
}
-// Write a file name to the session file.
-// Takes care of the "slash" option in 'sessionoptions' and escapes special
-// characters.
-// Returns FAIL if writing fails.
+/// Write a file name to the session file.
+/// Takes care of the "slash" option in 'sessionoptions' and escapes special
+/// characters.
+///
+/// @return FAIL if writing fails.
static int ses_put_fname(FILE *fd, char_u *name, unsigned *flagp)
{
char *p = ses_escape_fname((char *)name, flagp);
@@ -1052,7 +1056,7 @@ void ex_mkrc(exarg_T *eap)
xfree(viewFile);
}
-/// Get the name of the view file for the current buffer.
+/// @return the name of the view file for the current buffer.
static char *get_view_file(int c)
{
if (curbuf->b_ffname == NULL) {
@@ -1100,7 +1104,7 @@ static char *get_view_file(int c)
return retval;
}
-// TODO(justinmk): remove this, not needed after 5ba3cecb68cd.
+/// TODO(justinmk): remove this, not needed after 5ba3cecb68cd.
int put_eol(FILE *fd)
{
if (putc('\n', fd) < 0) {
@@ -1109,7 +1113,7 @@ int put_eol(FILE *fd)
return OK;
}
-// TODO(justinmk): remove this, not needed after 5ba3cecb68cd.
+/// TODO(justinmk): remove this, not needed after 5ba3cecb68cd.
int put_line(FILE *fd, char *s)
{
if (fprintf(fd, "%s\n", s) < 0) {
diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c
index 6b879f5139..a3fcc7d784 100644
--- a/src/nvim/extmark.c
+++ b/src/nvim/extmark.c
@@ -175,8 +175,9 @@ static bool extmark_setraw(buf_T *buf, uint64_t mark, int row, colnr_T col)
return true;
}
-// Remove an extmark
-// Returns 0 on missing id
+/// Remove an extmark
+///
+/// @return 0 on missing id
bool extmark_del(buf_T *buf, uint32_t ns_id, uint32_t id)
{
MarkTreeIter itr[1] = { 0 };
@@ -203,8 +204,8 @@ bool extmark_del(buf_T *buf, uint32_t ns_id, uint32_t id)
return true;
}
-// Free extmarks in a ns between lines
-// if ns = 0, it means clear all namespaces
+/// Free extmarks in a ns between lines
+/// if ns = 0, it means clear all namespaces
bool extmark_clear(buf_T *buf, uint32_t ns_id, int l_row, colnr_T l_col, int u_row, colnr_T u_col)
{
if (!map_size(buf->b_extmark_ns)) {
@@ -287,12 +288,13 @@ bool extmark_clear(buf_T *buf, uint32_t ns_id, int l_row, colnr_T l_col, int u_r
return marks_cleared;
}
-// Returns the position of marks between a range,
-// marks found at the start or end index will be included,
-// if upper_lnum or upper_col are negative the buffer
-// will be searched to the start, or end
-// dir can be set to control the order of the array
-// amount = amount of marks to find or -1 for all
+/// @return the position of marks between a range,
+/// marks found at the start or end index will be included.
+///
+/// if upper_lnum or upper_col are negative the buffer
+/// will be searched to the start, or end
+/// dir can be set to control the order of the array
+/// amount = amount of marks to find or -1 for all
ExtmarkInfoArray extmark_get(buf_T *buf, uint32_t ns_id, int l_row, colnr_T l_col, int u_row,
colnr_T u_col, int64_t amount, bool reverse)
{
@@ -334,7 +336,7 @@ next_mark:
return array;
}
-// Lookup an extmark by id
+/// Lookup an extmark by id
ExtmarkInfo extmark_from_id(buf_T *buf, uint32_t ns_id, uint32_t id)
{
ExtmarkInfo ret = { 0, 0, -1, -1, -1, -1, false, false, DECORATION_INIT };
@@ -359,7 +361,7 @@ ExtmarkInfo extmark_from_id(buf_T *buf, uint32_t ns_id, uint32_t id)
}
-// free extmarks from the buffer
+/// free extmarks from the buffer
void extmark_free_all(buf_T *buf)
{
if (!map_size(buf->b_extmark_ns)) {
@@ -389,7 +391,7 @@ void extmark_free_all(buf_T *buf)
}
-// Save info for undo/redo of set marks
+/// Save info for undo/redo of set marks
static void u_extmark_set(buf_T *buf, uint64_t mark, int row, colnr_T col)
{
u_header_T *uhp = u_force_get_undo_header(buf);
@@ -499,7 +501,7 @@ void extmark_apply_undo(ExtmarkUndoObject undo_info, bool undo)
}
-// Adjust extmark row for inserted/deleted rows (columns stay fixed).
+/// Adjust extmark row for inserted/deleted rows (columns stay fixed).
void extmark_adjust(buf_T *buf, linenr_T line1, linenr_T line2, long amount, long amount_after,
ExtmarkOp undo)
{
diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c
index d0f7a91d6c..15f1d3d065 100644
--- a/src/nvim/file_search.c
+++ b/src/nvim/file_search.c
@@ -529,9 +529,7 @@ error_return:
return NULL;
}
-/*
- * Get the stopdir string. Check that ';' is not escaped.
- */
+/// @return the stopdir string. Check that ';' is not escaped.
char_u *vim_findfile_stopdir(char_u *buf)
{
char_u *r_ptr = buf;
@@ -554,9 +552,7 @@ char_u *vim_findfile_stopdir(char_u *buf)
return r_ptr;
}
-/*
- * Clean up the given search context. Can handle a NULL pointer.
- */
+/// Clean up the given search context. Can handle a NULL pointer.
void vim_findfile_cleanup(void *ctx)
{
if (ctx == NULL) {
@@ -568,18 +564,19 @@ void vim_findfile_cleanup(void *ctx)
xfree(ctx);
}
-/*
- * Find a file in a search context.
- * The search context was created with vim_findfile_init() above.
- * Return a pointer to an allocated file name or NULL if nothing found.
- * To get all matching files call this function until you get NULL.
- *
- * If the passed search_context is NULL, NULL is returned.
- *
- * The search algorithm is depth first. To change this replace the
- * stack with a list (don't forget to leave partly searched directories on the
- * top of the list).
- */
+/// Find a file in a search context.
+/// The search context was created with vim_findfile_init() above.
+///
+/// To get all matching files call this function until you get NULL.
+///
+/// If the passed search_context is NULL, NULL is returned.
+///
+/// The search algorithm is depth first. To change this replace the
+/// stack with a list (don't forget to leave partly searched directories on the
+/// top of the list).
+///
+/// @return a pointer to an allocated file name or,
+/// NULL if nothing found.
char_u *vim_findfile(void *search_ctx_arg)
{
char_u *file_path;
@@ -999,10 +996,8 @@ fail:
return NULL;
}
-/*
- * Free the list of lists of visited files and directories
- * Can handle it if the passed search_context is NULL;
- */
+/// Free the list of lists of visited files and directories
+/// Can handle it if the passed search_context is NULL;
void vim_findfile_free_visited(void *search_ctx_arg)
{
ff_search_ctx_T *search_ctx;
@@ -1044,10 +1039,8 @@ static void ff_free_visited_list(ff_visited_T *vl)
vl = NULL;
}
-/*
- * Returns the already visited list for the given filename. If none is found it
- * allocates a new one.
- */
+/// @return the already visited list for the given filename. If none is found it
+/// allocates a new one.
static ff_visited_list_hdr_T *ff_get_visited_list(char_u *filename,
ff_visited_list_hdr_T **list_headp)
{
@@ -1094,13 +1087,13 @@ static ff_visited_list_hdr_T *ff_get_visited_list(char_u *filename,
return retptr;
}
-// Check if two wildcard paths are equal.
-// They are equal if:
-// - both paths are NULL
-// - they have the same length
-// - char by char comparison is OK
-// - the only differences are in the counters behind a '**', so
-// '**\20' is equal to '**\24'
+/// Check if two wildcard paths are equal.
+/// They are equal if:
+/// - both paths are NULL
+/// - they have the same length
+/// - char by char comparison is OK
+/// - the only differences are in the counters behind a '**', so
+/// '**\20' is equal to '**\24'
static bool ff_wc_equal(char_u *s1, char_u *s2)
{
int i, j;
@@ -1134,11 +1127,10 @@ static bool ff_wc_equal(char_u *s1, char_u *s2)
return s1[i] == s2[j];
}
-/*
- * maintains the list of already visited files and dirs
- * returns FAIL if the given file/dir is already in the list
- * returns OK if it is newly added
- */
+/// maintains the list of already visited files and dirs
+///
+/// @return FAIL if the given file/dir is already in the list or,
+/// OK if it is newly added
static int ff_check_visited(ff_visited_T **visited_list, char_u *fname, char_u *wc_path)
{
ff_visited_T *vp;
@@ -1196,9 +1188,7 @@ static int ff_check_visited(ff_visited_T **visited_list, char_u *fname, char_u *
return OK;
}
-/*
- * create stack element from given path pieces
- */
+/// create stack element from given path pieces
static ff_stack_T *ff_create_stack_element(char_u *fix_part, char_u *wc_part, int level,
int star_star_empty)
{
@@ -1226,9 +1216,7 @@ static ff_stack_T *ff_create_stack_element(char_u *fix_part, char_u *wc_part, in
return new;
}
-/*
- * Push a dir on the directory stack.
- */
+/// Push a dir on the directory stack.
static void ff_push(ff_search_ctx_T *search_ctx, ff_stack_T *stack_ptr)
{
/* check for NULL pointer, not to return an error to the user, but
@@ -1239,10 +1227,9 @@ static void ff_push(ff_search_ctx_T *search_ctx, ff_stack_T *stack_ptr)
}
}
-/*
- * Pop a dir from the directory stack.
- * Returns NULL if stack is empty.
- */
+/// Pop a dir from the directory stack.
+///
+/// @return NULL if stack is empty.
static ff_stack_T *ff_pop(ff_search_ctx_T *search_ctx)
{
ff_stack_T *sptr;
@@ -1255,9 +1242,7 @@ static ff_stack_T *ff_pop(ff_search_ctx_T *search_ctx)
return sptr;
}
-/*
- * free the given stack element
- */
+/// free the given stack element
static void ff_free_stack_element(ff_stack_T *const stack_ptr)
{
if (stack_ptr == NULL) {
@@ -1275,9 +1260,7 @@ static void ff_free_stack_element(ff_stack_T *const stack_ptr)
xfree(stack_ptr);
}
-/*
- * Clear the search context, but NOT the visited list.
- */
+/// Clear the search context, but NOT the visited list.
static void ff_clear(ff_search_ctx_T *search_ctx)
{
ff_stack_T *sptr;
@@ -1311,10 +1294,9 @@ static void ff_clear(ff_search_ctx_T *search_ctx)
search_ctx->ffsc_level = 0;
}
-/*
- * check if the given path is in the stopdirs
- * returns TRUE if yes else FALSE
- */
+/// check if the given path is in the stopdirs
+///
+/// @return TRUE if yes else FALSE
static int ff_path_in_stoplist(char_u *path, int path_len, char_u **stopdirs_v)
{
int i = 0;
@@ -1670,7 +1652,8 @@ void do_autocmd_dirchanged(char *new_dir, CdScope scope, CdCause cause, bool pre
/// Change to a file's directory.
/// Caller must call shorten_fnames()!
-/// @return OK or FAIL
+///
+/// @return OK or FAIL
int vim_chdirfile(char_u *fname, CdCause cause)
{
char dir[MAXPATHL];
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index 41e67e5b3b..fe61a2fc90 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -2033,9 +2033,7 @@ void prep_exarg(exarg_T *eap, const buf_T *buf)
eap->forceit = FALSE;
}
-/*
- * Set default or forced 'fileformat' and 'binary'.
- */
+/// Set default or forced 'fileformat' and 'binary'.
void set_file_options(int set_options, exarg_T *eap)
{
// set default 'fileformat'
@@ -2056,9 +2054,7 @@ void set_file_options(int set_options, exarg_T *eap)
}
}
-/*
- * Set forced 'fileencoding'.
- */
+/// Set forced 'fileencoding'.
void set_forced_fenc(exarg_T *eap)
{
if (eap->force_enc != 0) {
@@ -2068,12 +2064,12 @@ void set_forced_fenc(exarg_T *eap)
}
}
-// Find next fileencoding to use from 'fileencodings'.
-// "pp" points to fenc_next. It's advanced to the next item.
-// When there are no more items, an empty string is returned and *pp is set to
-// NULL.
-// When *pp is not set to NULL, the result is in allocated memory and "alloced"
-// is set to true.
+/// Find next fileencoding to use from 'fileencodings'.
+/// "pp" points to fenc_next. It's advanced to the next item.
+/// When there are no more items, an empty string is returned and *pp is set to
+/// NULL.
+/// When *pp is not set to NULL, the result is in allocated memory and "alloced"
+/// is set to true.
static char_u *next_fenc(char_u **pp, bool *alloced)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
{
@@ -2149,10 +2145,8 @@ static char_u *readfile_charconvert(char_u *fname, char_u *fenc, int *fdp)
}
-/*
- * Read marks for the current buffer from the ShaDa file, when we support
- * buffer marks and the buffer has a name.
- */
+/// Read marks for the current buffer from the ShaDa file, when we support
+/// buffer marks and the buffer has a name.
static void check_marks_read(void)
{
if (!curbuf->b_marks_read && get_shada_parameter('\'') > 0
@@ -3761,10 +3755,8 @@ nofail:
#undef SET_ERRMSG_NUM
}
-/*
- * Set the name of the current buffer. Use when the buffer doesn't have a
- * name and a ":r" or ":w" command with a file name is used.
- */
+/// Set the name of the current buffer. Use when the buffer doesn't have a
+/// name and a ":r" or ":w" command with a file name is used.
static int set_rw_fname(char_u *fname, char_u *sfname)
{
buf_T *buf = curbuf;
@@ -3854,9 +3846,7 @@ static bool msg_add_fileformat(int eol_type)
return false;
}
-/*
- * Append line and character count to IObuff.
- */
+/// Append line and character count to IObuff.
void msg_add_lines(int insert_space, long lnum, off_T nchars)
{
char_u *p;
@@ -3880,20 +3870,16 @@ void msg_add_lines(int insert_space, long lnum, off_T nchars)
}
}
-/*
- * Append message for missing line separator to IObuff.
- */
+/// Append message for missing line separator to IObuff.
static void msg_add_eol(void)
{
STRCAT(IObuff,
shortmess(SHM_LAST) ? _("[noeol]") : _("[Incomplete last line]"));
}
-/*
- * Check modification time of file, before writing to it.
- * The size isn't checked, because using a tool like "gzip" takes care of
- * using the same timestamp but can't set the size.
- */
+/// Check modification time of file, before writing to it.
+/// The size isn't checked, because using a tool like "gzip" takes care of
+/// using the same timestamp but can't set the size.
static int check_mtime(buf_T *buf, FileInfo *file_info)
{
if (buf->b_mtime_read != 0
@@ -3925,12 +3911,10 @@ static bool time_differs(const FileInfo *file_info, long mtime, long mtime_ns) F
#endif
}
-/*
- * Call write() to write a number of bytes to the file.
- * Handles 'encoding' conversion.
- *
- * Return FAIL for failure, OK otherwise.
- */
+/// Call write() to write a number of bytes to the file.
+/// Handles 'encoding' conversion.
+///
+/// @return FAIL for failure, OK otherwise.
static int buf_write_bytes(struct bw_info *ip)
{
int wlen;
@@ -4258,12 +4242,11 @@ static int get_fio_flags(const char_u *name)
}
-/*
- * Check for a Unicode BOM (Byte Order Mark) at the start of p[size].
- * "size" must be at least 2.
- * Return the name of the encoding and set "*lenp" to the length.
- * Returns NULL when no BOM found.
- */
+/// Check for a Unicode BOM (Byte Order Mark) at the start of p[size].
+/// "size" must be at least 2.
+///
+/// @return the name of the encoding and set "*lenp" to the length or,
+/// NULL when no BOM found.
static char_u *check_for_bom(char_u *p, long size, int *lenp, int flags)
{
char *name = NULL;
@@ -4304,10 +4287,9 @@ static char_u *check_for_bom(char_u *p, long size, int *lenp, int flags)
return (char_u *)name;
}
-/*
- * Generate a BOM in "buf[4]" for encoding "name".
- * Return the length of the BOM (zero when no BOM).
- */
+/// Generate a BOM in "buf[4]" for encoding "name".
+///
+/// @return the length of the BOM (zero when no BOM).
static int make_bom(char_u *buf, char_u *name)
{
int flags;
@@ -4332,10 +4314,12 @@ static int make_bom(char_u *buf, char_u *name)
}
/// Shorten filename of a buffer.
-/// When "force" is TRUE: Use full path from now on for files currently being
-/// edited, both for file name and swap file name. Try to shorten the file
-/// names a bit, if safe to do so.
-/// When "force" is FALSE: Only try to shorten absolute file names.
+///
+/// @param force when TRUE: Use full path from now on for files currently being
+/// edited, both for file name and swap file name. Try to shorten the file
+/// names a bit, if safe to do so.
+/// when FALSE: Only try to shorten absolute file names.
+///
/// For buffers that have buftype "nofile" or "scratch": never change the file
/// name.
void shorten_buf_fname(buf_T *buf, char_u *dirname, int force)
@@ -4513,7 +4497,8 @@ bool vim_fgets(char_u *buf, int size, FILE *fp) FUNC_ATTR_NONNULL_ALL
}
/// Read 2 bytes from "fd" and turn them into an int, MSB first.
-/// Returns -1 when encountering EOF.
+///
+/// @return -1 when encountering EOF.
int get2c(FILE *fd)
{
const int n = getc(fd);
@@ -4528,7 +4513,8 @@ int get2c(FILE *fd)
}
/// Read 3 bytes from "fd" and turn them into an int, MSB first.
-/// Returns -1 when encountering EOF.
+///
+/// @return -1 when encountering EOF.
int get3c(FILE *fd)
{
int n = getc(fd);
@@ -4548,7 +4534,8 @@ int get3c(FILE *fd)
}
/// Read 4 bytes from "fd" and turn them into an int, MSB first.
-/// Returns -1 when encountering EOF.
+///
+/// @return -1 when encountering EOF.
int get4c(FILE *fd)
{
// Use unsigned rather than int otherwise result is undefined
@@ -4579,7 +4566,8 @@ int get4c(FILE *fd)
}
/// Read 8 bytes from `fd` and turn them into a time_t, MSB first.
-/// Returns -1 when encountering EOF.
+///
+/// @return -1 when encountering EOF.
time_t get8ctime(FILE *fd)
{
time_t n = 0;
@@ -4595,7 +4583,8 @@ time_t get8ctime(FILE *fd)
}
/// Reads a string of length "cnt" from "fd" into allocated memory.
-/// @return pointer to the string or NULL when unable to read that many bytes.
+///
+/// @return pointer to the string or NULL when unable to read that many bytes.
char *read_string(FILE *fd, size_t cnt)
{
char *str = xmallocz(cnt);
@@ -4611,7 +4600,8 @@ char *read_string(FILE *fd, size_t cnt)
}
/// Writes a number to file "fd", most significant bit first, in "len" bytes.
-/// @returns false in case of an error.
+///
+/// @return false in case of an error.
bool put_bytes(FILE *fd, uintmax_t number, size_t len)
{
assert(len > 0);
@@ -4624,7 +4614,8 @@ bool put_bytes(FILE *fd, uintmax_t number, size_t len)
}
/// Writes time_t to file "fd" in 8 bytes.
-/// @returns FAIL when the write failed.
+///
+/// @return FAIL when the write failed.
int put_time(FILE *fd, time_t time_)
{
uint8_t buf[8];
@@ -4635,7 +4626,7 @@ int put_time(FILE *fd, time_t time_)
/// os_rename() only works if both files are on the same file system, this
/// function will (attempts to?) copy the file across if rename fails -- webb
///
-/// @return -1 for failure, 0 for success
+/// @return -1 for failure, 0 for success
int vim_rename(const char_u *from, const char_u *to)
FUNC_ATTR_NONNULL_ALL
{
@@ -4860,11 +4851,10 @@ int check_timestamps(int focus)
return didit;
}
-/*
- * Move all the lines from buffer "frombuf" to buffer "tobuf".
- * Return OK or FAIL. When FAIL "tobuf" is incomplete and/or "frombuf" is not
- * empty.
- */
+/// Move all the lines from buffer "frombuf" to buffer "tobuf".
+///
+/// @return OK or FAIL.
+/// When FAIL "tobuf" is incomplete and/or "frombuf" is not empty.
static int move_lines(buf_T *frombuf, buf_T *tobuf)
{
buf_T *tbuf = curbuf;
@@ -4903,9 +4893,10 @@ static int move_lines(buf_T *frombuf, buf_T *tobuf)
/// Check if buffer "buf" has been changed.
/// Also check if the file for a new buffer unexpectedly appeared.
-/// return 1 if a changed buffer was found.
-/// return 2 if a message has been displayed.
-/// return 0 otherwise.
+///
+/// @return 1 if a changed buffer was found or,
+/// 2 if a message has been displayed or,
+/// 0 otherwise.
int buf_check_timestamp(buf_T *buf)
FUNC_ATTR_NONNULL_ALL
{
@@ -5280,10 +5271,8 @@ void buf_store_file_info(buf_T *buf, FileInfo *file_info)
buf->b_orig_mode = (int)file_info->stat.st_mode;
}
-/*
- * Adjust the line with missing eol, used for the next write.
- * Used for do_filter(), when the input lines for the filter are deleted.
- */
+/// Adjust the line with missing eol, used for the next write.
+/// Used for do_filter(), when the input lines for the filter are deleted.
void write_lnum_adjust(linenr_T offset)
{
if (curbuf->b_no_eol_lnum != 0) { // only if there is a missing eol
@@ -5355,8 +5344,10 @@ static void vim_maketempdir(void)
}
/// Delete "name" and everything in it, recursively.
+///
/// @param name The path which should be deleted.
-/// @return 0 for success, -1 if some file was not deleted.
+///
+/// @return 0 for success, -1 if some file was not deleted.
int delete_recursive(const char *name)
{
int result = 0;
@@ -5400,8 +5391,8 @@ void vim_deltempdir(void)
}
}
-/// Get the name of temp directory. This directory would be created on the first
-/// call to this function.
+/// @return the name of temp directory. This directory would be created on the first
+/// call to this function.
char_u *vim_gettempdir(void)
{
if (vim_tempdir == NULL) {
@@ -5435,8 +5426,8 @@ static bool vim_settempdir(char *tempdir)
///
/// @note The temp file is NOT created.
///
-/// @return pointer to the temp file name or NULL if Neovim can't create
-/// temporary directory for its own temporary files.
+/// @return pointer to the temp file name or NULL if Neovim can't create
+/// temporary directory for its own temporary files.
char_u *vim_tempname(void)
{
// Temp filename counter.
@@ -5747,10 +5738,9 @@ char_u *file_pat_to_reg_pat(const char_u *pat, const char_u *pat_end, char *allo
}
#if defined(EINTR)
-/*
- * Version of read() that retries when interrupted by EINTR (possibly
- * by a SIGWINCH).
- */
+
+/// Version of read() that retries when interrupted by EINTR (possibly
+/// by a SIGWINCH).
long read_eintr(int fd, void *buf, size_t bufsize)
{
long ret;
@@ -5764,10 +5754,8 @@ long read_eintr(int fd, void *buf, size_t bufsize)
return ret;
}
-/*
- * Version of write() that retries when interrupted by EINTR (possibly
- * by a SIGWINCH).
- */
+/// Version of write() that retries when interrupted by EINTR (possibly
+/// by a SIGWINCH).
long write_eintr(int fd, void *buf, size_t bufsize)
{
long ret = 0;
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
index 3ec5d24753..c10172cc52 100644
--- a/src/nvim/getchar.c
+++ b/src/nvim/getchar.c
@@ -1583,6 +1583,14 @@ int vgetc(void)
c = utf_ptr2char(buf);
}
+ if ((mod_mask & MOD_MASK_CTRL) && (c >= '?' && c <= '_')) {
+ c = Ctrl_chr(c);
+ mod_mask &= ~MOD_MASK_CTRL;
+ if (c == 0) { // <C-@> is <Nul>
+ c = K_ZERO;
+ }
+ }
+
// If mappings are enabled (i.e., not Ctrl-v) and the user directly typed
// something with a meta- or alt- modifier that was not mapped, interpret
// <M-x> as <Esc>x rather than as an unbound meta keypress. #8213
diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c
index ee78a79a97..d448f3a646 100644
--- a/src/nvim/highlight_group.c
+++ b/src/nvim/highlight_group.c
@@ -2,25 +2,18 @@
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
// highlight_group.c: code for managing highlight groups
-// Includes highlighting matches
#include <stdbool.h>
#include "nvim/autocmd.h"
#include "nvim/api/private/helpers.h"
#include "nvim/charset.h"
#include "nvim/cursor_shape.h"
-#include "nvim/eval/funcs.h"
-#include "nvim/eval/typval.h"
-#include "nvim/ex_docmd.h"
#include "nvim/fold.h"
-#include "nvim/garray.h"
#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
#include "nvim/lua/executor.h"
-#include "nvim/map.h"
-#include "nvim/memline.h"
+#include "nvim/match.h"
#include "nvim/option.h"
-#include "nvim/regexp.h"
#include "nvim/runtime.h"
#include "nvim/screen.h"
@@ -32,9 +25,6 @@
/// @}
#define MAX_SYN_NAME 200
-#define SEARCH_HL_PRIORITY 0
-
-static char *e_invalwindow = N_("E957: Invalid window number");
// builtin |highlight-groups|
static garray_T highlight_ga = GA_EMPTY_INIT_VALUE;
@@ -2807,1163 +2797,3 @@ int name_to_ctermcolor(const char *name)
TriState bold = kNone;
return lookup_color(i, false, &bold);
}
-
-/// Add match to the match list of window 'wp'. The pattern 'pat' will be
-/// highlighted with the group 'grp' with priority 'prio'.
-/// Optionally, a desired ID 'id' can be specified (greater than or equal to 1).
-///
-/// @param[in] id a desired ID 'id' can be specified
-/// (greater than or equal to 1). -1 must be specified if no
-/// particular ID is desired
-/// @param[in] conceal_char pointer to conceal replacement char
-/// @return ID of added match, -1 on failure.
-static int match_add(win_T *wp, const char *const grp, const char *const pat, int prio, int id,
- list_T *pos_list, const char *const conceal_char)
- FUNC_ATTR_NONNULL_ARG(1, 2)
-{
- matchitem_T *cur;
- matchitem_T *prev;
- matchitem_T *m;
- int hlg_id;
- regprog_T *regprog = NULL;
- int rtype = SOME_VALID;
-
- if (*grp == NUL || (pat != NULL && *pat == NUL)) {
- return -1;
- }
- if (id < -1 || id == 0) {
- semsg(_("E799: Invalid ID: %" PRId64
- " (must be greater than or equal to 1)"),
- (int64_t)id);
- return -1;
- }
- if (id != -1) {
- cur = wp->w_match_head;
- while (cur != NULL) {
- if (cur->id == id) {
- semsg(_("E801: ID already taken: %" PRId64), (int64_t)id);
- return -1;
- }
- cur = cur->next;
- }
- }
- if ((hlg_id = syn_check_group(grp, strlen(grp))) == 0) {
- return -1;
- }
- if (pat != NULL && (regprog = vim_regcomp((char_u *)pat, RE_MAGIC)) == NULL) {
- semsg(_(e_invarg2), pat);
- return -1;
- }
-
- // Find available match ID.
- while (id == -1) {
- cur = wp->w_match_head;
- while (cur != NULL && cur->id != wp->w_next_match_id) {
- cur = cur->next;
- }
- if (cur == NULL) {
- id = wp->w_next_match_id;
- }
- wp->w_next_match_id++;
- }
-
- // Build new match.
- m = xcalloc(1, sizeof(matchitem_T));
- m->id = id;
- m->priority = prio;
- m->pattern = pat == NULL ? NULL: (char_u *)xstrdup(pat);
- m->hlg_id = hlg_id;
- m->match.regprog = regprog;
- m->match.rmm_ic = false;
- m->match.rmm_maxcol = 0;
- m->conceal_char = 0;
- if (conceal_char != NULL) {
- m->conceal_char = utf_ptr2char((const char_u *)conceal_char);
- }
-
- // Set up position matches
- if (pos_list != NULL) {
- linenr_T toplnum = 0;
- linenr_T botlnum = 0;
-
- int i = 0;
- TV_LIST_ITER(pos_list, li, {
- linenr_T lnum = 0;
- colnr_T col = 0;
- int len = 1;
- bool error = false;
-
- if (TV_LIST_ITEM_TV(li)->v_type == VAR_LIST) {
- const list_T *const subl = TV_LIST_ITEM_TV(li)->vval.v_list;
- const listitem_T *subli = tv_list_first(subl);
- if (subli == NULL) {
- semsg(_("E5030: Empty list at position %d"),
- (int)tv_list_idx_of_item(pos_list, li));
- goto fail;
- }
- lnum = tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error);
- if (error) {
- goto fail;
- }
- if (lnum <= 0) {
- continue;
- }
- m->pos.pos[i].lnum = lnum;
- subli = TV_LIST_ITEM_NEXT(subl, subli);
- if (subli != NULL) {
- col = (colnr_T)tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error);
- if (error) {
- goto fail;
- }
- if (col < 0) {
- continue;
- }
- subli = TV_LIST_ITEM_NEXT(subl, subli);
- if (subli != NULL) {
- len = (colnr_T)tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error);
- if (len < 0) {
- continue;
- }
- if (error) {
- goto fail;
- }
- }
- }
- m->pos.pos[i].col = col;
- m->pos.pos[i].len = len;
- } else if (TV_LIST_ITEM_TV(li)->v_type == VAR_NUMBER) {
- if (TV_LIST_ITEM_TV(li)->vval.v_number <= 0) {
- continue;
- }
- m->pos.pos[i].lnum = TV_LIST_ITEM_TV(li)->vval.v_number;
- m->pos.pos[i].col = 0;
- m->pos.pos[i].len = 0;
- } else {
- semsg(_("E5031: List or number required at position %d"),
- (int)tv_list_idx_of_item(pos_list, li));
- goto fail;
- }
- if (toplnum == 0 || lnum < toplnum) {
- toplnum = lnum;
- }
- if (botlnum == 0 || lnum >= botlnum) {
- botlnum = lnum + 1;
- }
- i++;
- if (i >= MAXPOSMATCH) {
- break;
- }
- });
-
- // Calculate top and bottom lines for redrawing area
- if (toplnum != 0) {
- if (wp->w_buffer->b_mod_set) {
- if (wp->w_buffer->b_mod_top > toplnum) {
- wp->w_buffer->b_mod_top = toplnum;
- }
- if (wp->w_buffer->b_mod_bot < botlnum) {
- wp->w_buffer->b_mod_bot = botlnum;
- }
- } else {
- wp->w_buffer->b_mod_set = true;
- wp->w_buffer->b_mod_top = toplnum;
- wp->w_buffer->b_mod_bot = botlnum;
- wp->w_buffer->b_mod_xlines = 0;
- }
- m->pos.toplnum = toplnum;
- m->pos.botlnum = botlnum;
- rtype = VALID;
- }
- }
-
- // Insert new match. The match list is in ascending order with regard to
- // the match priorities.
- cur = wp->w_match_head;
- prev = cur;
- while (cur != NULL && prio >= cur->priority) {
- prev = cur;
- cur = cur->next;
- }
- if (cur == prev) {
- wp->w_match_head = m;
- } else {
- prev->next = m;
- }
- m->next = cur;
-
- redraw_later(wp, rtype);
- return id;
-
-fail:
- xfree(m);
- return -1;
-}
-
-/// Delete match with ID 'id' in the match list of window 'wp'.
-///
-/// @param perr print error messages if true.
-static int match_delete(win_T *wp, int id, bool perr)
-{
- matchitem_T *cur = wp->w_match_head;
- matchitem_T *prev = cur;
- int rtype = SOME_VALID;
-
- if (id < 1) {
- if (perr) {
- semsg(_("E802: Invalid ID: %" PRId64
- " (must be greater than or equal to 1)"),
- (int64_t)id);
- }
- return -1;
- }
- while (cur != NULL && cur->id != id) {
- prev = cur;
- cur = cur->next;
- }
- if (cur == NULL) {
- if (perr) {
- semsg(_("E803: ID not found: %" PRId64), (int64_t)id);
- }
- return -1;
- }
- if (cur == prev) {
- wp->w_match_head = cur->next;
- } else {
- prev->next = cur->next;
- }
- vim_regfree(cur->match.regprog);
- xfree(cur->pattern);
- if (cur->pos.toplnum != 0) {
- if (wp->w_buffer->b_mod_set) {
- if (wp->w_buffer->b_mod_top > cur->pos.toplnum) {
- wp->w_buffer->b_mod_top = cur->pos.toplnum;
- }
- if (wp->w_buffer->b_mod_bot < cur->pos.botlnum) {
- wp->w_buffer->b_mod_bot = cur->pos.botlnum;
- }
- } else {
- wp->w_buffer->b_mod_set = true;
- wp->w_buffer->b_mod_top = cur->pos.toplnum;
- wp->w_buffer->b_mod_bot = cur->pos.botlnum;
- wp->w_buffer->b_mod_xlines = 0;
- }
- rtype = VALID;
- }
- xfree(cur);
- redraw_later(wp, rtype);
- return 0;
-}
-
-/// Delete all matches in the match list of window 'wp'.
-void clear_matches(win_T *wp)
-{
- matchitem_T *m;
-
- while (wp->w_match_head != NULL) {
- m = wp->w_match_head->next;
- vim_regfree(wp->w_match_head->match.regprog);
- xfree(wp->w_match_head->pattern);
- xfree(wp->w_match_head);
- wp->w_match_head = m;
- }
- redraw_later(wp, SOME_VALID);
-}
-
-/// Get match from ID 'id' in window 'wp'.
-/// Return NULL if match not found.
-matchitem_T *get_match(win_T *wp, int id)
-{
- matchitem_T *cur = wp->w_match_head;
-
- while (cur != NULL && cur->id != id) {
- cur = cur->next;
- }
- return cur;
-}
-
-/// Init for calling prepare_search_hl().
-void init_search_hl(win_T *wp, match_T *search_hl)
- FUNC_ATTR_NONNULL_ALL
-{
- // Setup for match and 'hlsearch' highlighting. Disable any previous
- // match
- matchitem_T *cur = wp->w_match_head;
- while (cur != NULL) {
- cur->hl.rm = cur->match;
- if (cur->hlg_id == 0) {
- cur->hl.attr = 0;
- } else {
- cur->hl.attr = syn_id2attr(cur->hlg_id);
- }
- cur->hl.buf = wp->w_buffer;
- cur->hl.lnum = 0;
- cur->hl.first_lnum = 0;
- // Set the time limit to 'redrawtime'.
- cur->hl.tm = profile_setlimit(p_rdt);
- cur = cur->next;
- }
- search_hl->buf = wp->w_buffer;
- search_hl->lnum = 0;
- search_hl->first_lnum = 0;
- search_hl->attr = win_hl_attr(wp, HLF_L);
-
- // time limit is set at the toplevel, for all windows
-}
-
-/// @param shl points to a match. Fill on match.
-/// @param posmatch match positions
-/// @param mincol minimal column for a match
-///
-/// @return one on match, otherwise return zero.
-static int next_search_hl_pos(match_T *shl, linenr_T lnum, posmatch_T *posmatch, colnr_T mincol)
- FUNC_ATTR_NONNULL_ALL
-{
- int i;
- int found = -1;
-
- shl->lnum = 0;
- for (i = posmatch->cur; i < MAXPOSMATCH; i++) {
- llpos_T *pos = &posmatch->pos[i];
-
- if (pos->lnum == 0) {
- break;
- }
- if (pos->len == 0 && pos->col < mincol) {
- continue;
- }
- if (pos->lnum == lnum) {
- if (found >= 0) {
- // if this match comes before the one at "found" then swap
- // them
- if (pos->col < posmatch->pos[found].col) {
- llpos_T tmp = *pos;
-
- *pos = posmatch->pos[found];
- posmatch->pos[found] = tmp;
- }
- } else {
- found = i;
- }
- }
- }
- posmatch->cur = 0;
- if (found >= 0) {
- colnr_T start = posmatch->pos[found].col == 0
- ? 0: posmatch->pos[found].col - 1;
- colnr_T end = posmatch->pos[found].col == 0
- ? MAXCOL : start + posmatch->pos[found].len;
-
- shl->lnum = lnum;
- shl->rm.startpos[0].lnum = 0;
- shl->rm.startpos[0].col = start;
- shl->rm.endpos[0].lnum = 0;
- shl->rm.endpos[0].col = end;
- shl->is_addpos = true;
- posmatch->cur = found + 1;
- return 1;
- }
- return 0;
-}
-
-/// Search for a next 'hlsearch' or match.
-/// Uses shl->buf.
-/// Sets shl->lnum and shl->rm contents.
-/// Note: Assumes a previous match is always before "lnum", unless
-/// shl->lnum is zero.
-/// Careful: Any pointers for buffer lines will become invalid.
-///
-/// @param shl points to search_hl or a match
-/// @param mincol minimal column for a match
-/// @param cur to retrieve match positions if any
-static void next_search_hl(win_T *win, match_T *search_hl, match_T *shl, linenr_T lnum,
- colnr_T mincol, matchitem_T *cur)
- FUNC_ATTR_NONNULL_ARG(2)
-{
- linenr_T l;
- colnr_T matchcol;
- long nmatched = 0;
- int save_called_emsg = called_emsg;
-
- // for :{range}s/pat only highlight inside the range
- if (lnum < search_first_line || lnum > search_last_line) {
- shl->lnum = 0;
- return;
- }
-
- if (shl->lnum != 0) {
- // Check for three situations:
- // 1. If the "lnum" is below a previous match, start a new search.
- // 2. If the previous match includes "mincol", use it.
- // 3. Continue after the previous match.
- l = shl->lnum + shl->rm.endpos[0].lnum - shl->rm.startpos[0].lnum;
- if (lnum > l) {
- shl->lnum = 0;
- } else if (lnum < l || shl->rm.endpos[0].col > mincol) {
- return;
- }
- }
-
- // Repeat searching for a match until one is found that includes "mincol"
- // or none is found in this line.
- called_emsg = false;
- for (;;) {
- // Stop searching after passing the time limit.
- if (profile_passed_limit(shl->tm)) {
- shl->lnum = 0; // no match found in time
- break;
- }
- // Three situations:
- // 1. No useful previous match: search from start of line.
- // 2. Not Vi compatible or empty match: continue at next character.
- // Break the loop if this is beyond the end of the line.
- // 3. Vi compatible searching: continue at end of previous match.
- if (shl->lnum == 0) {
- matchcol = 0;
- } else if (vim_strchr(p_cpo, CPO_SEARCH) == NULL
- || (shl->rm.endpos[0].lnum == 0
- && shl->rm.endpos[0].col <= shl->rm.startpos[0].col)) {
- char_u *ml;
-
- matchcol = shl->rm.startpos[0].col;
- ml = ml_get_buf(shl->buf, lnum, false) + matchcol;
- if (*ml == NUL) {
- matchcol++;
- shl->lnum = 0;
- break;
- }
- matchcol += utfc_ptr2len(ml);
- } else {
- matchcol = shl->rm.endpos[0].col;
- }
-
- shl->lnum = lnum;
- if (shl->rm.regprog != NULL) {
- // Remember whether shl->rm is using a copy of the regprog in
- // cur->match.
- bool regprog_is_copy = (shl != search_hl
- && cur != NULL
- && shl == &cur->hl
- && cur->match.regprog == cur->hl.rm.regprog);
- int timed_out = false;
-
- nmatched = vim_regexec_multi(&shl->rm, win, shl->buf, lnum, matchcol,
- &(shl->tm), &timed_out);
- // Copy the regprog, in case it got freed and recompiled.
- if (regprog_is_copy) {
- cur->match.regprog = cur->hl.rm.regprog;
- }
- if (called_emsg || got_int || timed_out) {
- // Error while handling regexp: stop using this regexp.
- if (shl == search_hl) {
- // don't free regprog in the match list, it's a copy
- vim_regfree(shl->rm.regprog);
- set_no_hlsearch(true);
- }
- shl->rm.regprog = NULL;
- shl->lnum = 0;
- got_int = false; // avoid the "Type :quit to exit Vim" message
- break;
- }
- } else if (cur != NULL) {
- nmatched = next_search_hl_pos(shl, lnum, &(cur->pos), matchcol);
- }
- if (nmatched == 0) {
- shl->lnum = 0; // no match found
- break;
- }
- if (shl->rm.startpos[0].lnum > 0
- || shl->rm.startpos[0].col >= mincol
- || nmatched > 1
- || shl->rm.endpos[0].col > mincol) {
- shl->lnum += shl->rm.startpos[0].lnum;
- break; // useful match found
- }
-
- // Restore called_emsg for assert_fails().
- called_emsg = save_called_emsg;
- }
-}
-
-/// Advance to the match in window "wp" line "lnum" or past it.
-void prepare_search_hl(win_T *wp, match_T *search_hl, linenr_T lnum)
- FUNC_ATTR_NONNULL_ALL
-{
- matchitem_T *cur; // points to the match list
- match_T *shl; // points to search_hl or a match
- bool shl_flag; // flag to indicate whether search_hl
- // has been processed or not
-
- // When using a multi-line pattern, start searching at the top
- // of the window or just after a closed fold.
- // Do this both for search_hl and the match list.
- cur = wp->w_match_head;
- shl_flag = false;
- while (cur != NULL || shl_flag == false) {
- if (shl_flag == false) {
- shl = search_hl;
- shl_flag = true;
- } else {
- shl = &cur->hl; // -V595
- }
- if (shl->rm.regprog != NULL
- && shl->lnum == 0
- && re_multiline(shl->rm.regprog)) {
- if (shl->first_lnum == 0) {
- for (shl->first_lnum = lnum;
- shl->first_lnum > wp->w_topline;
- shl->first_lnum--) {
- if (hasFoldingWin(wp, shl->first_lnum - 1, NULL, NULL, true, NULL)) {
- break;
- }
- }
- }
- if (cur != NULL) {
- cur->pos.cur = 0;
- }
- bool pos_inprogress = true; // mark that a position match search is
- // in progress
- int n = 0;
- while (shl->first_lnum < lnum && (shl->rm.regprog != NULL
- || (cur != NULL && pos_inprogress))) {
- next_search_hl(wp, search_hl, shl, shl->first_lnum, (colnr_T)n,
- shl == search_hl ? NULL : cur);
- pos_inprogress = !(cur == NULL || cur->pos.cur == 0);
- if (shl->lnum != 0) {
- shl->first_lnum = shl->lnum
- + shl->rm.endpos[0].lnum
- - shl->rm.startpos[0].lnum;
- n = shl->rm.endpos[0].col;
- } else {
- shl->first_lnum++;
- n = 0;
- }
- }
- }
- if (shl != search_hl && cur != NULL) {
- cur = cur->next;
- }
- }
-}
-
-/// Prepare for 'hlsearch' and match highlighting in one window line.
-/// Return true if there is such highlighting and set "search_attr" to the
-/// current highlight attribute.
-bool prepare_search_hl_line(win_T *wp, linenr_T lnum, colnr_T mincol, char_u **line,
- match_T *search_hl, int *search_attr, bool *search_attr_from_match)
-{
- matchitem_T *cur = wp->w_match_head; // points to the match list
- match_T *shl; // points to search_hl or a match
- bool shl_flag = false; // flag to indicate whether search_hl
- // has been processed or not
- bool area_highlighting = false;
-
- // Handle highlighting the last used search pattern and matches.
- // Do this for both search_hl and the match list.
- while (cur != NULL || !shl_flag) {
- if (!shl_flag) {
- shl = search_hl;
- shl_flag = true;
- } else {
- shl = &cur->hl; // -V595
- }
- shl->startcol = MAXCOL;
- shl->endcol = MAXCOL;
- shl->attr_cur = 0;
- shl->is_addpos = false;
- if (cur != NULL) {
- cur->pos.cur = 0;
- }
- next_search_hl(wp, search_hl, shl, lnum, mincol,
- shl == search_hl ? NULL : cur);
-
- // Need to get the line again, a multi-line regexp may have made it
- // invalid.
- *line = ml_get_buf(wp->w_buffer, lnum, false);
-
- if (shl->lnum != 0 && shl->lnum <= lnum) {
- if (shl->lnum == lnum) {
- shl->startcol = shl->rm.startpos[0].col;
- } else {
- shl->startcol = 0;
- }
- if (lnum == shl->lnum + shl->rm.endpos[0].lnum
- - shl->rm.startpos[0].lnum) {
- shl->endcol = shl->rm.endpos[0].col;
- } else {
- shl->endcol = MAXCOL;
- }
- // Highlight one character for an empty match.
- if (shl->startcol == shl->endcol) {
- if ((*line)[shl->endcol] != NUL) {
- shl->endcol += utfc_ptr2len(*line + shl->endcol);
- } else {
- shl->endcol++;
- }
- }
- if ((long)shl->startcol < mincol) { // match at leftcol
- shl->attr_cur = shl->attr;
- *search_attr = shl->attr;
- *search_attr_from_match = shl != search_hl;
- }
- area_highlighting = true;
- }
- if (shl != search_hl && cur != NULL) {
- cur = cur->next;
- }
- }
- return area_highlighting;
-}
-
-/// For a position in a line: Check for start/end of 'hlsearch' and other
-/// matches.
-/// After end, check for start/end of next match.
-/// When another match, have to check for start again.
-/// Watch out for matching an empty string!
-/// Return the updated search_attr.
-int update_search_hl(win_T *wp, linenr_T lnum, colnr_T col, char_u **line, match_T *search_hl,
- int *has_match_conc, int *match_conc, int lcs_eol_one,
- bool *search_attr_from_match)
-{
- matchitem_T *cur = wp->w_match_head; // points to the match list
- match_T *shl; // points to search_hl or a match
- bool shl_flag = false; // flag to indicate whether search_hl
- // has been processed or not
- int search_attr = 0;
-
- // Do this for 'search_hl' and the match list (ordered by priority).
- while (cur != NULL || !shl_flag) {
- if (!shl_flag
- && (cur == NULL || cur->priority > SEARCH_HL_PRIORITY)) {
- shl = search_hl;
- shl_flag = true;
- } else {
- shl = &cur->hl;
- }
- if (cur != NULL) {
- cur->pos.cur = 0;
- }
- bool pos_inprogress = true; // mark that a position match search is
- // in progress
- while (shl->rm.regprog != NULL
- || (cur != NULL && pos_inprogress)) {
- if (shl->startcol != MAXCOL
- && col >= shl->startcol
- && col < shl->endcol) {
- int next_col = col + utfc_ptr2len(*line + col);
-
- if (shl->endcol < next_col) {
- shl->endcol = next_col;
- }
- shl->attr_cur = shl->attr;
- // Match with the "Conceal" group results in hiding
- // the match.
- if (cur != NULL
- && shl != search_hl
- && syn_name2id("Conceal") == cur->hlg_id) {
- *has_match_conc = col == shl->startcol ? 2 : 1;
- *match_conc = cur->conceal_char;
- } else {
- *has_match_conc = 0;
- }
- } else if (col == shl->endcol) {
- shl->attr_cur = 0;
-
- next_search_hl(wp, search_hl, shl, lnum, col,
- shl == search_hl ? NULL : cur);
- pos_inprogress = !(cur == NULL || cur->pos.cur == 0);
-
- // Need to get the line again, a multi-line regexp
- // may have made it invalid.
- *line = ml_get_buf(wp->w_buffer, lnum, false);
-
- if (shl->lnum == lnum) {
- shl->startcol = shl->rm.startpos[0].col;
- if (shl->rm.endpos[0].lnum == 0) {
- shl->endcol = shl->rm.endpos[0].col;
- } else {
- shl->endcol = MAXCOL;
- }
-
- if (shl->startcol == shl->endcol) {
- // highlight empty match, try again after it
- shl->endcol += utfc_ptr2len(*line + shl->endcol);
- }
-
- // Loop to check if the match starts at the
- // current position
- continue;
- }
- }
- break;
- }
- if (shl != search_hl && cur != NULL) {
- cur = cur->next;
- }
- }
-
- // Use attributes from match with highest priority among
- // 'search_hl' and the match list.
- *search_attr_from_match = false;
- search_attr = search_hl->attr_cur;
- cur = wp->w_match_head;
- shl_flag = false;
- while (cur != NULL || !shl_flag) {
- if (!shl_flag
- && (cur == NULL || cur->priority > SEARCH_HL_PRIORITY)) {
- shl = search_hl;
- shl_flag = true;
- } else {
- shl = &cur->hl;
- }
- if (shl->attr_cur != 0) {
- search_attr = shl->attr_cur;
- *search_attr_from_match = shl != search_hl;
- }
- if (shl != search_hl && cur != NULL) {
- cur = cur->next;
- }
- }
- // Only highlight one character after the last column.
- if (*(*line + col) == NUL && (wp->w_p_list && lcs_eol_one == -1)) {
- search_attr = 0;
- }
- return search_attr;
-}
-
-bool get_prevcol_hl_flag(win_T *wp, match_T *search_hl, long curcol)
-{
- long prevcol = curcol;
- matchitem_T *cur; // points to the match list
-
- // we're not really at that column when skipping some text
- if ((long)(wp->w_p_wrap ? wp->w_skipcol : wp->w_leftcol) > prevcol) {
- prevcol++;
- }
-
- if (!search_hl->is_addpos && prevcol == search_hl->startcol) {
- return true;
- } else {
- cur = wp->w_match_head;
- while (cur != NULL) {
- if (!cur->hl.is_addpos && prevcol == cur->hl.startcol) {
- return true;
- }
- cur = cur->next;
- }
- }
- return false;
-}
-
-/// Get highlighting for the char after the text in "char_attr" from 'hlsearch'
-/// or match highlighting.
-void get_search_match_hl(win_T *wp, match_T *search_hl, long col, int *char_attr)
-{
- matchitem_T *cur = wp->w_match_head; // points to the match list
- match_T *shl; // points to search_hl or a match
- bool shl_flag = false; // flag to indicate whether search_hl
- // has been processed or not
-
- *char_attr = search_hl->attr;
- while (cur != NULL || !shl_flag) {
- if (!shl_flag
- && (cur == NULL || cur->priority > SEARCH_HL_PRIORITY)) {
- shl = search_hl;
- shl_flag = true;
- } else {
- shl = &cur->hl;
- }
- if (col - 1 == (long)shl->startcol
- && (shl == search_hl || !shl->is_addpos)) {
- *char_attr = shl->attr;
- }
- if (shl != search_hl && cur != NULL) {
- cur = cur->next;
- }
- }
-}
-
-static int matchadd_dict_arg(typval_T *tv, const char **conceal_char, win_T **win)
-{
- dictitem_T *di;
-
- if (tv->v_type != VAR_DICT) {
- emsg(_(e_dictreq));
- return FAIL;
- }
-
- if ((di = tv_dict_find(tv->vval.v_dict, S_LEN("conceal"))) != NULL) {
- *conceal_char = tv_get_string(&di->di_tv);
- }
-
- if ((di = tv_dict_find(tv->vval.v_dict, S_LEN("window"))) != NULL) {
- *win = find_win_by_nr_or_id(&di->di_tv);
- if (*win == NULL) {
- emsg(_(e_invalwindow));
- return FAIL;
- }
- }
-
- return OK;
-}
-
-/// "clearmatches()" function
-void f_clearmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- win_T *win = get_optional_window(argvars, 0);
-
- if (win != NULL) {
- clear_matches(win);
- }
-}
-
-/// "getmatches()" function
-void f_getmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- matchitem_T *cur;
- int i;
- win_T *win = get_optional_window(argvars, 0);
-
- if (win == NULL) {
- return;
- }
-
- tv_list_alloc_ret(rettv, kListLenMayKnow);
- cur = win->w_match_head;
- while (cur != NULL) {
- dict_T *dict = tv_dict_alloc();
- if (cur->match.regprog == NULL) {
- // match added with matchaddpos()
- for (i = 0; i < MAXPOSMATCH; i++) {
- llpos_T *llpos;
- char buf[30]; // use 30 to avoid compiler warning
-
- llpos = &cur->pos.pos[i];
- if (llpos->lnum == 0) {
- break;
- }
- list_T *const l = tv_list_alloc(1 + (llpos->col > 0 ? 2 : 0));
- tv_list_append_number(l, (varnumber_T)llpos->lnum);
- if (llpos->col > 0) {
- tv_list_append_number(l, (varnumber_T)llpos->col);
- tv_list_append_number(l, (varnumber_T)llpos->len);
- }
- int len = snprintf(buf, sizeof(buf), "pos%d", i + 1);
- assert((size_t)len < sizeof(buf));
- tv_dict_add_list(dict, buf, (size_t)len, l);
- }
- } else {
- tv_dict_add_str(dict, S_LEN("pattern"), (const char *)cur->pattern);
- }
- tv_dict_add_str(dict, S_LEN("group"),
- (const char *)syn_id2name(cur->hlg_id));
- tv_dict_add_nr(dict, S_LEN("priority"), (varnumber_T)cur->priority);
- tv_dict_add_nr(dict, S_LEN("id"), (varnumber_T)cur->id);
-
- if (cur->conceal_char) {
- char buf[MB_MAXBYTES + 1];
-
- buf[utf_char2bytes(cur->conceal_char, (char_u *)buf)] = NUL;
- tv_dict_add_str(dict, S_LEN("conceal"), buf);
- }
-
- tv_list_append_dict(rettv->vval.v_list, dict);
- cur = cur->next;
- }
-}
-
-/// "setmatches()" function
-void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- dict_T *d;
- list_T *s = NULL;
- win_T *win = get_optional_window(argvars, 1);
-
- rettv->vval.v_number = -1;
- if (argvars[0].v_type != VAR_LIST) {
- emsg(_(e_listreq));
- return;
- }
- if (win == NULL) {
- return;
- }
-
- list_T *const l = argvars[0].vval.v_list;
- // To some extent make sure that we are dealing with a list from
- // "getmatches()".
- int li_idx = 0;
- TV_LIST_ITER_CONST(l, li, {
- if (TV_LIST_ITEM_TV(li)->v_type != VAR_DICT
- || (d = TV_LIST_ITEM_TV(li)->vval.v_dict) == NULL) {
- semsg(_("E474: List item %d is either not a dictionary "
- "or an empty one"), li_idx);
- return;
- }
- if (!(tv_dict_find(d, S_LEN("group")) != NULL
- && (tv_dict_find(d, S_LEN("pattern")) != NULL
- || tv_dict_find(d, S_LEN("pos1")) != NULL)
- && tv_dict_find(d, S_LEN("priority")) != NULL
- && tv_dict_find(d, S_LEN("id")) != NULL)) {
- semsg(_("E474: List item %d is missing one of the required keys"),
- li_idx);
- return;
- }
- li_idx++;
- });
-
- clear_matches(win);
- bool match_add_failed = false;
- TV_LIST_ITER_CONST(l, li, {
- int i = 0;
-
- d = TV_LIST_ITEM_TV(li)->vval.v_dict;
- dictitem_T *const di = tv_dict_find(d, S_LEN("pattern"));
- if (di == NULL) {
- if (s == NULL) {
- s = tv_list_alloc(9);
- }
-
- // match from matchaddpos()
- for (i = 1; i < 9; i++) {
- char buf[30]; // use 30 to avoid compiler warning
- snprintf(buf, sizeof(buf), "pos%d", i);
- dictitem_T *const pos_di = tv_dict_find(d, buf, -1);
- if (pos_di != NULL) {
- if (pos_di->di_tv.v_type != VAR_LIST) {
- return;
- }
-
- tv_list_append_tv(s, &pos_di->di_tv);
- tv_list_ref(s);
- } else {
- break;
- }
- }
- }
-
- // Note: there are three number buffers involved:
- // - group_buf below.
- // - numbuf in tv_dict_get_string().
- // - mybuf in tv_get_string().
- //
- // If you change this code make sure that buffers will not get
- // accidentally reused.
- char group_buf[NUMBUFLEN];
- const char *const group = tv_dict_get_string_buf(d, "group", group_buf);
- const int priority = (int)tv_dict_get_number(d, "priority");
- const int id = (int)tv_dict_get_number(d, "id");
- dictitem_T *const conceal_di = tv_dict_find(d, S_LEN("conceal"));
- const char *const conceal = (conceal_di != NULL
- ? tv_get_string(&conceal_di->di_tv)
- : NULL);
- if (i == 0) {
- if (match_add(win, group,
- tv_dict_get_string(d, "pattern", false),
- priority, id, NULL, conceal) != id) {
- match_add_failed = true;
- }
- } else {
- if (match_add(win, group, NULL, priority, id, s, conceal) != id) {
- match_add_failed = true;
- }
- tv_list_unref(s);
- s = NULL;
- }
- });
- if (!match_add_failed) {
- rettv->vval.v_number = 0;
- }
-}
-
-
-/// "matchadd()" function
-void f_matchadd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char grpbuf[NUMBUFLEN];
- char patbuf[NUMBUFLEN];
- // group
- const char *const grp = tv_get_string_buf_chk(&argvars[0], grpbuf);
- // pattern
- const char *const pat = tv_get_string_buf_chk(&argvars[1], patbuf);
- // default priority
- int prio = 10;
- int id = -1;
- bool error = false;
- const char *conceal_char = NULL;
- win_T *win = curwin;
-
- rettv->vval.v_number = -1;
-
- if (grp == NULL || pat == NULL) {
- return;
- }
- if (argvars[2].v_type != VAR_UNKNOWN) {
- prio = (int)tv_get_number_chk(&argvars[2], &error);
- if (argvars[3].v_type != VAR_UNKNOWN) {
- id = (int)tv_get_number_chk(&argvars[3], &error);
- if (argvars[4].v_type != VAR_UNKNOWN
- && matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL) {
- return;
- }
- }
- }
- if (error) {
- return;
- }
- if (id >= 1 && id <= 3) {
- semsg(_("E798: ID is reserved for \":match\": %" PRId64), (int64_t)id);
- return;
- }
-
- rettv->vval.v_number = match_add(win, grp, pat, prio, id, NULL, conceal_char);
-}
-
-/// "matchaddpo()" function
-void f_matchaddpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = -1;
-
- char buf[NUMBUFLEN];
- const char *const group = tv_get_string_buf_chk(&argvars[0], buf);
- if (group == NULL) {
- return;
- }
-
- if (argvars[1].v_type != VAR_LIST) {
- semsg(_(e_listarg), "matchaddpos()");
- return;
- }
-
- list_T *l;
- l = argvars[1].vval.v_list;
- if (l == NULL) {
- return;
- }
-
- bool error = false;
- int prio = 10;
- int id = -1;
- const char *conceal_char = NULL;
- win_T *win = curwin;
-
- if (argvars[2].v_type != VAR_UNKNOWN) {
- prio = (int)tv_get_number_chk(&argvars[2], &error);
- if (argvars[3].v_type != VAR_UNKNOWN) {
- id = (int)tv_get_number_chk(&argvars[3], &error);
- if (argvars[4].v_type != VAR_UNKNOWN
- && matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL) {
- return;
- }
- }
- }
- if (error == true) {
- return;
- }
-
- // id == 3 is ok because matchaddpos() is supposed to substitute :3match
- if (id == 1 || id == 2) {
- semsg(_("E798: ID is reserved for \"match\": %" PRId64), (int64_t)id);
- return;
- }
-
- rettv->vval.v_number = match_add(win, group, NULL, prio, id, l, conceal_char);
-}
-
-/// "matcharg()" function
-void f_matcharg(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const int id = (int)tv_get_number(&argvars[0]);
-
- tv_list_alloc_ret(rettv, (id >= 1 && id <= 3
- ? 2
- : 0));
-
- if (id >= 1 && id <= 3) {
- matchitem_T *const m = get_match(curwin, id);
-
- if (m != NULL) {
- tv_list_append_string(rettv->vval.v_list,
- (const char *)syn_id2name(m->hlg_id), -1);
- tv_list_append_string(rettv->vval.v_list, (const char *)m->pattern, -1);
- } else {
- tv_list_append_string(rettv->vval.v_list, NULL, 0);
- tv_list_append_string(rettv->vval.v_list, NULL, 0);
- }
- }
-}
-
-/// "matchdelete()" function
-void f_matchdelete(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- win_T *win = get_optional_window(argvars, 1);
- if (win == NULL) {
- rettv->vval.v_number = -1;
- } else {
- rettv->vval.v_number = match_delete(win,
- (int)tv_get_number(&argvars[0]), true);
- }
-}
-
-/// ":[N]match {group} {pattern}"
-/// Sets nextcmd to the start of the next command, if any. Also called when
-/// skipping commands to find the next command.
-void ex_match(exarg_T *eap)
-{
- char_u *p;
- char_u *g = NULL;
- char_u *end;
- int c;
- int id;
-
- if (eap->line2 <= 3) {
- id = (int)eap->line2;
- } else {
- emsg(e_invcmd);
- return;
- }
-
- // First clear any old pattern.
- if (!eap->skip) {
- match_delete(curwin, id, false);
- }
-
- if (ends_excmd(*eap->arg)) {
- end = eap->arg;
- } else if ((STRNICMP(eap->arg, "none", 4) == 0
- && (ascii_iswhite(eap->arg[4]) || ends_excmd(eap->arg[4])))) {
- end = eap->arg + 4;
- } else {
- p = skiptowhite(eap->arg);
- if (!eap->skip) {
- g = vim_strnsave(eap->arg, (size_t)(p - eap->arg));
- }
- p = skipwhite(p);
- if (*p == NUL) {
- // There must be two arguments.
- xfree(g);
- semsg(_(e_invarg2), eap->arg);
- return;
- }
- end = skip_regexp(p + 1, *p, true, NULL);
- if (!eap->skip) {
- if (*end != NUL && !ends_excmd(*skipwhite(end + 1))) {
- xfree(g);
- eap->errmsg = e_trailing;
- return;
- }
- if (*end != *p) {
- xfree(g);
- semsg(_(e_invarg2), p);
- return;
- }
-
- c = *end;
- *end = NUL;
- match_add(curwin, (const char *)g, (const char *)p + 1, 10, id,
- NULL, NULL);
- xfree(g);
- *end = (char_u)c;
- }
- }
- eap->nextcmd = find_nextcmd(end);
-}
-
diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c
index 32f2158d7b..9ad9640834 100644
--- a/src/nvim/keymap.c
+++ b/src/nvim/keymap.c
@@ -744,12 +744,15 @@ static int extract_modifiers(int key, int *modp)
modifiers &= ~MOD_MASK_SHIFT;
}
}
- if ((modifiers & MOD_MASK_CTRL)
- && ((key >= '?' && key <= '_') || ASCII_ISALPHA(key))) {
- key = Ctrl_chr(key);
- modifiers &= ~MOD_MASK_CTRL;
- if (key == 0) { // <C-@> is <Nul>
- key = K_ZERO;
+ if ((modifiers & MOD_MASK_CTRL) && ((key >= '?' && key <= '_') || ASCII_ISALPHA(key))) {
+ key = TOUPPER_ASC(key);
+ int new_key = Ctrl_chr(key);
+ if (new_key != TAB && new_key != CAR && new_key != ESC) {
+ key = new_key;
+ modifiers &= ~MOD_MASK_CTRL;
+ if (key == 0) { // <C-@> is <Nul>
+ key = K_ZERO;
+ }
}
}
diff --git a/src/nvim/log.c b/src/nvim/log.c
index 5539e3d6c5..7d50ecf69e 100644
--- a/src/nvim/log.c
+++ b/src/nvim/log.c
@@ -282,6 +282,7 @@ static bool do_log_to_file(FILE *log_file, int log_level, const char *context,
static bool v_do_log_to_file(FILE *log_file, int log_level, const char *context,
const char *func_name, int line_num, bool eol, const char *fmt,
va_list args)
+ FUNC_ATTR_PRINTF(7, 0)
{
static const char *log_levels[] = {
[DEBUG_LOG_LEVEL] = "DEBUG",
diff --git a/src/nvim/main.c b/src/nvim/main.c
index dec1ae93e7..afb9313cba 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -2099,6 +2099,7 @@ static bool file_owned(const char *fname)
/// @param errstr string containing an error message
/// @param str string to append to the primary error message, or NULL
static void mainerr(const char *errstr, const char *str)
+ FUNC_ATTR_NORETURN
{
char *prgname = (char *)path_tail((char_u *)argv0);
diff --git a/src/nvim/match.c b/src/nvim/match.c
new file mode 100644
index 0000000000..af89319a09
--- /dev/null
+++ b/src/nvim/match.c
@@ -0,0 +1,1181 @@
+// This is an open source non-commercial project. Dear PVS-Studio, please check
+// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+
+// match.c: functions for highlighting matches
+
+#include <stdbool.h>
+#include "nvim/charset.h"
+#include "nvim/fold.h"
+#include "nvim/highlight_group.h"
+#include "nvim/match.h"
+#include "nvim/memline.h"
+#include "nvim/regexp.h"
+#include "nvim/runtime.h"
+#include "nvim/screen.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "match.c.generated.h"
+#endif
+
+static char *e_invalwindow = N_("E957: Invalid window number");
+
+#define SEARCH_HL_PRIORITY 0
+
+/// Add match to the match list of window 'wp'. The pattern 'pat' will be
+/// highlighted with the group 'grp' with priority 'prio'.
+/// Optionally, a desired ID 'id' can be specified (greater than or equal to 1).
+///
+/// @param[in] id a desired ID 'id' can be specified
+/// (greater than or equal to 1). -1 must be specified if no
+/// particular ID is desired
+/// @param[in] conceal_char pointer to conceal replacement char
+/// @return ID of added match, -1 on failure.
+static int match_add(win_T *wp, const char *const grp, const char *const pat, int prio, int id,
+ list_T *pos_list, const char *const conceal_char)
+ FUNC_ATTR_NONNULL_ARG(1, 2)
+{
+ matchitem_T *cur;
+ matchitem_T *prev;
+ matchitem_T *m;
+ int hlg_id;
+ regprog_T *regprog = NULL;
+ int rtype = SOME_VALID;
+
+ if (*grp == NUL || (pat != NULL && *pat == NUL)) {
+ return -1;
+ }
+ if (id < -1 || id == 0) {
+ semsg(_("E799: Invalid ID: %" PRId64
+ " (must be greater than or equal to 1)"),
+ (int64_t)id);
+ return -1;
+ }
+ if (id != -1) {
+ cur = wp->w_match_head;
+ while (cur != NULL) {
+ if (cur->id == id) {
+ semsg(_("E801: ID already taken: %" PRId64), (int64_t)id);
+ return -1;
+ }
+ cur = cur->next;
+ }
+ }
+ if ((hlg_id = syn_check_group(grp, strlen(grp))) == 0) {
+ return -1;
+ }
+ if (pat != NULL && (regprog = vim_regcomp((char_u *)pat, RE_MAGIC)) == NULL) {
+ semsg(_(e_invarg2), pat);
+ return -1;
+ }
+
+ // Find available match ID.
+ while (id == -1) {
+ cur = wp->w_match_head;
+ while (cur != NULL && cur->id != wp->w_next_match_id) {
+ cur = cur->next;
+ }
+ if (cur == NULL) {
+ id = wp->w_next_match_id;
+ }
+ wp->w_next_match_id++;
+ }
+
+ // Build new match.
+ m = xcalloc(1, sizeof(matchitem_T));
+ m->id = id;
+ m->priority = prio;
+ m->pattern = pat == NULL ? NULL: (char_u *)xstrdup(pat);
+ m->hlg_id = hlg_id;
+ m->match.regprog = regprog;
+ m->match.rmm_ic = false;
+ m->match.rmm_maxcol = 0;
+ m->conceal_char = 0;
+ if (conceal_char != NULL) {
+ m->conceal_char = utf_ptr2char((const char_u *)conceal_char);
+ }
+
+ // Set up position matches
+ if (pos_list != NULL) {
+ linenr_T toplnum = 0;
+ linenr_T botlnum = 0;
+
+ int i = 0;
+ TV_LIST_ITER(pos_list, li, {
+ linenr_T lnum = 0;
+ colnr_T col = 0;
+ int len = 1;
+ bool error = false;
+
+ if (TV_LIST_ITEM_TV(li)->v_type == VAR_LIST) {
+ const list_T *const subl = TV_LIST_ITEM_TV(li)->vval.v_list;
+ const listitem_T *subli = tv_list_first(subl);
+ if (subli == NULL) {
+ semsg(_("E5030: Empty list at position %d"),
+ (int)tv_list_idx_of_item(pos_list, li));
+ goto fail;
+ }
+ lnum = tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error);
+ if (error) {
+ goto fail;
+ }
+ if (lnum <= 0) {
+ continue;
+ }
+ m->pos.pos[i].lnum = lnum;
+ subli = TV_LIST_ITEM_NEXT(subl, subli);
+ if (subli != NULL) {
+ col = (colnr_T)tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error);
+ if (error) {
+ goto fail;
+ }
+ if (col < 0) {
+ continue;
+ }
+ subli = TV_LIST_ITEM_NEXT(subl, subli);
+ if (subli != NULL) {
+ len = (colnr_T)tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error);
+ if (len < 0) {
+ continue;
+ }
+ if (error) {
+ goto fail;
+ }
+ }
+ }
+ m->pos.pos[i].col = col;
+ m->pos.pos[i].len = len;
+ } else if (TV_LIST_ITEM_TV(li)->v_type == VAR_NUMBER) {
+ if (TV_LIST_ITEM_TV(li)->vval.v_number <= 0) {
+ continue;
+ }
+ m->pos.pos[i].lnum = TV_LIST_ITEM_TV(li)->vval.v_number;
+ m->pos.pos[i].col = 0;
+ m->pos.pos[i].len = 0;
+ } else {
+ semsg(_("E5031: List or number required at position %d"),
+ (int)tv_list_idx_of_item(pos_list, li));
+ goto fail;
+ }
+ if (toplnum == 0 || lnum < toplnum) {
+ toplnum = lnum;
+ }
+ if (botlnum == 0 || lnum >= botlnum) {
+ botlnum = lnum + 1;
+ }
+ i++;
+ if (i >= MAXPOSMATCH) {
+ break;
+ }
+ });
+
+ // Calculate top and bottom lines for redrawing area
+ if (toplnum != 0) {
+ if (wp->w_buffer->b_mod_set) {
+ if (wp->w_buffer->b_mod_top > toplnum) {
+ wp->w_buffer->b_mod_top = toplnum;
+ }
+ if (wp->w_buffer->b_mod_bot < botlnum) {
+ wp->w_buffer->b_mod_bot = botlnum;
+ }
+ } else {
+ wp->w_buffer->b_mod_set = true;
+ wp->w_buffer->b_mod_top = toplnum;
+ wp->w_buffer->b_mod_bot = botlnum;
+ wp->w_buffer->b_mod_xlines = 0;
+ }
+ m->pos.toplnum = toplnum;
+ m->pos.botlnum = botlnum;
+ rtype = VALID;
+ }
+ }
+
+ // Insert new match. The match list is in ascending order with regard to
+ // the match priorities.
+ cur = wp->w_match_head;
+ prev = cur;
+ while (cur != NULL && prio >= cur->priority) {
+ prev = cur;
+ cur = cur->next;
+ }
+ if (cur == prev) {
+ wp->w_match_head = m;
+ } else {
+ prev->next = m;
+ }
+ m->next = cur;
+
+ redraw_later(wp, rtype);
+ return id;
+
+fail:
+ xfree(m);
+ return -1;
+}
+
+/// Delete match with ID 'id' in the match list of window 'wp'.
+///
+/// @param perr print error messages if true.
+static int match_delete(win_T *wp, int id, bool perr)
+{
+ matchitem_T *cur = wp->w_match_head;
+ matchitem_T *prev = cur;
+ int rtype = SOME_VALID;
+
+ if (id < 1) {
+ if (perr) {
+ semsg(_("E802: Invalid ID: %" PRId64
+ " (must be greater than or equal to 1)"),
+ (int64_t)id);
+ }
+ return -1;
+ }
+ while (cur != NULL && cur->id != id) {
+ prev = cur;
+ cur = cur->next;
+ }
+ if (cur == NULL) {
+ if (perr) {
+ semsg(_("E803: ID not found: %" PRId64), (int64_t)id);
+ }
+ return -1;
+ }
+ if (cur == prev) {
+ wp->w_match_head = cur->next;
+ } else {
+ prev->next = cur->next;
+ }
+ vim_regfree(cur->match.regprog);
+ xfree(cur->pattern);
+ if (cur->pos.toplnum != 0) {
+ if (wp->w_buffer->b_mod_set) {
+ if (wp->w_buffer->b_mod_top > cur->pos.toplnum) {
+ wp->w_buffer->b_mod_top = cur->pos.toplnum;
+ }
+ if (wp->w_buffer->b_mod_bot < cur->pos.botlnum) {
+ wp->w_buffer->b_mod_bot = cur->pos.botlnum;
+ }
+ } else {
+ wp->w_buffer->b_mod_set = true;
+ wp->w_buffer->b_mod_top = cur->pos.toplnum;
+ wp->w_buffer->b_mod_bot = cur->pos.botlnum;
+ wp->w_buffer->b_mod_xlines = 0;
+ }
+ rtype = VALID;
+ }
+ xfree(cur);
+ redraw_later(wp, rtype);
+ return 0;
+}
+
+/// Delete all matches in the match list of window 'wp'.
+void clear_matches(win_T *wp)
+{
+ matchitem_T *m;
+
+ while (wp->w_match_head != NULL) {
+ m = wp->w_match_head->next;
+ vim_regfree(wp->w_match_head->match.regprog);
+ xfree(wp->w_match_head->pattern);
+ xfree(wp->w_match_head);
+ wp->w_match_head = m;
+ }
+ redraw_later(wp, SOME_VALID);
+}
+
+/// Get match from ID 'id' in window 'wp'.
+/// Return NULL if match not found.
+matchitem_T *get_match(win_T *wp, int id)
+{
+ matchitem_T *cur = wp->w_match_head;
+
+ while (cur != NULL && cur->id != id) {
+ cur = cur->next;
+ }
+ return cur;
+}
+
+/// Init for calling prepare_search_hl().
+void init_search_hl(win_T *wp, match_T *search_hl)
+ FUNC_ATTR_NONNULL_ALL
+{
+ // Setup for match and 'hlsearch' highlighting. Disable any previous
+ // match
+ matchitem_T *cur = wp->w_match_head;
+ while (cur != NULL) {
+ cur->hl.rm = cur->match;
+ if (cur->hlg_id == 0) {
+ cur->hl.attr = 0;
+ } else {
+ cur->hl.attr = syn_id2attr(cur->hlg_id);
+ }
+ cur->hl.buf = wp->w_buffer;
+ cur->hl.lnum = 0;
+ cur->hl.first_lnum = 0;
+ // Set the time limit to 'redrawtime'.
+ cur->hl.tm = profile_setlimit(p_rdt);
+ cur = cur->next;
+ }
+ search_hl->buf = wp->w_buffer;
+ search_hl->lnum = 0;
+ search_hl->first_lnum = 0;
+ search_hl->attr = win_hl_attr(wp, HLF_L);
+
+ // time limit is set at the toplevel, for all windows
+}
+
+/// @param shl points to a match. Fill on match.
+/// @param posmatch match positions
+/// @param mincol minimal column for a match
+///
+/// @return one on match, otherwise return zero.
+static int next_search_hl_pos(match_T *shl, linenr_T lnum, posmatch_T *posmatch, colnr_T mincol)
+ FUNC_ATTR_NONNULL_ALL
+{
+ int i;
+ int found = -1;
+
+ shl->lnum = 0;
+ for (i = posmatch->cur; i < MAXPOSMATCH; i++) {
+ llpos_T *pos = &posmatch->pos[i];
+
+ if (pos->lnum == 0) {
+ break;
+ }
+ if (pos->len == 0 && pos->col < mincol) {
+ continue;
+ }
+ if (pos->lnum == lnum) {
+ if (found >= 0) {
+ // if this match comes before the one at "found" then swap
+ // them
+ if (pos->col < posmatch->pos[found].col) {
+ llpos_T tmp = *pos;
+
+ *pos = posmatch->pos[found];
+ posmatch->pos[found] = tmp;
+ }
+ } else {
+ found = i;
+ }
+ }
+ }
+ posmatch->cur = 0;
+ if (found >= 0) {
+ colnr_T start = posmatch->pos[found].col == 0
+ ? 0: posmatch->pos[found].col - 1;
+ colnr_T end = posmatch->pos[found].col == 0
+ ? MAXCOL : start + posmatch->pos[found].len;
+
+ shl->lnum = lnum;
+ shl->rm.startpos[0].lnum = 0;
+ shl->rm.startpos[0].col = start;
+ shl->rm.endpos[0].lnum = 0;
+ shl->rm.endpos[0].col = end;
+ shl->is_addpos = true;
+ posmatch->cur = found + 1;
+ return 1;
+ }
+ return 0;
+}
+
+/// Search for a next 'hlsearch' or match.
+/// Uses shl->buf.
+/// Sets shl->lnum and shl->rm contents.
+/// Note: Assumes a previous match is always before "lnum", unless
+/// shl->lnum is zero.
+/// Careful: Any pointers for buffer lines will become invalid.
+///
+/// @param shl points to search_hl or a match
+/// @param mincol minimal column for a match
+/// @param cur to retrieve match positions if any
+static void next_search_hl(win_T *win, match_T *search_hl, match_T *shl, linenr_T lnum,
+ colnr_T mincol, matchitem_T *cur)
+ FUNC_ATTR_NONNULL_ARG(2)
+{
+ linenr_T l;
+ colnr_T matchcol;
+ long nmatched = 0;
+ int save_called_emsg = called_emsg;
+
+ // for :{range}s/pat only highlight inside the range
+ if (lnum < search_first_line || lnum > search_last_line) {
+ shl->lnum = 0;
+ return;
+ }
+
+ if (shl->lnum != 0) {
+ // Check for three situations:
+ // 1. If the "lnum" is below a previous match, start a new search.
+ // 2. If the previous match includes "mincol", use it.
+ // 3. Continue after the previous match.
+ l = shl->lnum + shl->rm.endpos[0].lnum - shl->rm.startpos[0].lnum;
+ if (lnum > l) {
+ shl->lnum = 0;
+ } else if (lnum < l || shl->rm.endpos[0].col > mincol) {
+ return;
+ }
+ }
+
+ // Repeat searching for a match until one is found that includes "mincol"
+ // or none is found in this line.
+ called_emsg = false;
+ for (;;) {
+ // Stop searching after passing the time limit.
+ if (profile_passed_limit(shl->tm)) {
+ shl->lnum = 0; // no match found in time
+ break;
+ }
+ // Three situations:
+ // 1. No useful previous match: search from start of line.
+ // 2. Not Vi compatible or empty match: continue at next character.
+ // Break the loop if this is beyond the end of the line.
+ // 3. Vi compatible searching: continue at end of previous match.
+ if (shl->lnum == 0) {
+ matchcol = 0;
+ } else if (vim_strchr(p_cpo, CPO_SEARCH) == NULL
+ || (shl->rm.endpos[0].lnum == 0
+ && shl->rm.endpos[0].col <= shl->rm.startpos[0].col)) {
+ char_u *ml;
+
+ matchcol = shl->rm.startpos[0].col;
+ ml = ml_get_buf(shl->buf, lnum, false) + matchcol;
+ if (*ml == NUL) {
+ matchcol++;
+ shl->lnum = 0;
+ break;
+ }
+ matchcol += utfc_ptr2len(ml);
+ } else {
+ matchcol = shl->rm.endpos[0].col;
+ }
+
+ shl->lnum = lnum;
+ if (shl->rm.regprog != NULL) {
+ // Remember whether shl->rm is using a copy of the regprog in
+ // cur->match.
+ bool regprog_is_copy = (shl != search_hl
+ && cur != NULL
+ && shl == &cur->hl
+ && cur->match.regprog == cur->hl.rm.regprog);
+ int timed_out = false;
+
+ nmatched = vim_regexec_multi(&shl->rm, win, shl->buf, lnum, matchcol,
+ &(shl->tm), &timed_out);
+ // Copy the regprog, in case it got freed and recompiled.
+ if (regprog_is_copy) {
+ cur->match.regprog = cur->hl.rm.regprog;
+ }
+ if (called_emsg || got_int || timed_out) {
+ // Error while handling regexp: stop using this regexp.
+ if (shl == search_hl) {
+ // don't free regprog in the match list, it's a copy
+ vim_regfree(shl->rm.regprog);
+ set_no_hlsearch(true);
+ }
+ shl->rm.regprog = NULL;
+ shl->lnum = 0;
+ got_int = false; // avoid the "Type :quit to exit Vim" message
+ break;
+ }
+ } else if (cur != NULL) {
+ nmatched = next_search_hl_pos(shl, lnum, &(cur->pos), matchcol);
+ }
+ if (nmatched == 0) {
+ shl->lnum = 0; // no match found
+ break;
+ }
+ if (shl->rm.startpos[0].lnum > 0
+ || shl->rm.startpos[0].col >= mincol
+ || nmatched > 1
+ || shl->rm.endpos[0].col > mincol) {
+ shl->lnum += shl->rm.startpos[0].lnum;
+ break; // useful match found
+ }
+
+ // Restore called_emsg for assert_fails().
+ called_emsg = save_called_emsg;
+ }
+}
+
+/// Advance to the match in window "wp" line "lnum" or past it.
+void prepare_search_hl(win_T *wp, match_T *search_hl, linenr_T lnum)
+ FUNC_ATTR_NONNULL_ALL
+{
+ matchitem_T *cur; // points to the match list
+ match_T *shl; // points to search_hl or a match
+ bool shl_flag; // flag to indicate whether search_hl
+ // has been processed or not
+
+ // When using a multi-line pattern, start searching at the top
+ // of the window or just after a closed fold.
+ // Do this both for search_hl and the match list.
+ cur = wp->w_match_head;
+ shl_flag = false;
+ while (cur != NULL || shl_flag == false) {
+ if (shl_flag == false) {
+ shl = search_hl;
+ shl_flag = true;
+ } else {
+ shl = &cur->hl; // -V595
+ }
+ if (shl->rm.regprog != NULL
+ && shl->lnum == 0
+ && re_multiline(shl->rm.regprog)) {
+ if (shl->first_lnum == 0) {
+ for (shl->first_lnum = lnum;
+ shl->first_lnum > wp->w_topline;
+ shl->first_lnum--) {
+ if (hasFoldingWin(wp, shl->first_lnum - 1, NULL, NULL, true, NULL)) {
+ break;
+ }
+ }
+ }
+ if (cur != NULL) {
+ cur->pos.cur = 0;
+ }
+ bool pos_inprogress = true; // mark that a position match search is
+ // in progress
+ int n = 0;
+ while (shl->first_lnum < lnum && (shl->rm.regprog != NULL
+ || (cur != NULL && pos_inprogress))) {
+ next_search_hl(wp, search_hl, shl, shl->first_lnum, (colnr_T)n,
+ shl == search_hl ? NULL : cur);
+ pos_inprogress = !(cur == NULL || cur->pos.cur == 0);
+ if (shl->lnum != 0) {
+ shl->first_lnum = shl->lnum
+ + shl->rm.endpos[0].lnum
+ - shl->rm.startpos[0].lnum;
+ n = shl->rm.endpos[0].col;
+ } else {
+ shl->first_lnum++;
+ n = 0;
+ }
+ }
+ }
+ if (shl != search_hl && cur != NULL) {
+ cur = cur->next;
+ }
+ }
+}
+
+/// Prepare for 'hlsearch' and match highlighting in one window line.
+/// Return true if there is such highlighting and set "search_attr" to the
+/// current highlight attribute.
+bool prepare_search_hl_line(win_T *wp, linenr_T lnum, colnr_T mincol, char_u **line,
+ match_T *search_hl, int *search_attr, bool *search_attr_from_match)
+{
+ matchitem_T *cur = wp->w_match_head; // points to the match list
+ match_T *shl; // points to search_hl or a match
+ bool shl_flag = false; // flag to indicate whether search_hl
+ // has been processed or not
+ bool area_highlighting = false;
+
+ // Handle highlighting the last used search pattern and matches.
+ // Do this for both search_hl and the match list.
+ while (cur != NULL || !shl_flag) {
+ if (!shl_flag) {
+ shl = search_hl;
+ shl_flag = true;
+ } else {
+ shl = &cur->hl; // -V595
+ }
+ shl->startcol = MAXCOL;
+ shl->endcol = MAXCOL;
+ shl->attr_cur = 0;
+ shl->is_addpos = false;
+ if (cur != NULL) {
+ cur->pos.cur = 0;
+ }
+ next_search_hl(wp, search_hl, shl, lnum, mincol,
+ shl == search_hl ? NULL : cur);
+
+ // Need to get the line again, a multi-line regexp may have made it
+ // invalid.
+ *line = ml_get_buf(wp->w_buffer, lnum, false);
+
+ if (shl->lnum != 0 && shl->lnum <= lnum) {
+ if (shl->lnum == lnum) {
+ shl->startcol = shl->rm.startpos[0].col;
+ } else {
+ shl->startcol = 0;
+ }
+ if (lnum == shl->lnum + shl->rm.endpos[0].lnum
+ - shl->rm.startpos[0].lnum) {
+ shl->endcol = shl->rm.endpos[0].col;
+ } else {
+ shl->endcol = MAXCOL;
+ }
+ // Highlight one character for an empty match.
+ if (shl->startcol == shl->endcol) {
+ if ((*line)[shl->endcol] != NUL) {
+ shl->endcol += utfc_ptr2len(*line + shl->endcol);
+ } else {
+ shl->endcol++;
+ }
+ }
+ if ((long)shl->startcol < mincol) { // match at leftcol
+ shl->attr_cur = shl->attr;
+ *search_attr = shl->attr;
+ *search_attr_from_match = shl != search_hl;
+ }
+ area_highlighting = true;
+ }
+ if (shl != search_hl && cur != NULL) {
+ cur = cur->next;
+ }
+ }
+ return area_highlighting;
+}
+
+/// For a position in a line: Check for start/end of 'hlsearch' and other
+/// matches.
+/// After end, check for start/end of next match.
+/// When another match, have to check for start again.
+/// Watch out for matching an empty string!
+/// Return the updated search_attr.
+int update_search_hl(win_T *wp, linenr_T lnum, colnr_T col, char_u **line, match_T *search_hl,
+ int *has_match_conc, int *match_conc, int lcs_eol_one,
+ bool *search_attr_from_match)
+{
+ matchitem_T *cur = wp->w_match_head; // points to the match list
+ match_T *shl; // points to search_hl or a match
+ bool shl_flag = false; // flag to indicate whether search_hl
+ // has been processed or not
+ int search_attr = 0;
+
+ // Do this for 'search_hl' and the match list (ordered by priority).
+ while (cur != NULL || !shl_flag) {
+ if (!shl_flag
+ && (cur == NULL || cur->priority > SEARCH_HL_PRIORITY)) {
+ shl = search_hl;
+ shl_flag = true;
+ } else {
+ shl = &cur->hl;
+ }
+ if (cur != NULL) {
+ cur->pos.cur = 0;
+ }
+ bool pos_inprogress = true; // mark that a position match search is
+ // in progress
+ while (shl->rm.regprog != NULL
+ || (cur != NULL && pos_inprogress)) {
+ if (shl->startcol != MAXCOL
+ && col >= shl->startcol
+ && col < shl->endcol) {
+ int next_col = col + utfc_ptr2len(*line + col);
+
+ if (shl->endcol < next_col) {
+ shl->endcol = next_col;
+ }
+ shl->attr_cur = shl->attr;
+ // Match with the "Conceal" group results in hiding
+ // the match.
+ if (cur != NULL
+ && shl != search_hl
+ && syn_name2id("Conceal") == cur->hlg_id) {
+ *has_match_conc = col == shl->startcol ? 2 : 1;
+ *match_conc = cur->conceal_char;
+ } else {
+ *has_match_conc = 0;
+ }
+ } else if (col == shl->endcol) {
+ shl->attr_cur = 0;
+
+ next_search_hl(wp, search_hl, shl, lnum, col,
+ shl == search_hl ? NULL : cur);
+ pos_inprogress = !(cur == NULL || cur->pos.cur == 0);
+
+ // Need to get the line again, a multi-line regexp
+ // may have made it invalid.
+ *line = ml_get_buf(wp->w_buffer, lnum, false);
+
+ if (shl->lnum == lnum) {
+ shl->startcol = shl->rm.startpos[0].col;
+ if (shl->rm.endpos[0].lnum == 0) {
+ shl->endcol = shl->rm.endpos[0].col;
+ } else {
+ shl->endcol = MAXCOL;
+ }
+
+ if (shl->startcol == shl->endcol) {
+ // highlight empty match, try again after it
+ shl->endcol += utfc_ptr2len(*line + shl->endcol);
+ }
+
+ // Loop to check if the match starts at the
+ // current position
+ continue;
+ }
+ }
+ break;
+ }
+ if (shl != search_hl && cur != NULL) {
+ cur = cur->next;
+ }
+ }
+
+ // Use attributes from match with highest priority among
+ // 'search_hl' and the match list.
+ *search_attr_from_match = false;
+ search_attr = search_hl->attr_cur;
+ cur = wp->w_match_head;
+ shl_flag = false;
+ while (cur != NULL || !shl_flag) {
+ if (!shl_flag
+ && (cur == NULL || cur->priority > SEARCH_HL_PRIORITY)) {
+ shl = search_hl;
+ shl_flag = true;
+ } else {
+ shl = &cur->hl;
+ }
+ if (shl->attr_cur != 0) {
+ search_attr = shl->attr_cur;
+ *search_attr_from_match = shl != search_hl;
+ }
+ if (shl != search_hl && cur != NULL) {
+ cur = cur->next;
+ }
+ }
+ // Only highlight one character after the last column.
+ if (*(*line + col) == NUL && (wp->w_p_list && lcs_eol_one == -1)) {
+ search_attr = 0;
+ }
+ return search_attr;
+}
+
+bool get_prevcol_hl_flag(win_T *wp, match_T *search_hl, long curcol)
+{
+ long prevcol = curcol;
+ matchitem_T *cur; // points to the match list
+
+ // we're not really at that column when skipping some text
+ if ((long)(wp->w_p_wrap ? wp->w_skipcol : wp->w_leftcol) > prevcol) {
+ prevcol++;
+ }
+
+ if (!search_hl->is_addpos && prevcol == search_hl->startcol) {
+ return true;
+ } else {
+ cur = wp->w_match_head;
+ while (cur != NULL) {
+ if (!cur->hl.is_addpos && prevcol == cur->hl.startcol) {
+ return true;
+ }
+ cur = cur->next;
+ }
+ }
+ return false;
+}
+
+/// Get highlighting for the char after the text in "char_attr" from 'hlsearch'
+/// or match highlighting.
+void get_search_match_hl(win_T *wp, match_T *search_hl, long col, int *char_attr)
+{
+ matchitem_T *cur = wp->w_match_head; // points to the match list
+ match_T *shl; // points to search_hl or a match
+ bool shl_flag = false; // flag to indicate whether search_hl
+ // has been processed or not
+
+ *char_attr = search_hl->attr;
+ while (cur != NULL || !shl_flag) {
+ if (!shl_flag
+ && (cur == NULL || cur->priority > SEARCH_HL_PRIORITY)) {
+ shl = search_hl;
+ shl_flag = true;
+ } else {
+ shl = &cur->hl;
+ }
+ if (col - 1 == (long)shl->startcol
+ && (shl == search_hl || !shl->is_addpos)) {
+ *char_attr = shl->attr;
+ }
+ if (shl != search_hl && cur != NULL) {
+ cur = cur->next;
+ }
+ }
+}
+
+static int matchadd_dict_arg(typval_T *tv, const char **conceal_char, win_T **win)
+{
+ dictitem_T *di;
+
+ if (tv->v_type != VAR_DICT) {
+ emsg(_(e_dictreq));
+ return FAIL;
+ }
+
+ if ((di = tv_dict_find(tv->vval.v_dict, S_LEN("conceal"))) != NULL) {
+ *conceal_char = tv_get_string(&di->di_tv);
+ }
+
+ if ((di = tv_dict_find(tv->vval.v_dict, S_LEN("window"))) != NULL) {
+ *win = find_win_by_nr_or_id(&di->di_tv);
+ if (*win == NULL) {
+ emsg(_(e_invalwindow));
+ return FAIL;
+ }
+ }
+
+ return OK;
+}
+
+/// "clearmatches()" function
+void f_clearmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ win_T *win = get_optional_window(argvars, 0);
+
+ if (win != NULL) {
+ clear_matches(win);
+ }
+}
+
+/// "getmatches()" function
+void f_getmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ matchitem_T *cur;
+ int i;
+ win_T *win = get_optional_window(argvars, 0);
+
+ if (win == NULL) {
+ return;
+ }
+
+ tv_list_alloc_ret(rettv, kListLenMayKnow);
+ cur = win->w_match_head;
+ while (cur != NULL) {
+ dict_T *dict = tv_dict_alloc();
+ if (cur->match.regprog == NULL) {
+ // match added with matchaddpos()
+ for (i = 0; i < MAXPOSMATCH; i++) {
+ llpos_T *llpos;
+ char buf[30]; // use 30 to avoid compiler warning
+
+ llpos = &cur->pos.pos[i];
+ if (llpos->lnum == 0) {
+ break;
+ }
+ list_T *const l = tv_list_alloc(1 + (llpos->col > 0 ? 2 : 0));
+ tv_list_append_number(l, (varnumber_T)llpos->lnum);
+ if (llpos->col > 0) {
+ tv_list_append_number(l, (varnumber_T)llpos->col);
+ tv_list_append_number(l, (varnumber_T)llpos->len);
+ }
+ int len = snprintf(buf, sizeof(buf), "pos%d", i + 1);
+ assert((size_t)len < sizeof(buf));
+ tv_dict_add_list(dict, buf, (size_t)len, l);
+ }
+ } else {
+ tv_dict_add_str(dict, S_LEN("pattern"), (const char *)cur->pattern);
+ }
+ tv_dict_add_str(dict, S_LEN("group"),
+ (const char *)syn_id2name(cur->hlg_id));
+ tv_dict_add_nr(dict, S_LEN("priority"), (varnumber_T)cur->priority);
+ tv_dict_add_nr(dict, S_LEN("id"), (varnumber_T)cur->id);
+
+ if (cur->conceal_char) {
+ char buf[MB_MAXBYTES + 1];
+
+ buf[utf_char2bytes(cur->conceal_char, (char_u *)buf)] = NUL;
+ tv_dict_add_str(dict, S_LEN("conceal"), buf);
+ }
+
+ tv_list_append_dict(rettv->vval.v_list, dict);
+ cur = cur->next;
+ }
+}
+
+/// "setmatches()" function
+void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ dict_T *d;
+ list_T *s = NULL;
+ win_T *win = get_optional_window(argvars, 1);
+
+ rettv->vval.v_number = -1;
+ if (argvars[0].v_type != VAR_LIST) {
+ emsg(_(e_listreq));
+ return;
+ }
+ if (win == NULL) {
+ return;
+ }
+
+ list_T *const l = argvars[0].vval.v_list;
+ // To some extent make sure that we are dealing with a list from
+ // "getmatches()".
+ int li_idx = 0;
+ TV_LIST_ITER_CONST(l, li, {
+ if (TV_LIST_ITEM_TV(li)->v_type != VAR_DICT
+ || (d = TV_LIST_ITEM_TV(li)->vval.v_dict) == NULL) {
+ semsg(_("E474: List item %d is either not a dictionary "
+ "or an empty one"), li_idx);
+ return;
+ }
+ if (!(tv_dict_find(d, S_LEN("group")) != NULL
+ && (tv_dict_find(d, S_LEN("pattern")) != NULL
+ || tv_dict_find(d, S_LEN("pos1")) != NULL)
+ && tv_dict_find(d, S_LEN("priority")) != NULL
+ && tv_dict_find(d, S_LEN("id")) != NULL)) {
+ semsg(_("E474: List item %d is missing one of the required keys"),
+ li_idx);
+ return;
+ }
+ li_idx++;
+ });
+
+ clear_matches(win);
+ bool match_add_failed = false;
+ TV_LIST_ITER_CONST(l, li, {
+ int i = 0;
+
+ d = TV_LIST_ITEM_TV(li)->vval.v_dict;
+ dictitem_T *const di = tv_dict_find(d, S_LEN("pattern"));
+ if (di == NULL) {
+ if (s == NULL) {
+ s = tv_list_alloc(9);
+ }
+
+ // match from matchaddpos()
+ for (i = 1; i < 9; i++) {
+ char buf[30]; // use 30 to avoid compiler warning
+ snprintf(buf, sizeof(buf), "pos%d", i);
+ dictitem_T *const pos_di = tv_dict_find(d, buf, -1);
+ if (pos_di != NULL) {
+ if (pos_di->di_tv.v_type != VAR_LIST) {
+ return;
+ }
+
+ tv_list_append_tv(s, &pos_di->di_tv);
+ tv_list_ref(s);
+ } else {
+ break;
+ }
+ }
+ }
+
+ // Note: there are three number buffers involved:
+ // - group_buf below.
+ // - numbuf in tv_dict_get_string().
+ // - mybuf in tv_get_string().
+ //
+ // If you change this code make sure that buffers will not get
+ // accidentally reused.
+ char group_buf[NUMBUFLEN];
+ const char *const group = tv_dict_get_string_buf(d, "group", group_buf);
+ const int priority = (int)tv_dict_get_number(d, "priority");
+ const int id = (int)tv_dict_get_number(d, "id");
+ dictitem_T *const conceal_di = tv_dict_find(d, S_LEN("conceal"));
+ const char *const conceal = (conceal_di != NULL
+ ? tv_get_string(&conceal_di->di_tv)
+ : NULL);
+ if (i == 0) {
+ if (match_add(win, group,
+ tv_dict_get_string(d, "pattern", false),
+ priority, id, NULL, conceal) != id) {
+ match_add_failed = true;
+ }
+ } else {
+ if (match_add(win, group, NULL, priority, id, s, conceal) != id) {
+ match_add_failed = true;
+ }
+ tv_list_unref(s);
+ s = NULL;
+ }
+ });
+ if (!match_add_failed) {
+ rettv->vval.v_number = 0;
+ }
+}
+
+/// "matchadd()" function
+void f_matchadd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char grpbuf[NUMBUFLEN];
+ char patbuf[NUMBUFLEN];
+ // group
+ const char *const grp = tv_get_string_buf_chk(&argvars[0], grpbuf);
+ // pattern
+ const char *const pat = tv_get_string_buf_chk(&argvars[1], patbuf);
+ // default priority
+ int prio = 10;
+ int id = -1;
+ bool error = false;
+ const char *conceal_char = NULL;
+ win_T *win = curwin;
+
+ rettv->vval.v_number = -1;
+
+ if (grp == NULL || pat == NULL) {
+ return;
+ }
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ prio = (int)tv_get_number_chk(&argvars[2], &error);
+ if (argvars[3].v_type != VAR_UNKNOWN) {
+ id = (int)tv_get_number_chk(&argvars[3], &error);
+ if (argvars[4].v_type != VAR_UNKNOWN
+ && matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL) {
+ return;
+ }
+ }
+ }
+ if (error) {
+ return;
+ }
+ if (id >= 1 && id <= 3) {
+ semsg(_("E798: ID is reserved for \":match\": %" PRId64), (int64_t)id);
+ return;
+ }
+
+ rettv->vval.v_number = match_add(win, grp, pat, prio, id, NULL, conceal_char);
+}
+
+/// "matchaddpo()" function
+void f_matchaddpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = -1;
+
+ char buf[NUMBUFLEN];
+ const char *const group = tv_get_string_buf_chk(&argvars[0], buf);
+ if (group == NULL) {
+ return;
+ }
+
+ if (argvars[1].v_type != VAR_LIST) {
+ semsg(_(e_listarg), "matchaddpos()");
+ return;
+ }
+
+ list_T *l;
+ l = argvars[1].vval.v_list;
+ if (l == NULL) {
+ return;
+ }
+
+ bool error = false;
+ int prio = 10;
+ int id = -1;
+ const char *conceal_char = NULL;
+ win_T *win = curwin;
+
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ prio = (int)tv_get_number_chk(&argvars[2], &error);
+ if (argvars[3].v_type != VAR_UNKNOWN) {
+ id = (int)tv_get_number_chk(&argvars[3], &error);
+ if (argvars[4].v_type != VAR_UNKNOWN
+ && matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL) {
+ return;
+ }
+ }
+ }
+ if (error == true) {
+ return;
+ }
+
+ // id == 3 is ok because matchaddpos() is supposed to substitute :3match
+ if (id == 1 || id == 2) {
+ semsg(_("E798: ID is reserved for \"match\": %" PRId64), (int64_t)id);
+ return;
+ }
+
+ rettv->vval.v_number = match_add(win, group, NULL, prio, id, l, conceal_char);
+}
+
+/// "matcharg()" function
+void f_matcharg(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const int id = (int)tv_get_number(&argvars[0]);
+
+ tv_list_alloc_ret(rettv, (id >= 1 && id <= 3
+ ? 2
+ : 0));
+
+ if (id >= 1 && id <= 3) {
+ matchitem_T *const m = get_match(curwin, id);
+
+ if (m != NULL) {
+ tv_list_append_string(rettv->vval.v_list,
+ (const char *)syn_id2name(m->hlg_id), -1);
+ tv_list_append_string(rettv->vval.v_list, (const char *)m->pattern, -1);
+ } else {
+ tv_list_append_string(rettv->vval.v_list, NULL, 0);
+ tv_list_append_string(rettv->vval.v_list, NULL, 0);
+ }
+ }
+}
+
+/// "matchdelete()" function
+void f_matchdelete(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ win_T *win = get_optional_window(argvars, 1);
+ if (win == NULL) {
+ rettv->vval.v_number = -1;
+ } else {
+ rettv->vval.v_number = match_delete(win,
+ (int)tv_get_number(&argvars[0]), true);
+ }
+}
+
+/// ":[N]match {group} {pattern}"
+/// Sets nextcmd to the start of the next command, if any. Also called when
+/// skipping commands to find the next command.
+void ex_match(exarg_T *eap)
+{
+ char_u *p;
+ char_u *g = NULL;
+ char_u *end;
+ int c;
+ int id;
+
+ if (eap->line2 <= 3) {
+ id = (int)eap->line2;
+ } else {
+ emsg(e_invcmd);
+ return;
+ }
+
+ // First clear any old pattern.
+ if (!eap->skip) {
+ match_delete(curwin, id, false);
+ }
+
+ if (ends_excmd(*eap->arg)) {
+ end = eap->arg;
+ } else if ((STRNICMP(eap->arg, "none", 4) == 0
+ && (ascii_iswhite(eap->arg[4]) || ends_excmd(eap->arg[4])))) {
+ end = eap->arg + 4;
+ } else {
+ p = skiptowhite(eap->arg);
+ if (!eap->skip) {
+ g = vim_strnsave(eap->arg, (size_t)(p - eap->arg));
+ }
+ p = skipwhite(p);
+ if (*p == NUL) {
+ // There must be two arguments.
+ xfree(g);
+ semsg(_(e_invarg2), eap->arg);
+ return;
+ }
+ end = skip_regexp(p + 1, *p, true, NULL);
+ if (!eap->skip) {
+ if (*end != NUL && !ends_excmd(*skipwhite(end + 1))) {
+ xfree(g);
+ eap->errmsg = e_trailing;
+ return;
+ }
+ if (*end != *p) {
+ xfree(g);
+ semsg(_(e_invarg2), p);
+ return;
+ }
+
+ c = *end;
+ *end = NUL;
+ match_add(curwin, (const char *)g, (const char *)p + 1, 10, id,
+ NULL, NULL);
+ xfree(g);
+ *end = (char_u)c;
+ }
+ }
+ eap->nextcmd = find_nextcmd(end);
+}
+
diff --git a/src/nvim/match.h b/src/nvim/match.h
new file mode 100644
index 0000000000..fdcec0ae05
--- /dev/null
+++ b/src/nvim/match.h
@@ -0,0 +1,12 @@
+#ifndef NVIM_MATCH_H
+#define NVIM_MATCH_H
+
+#include "nvim/buffer_defs.h"
+#include "nvim/eval/funcs.h"
+#include "nvim/ex_cmds_defs.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "match.h.generated.h"
+#endif
+
+#endif // NVIM_MATCH_H
diff --git a/src/nvim/memline.c b/src/nvim/memline.c
index 59f57aa667..1df32f79e1 100644
--- a/src/nvim/memline.c
+++ b/src/nvim/memline.c
@@ -242,11 +242,9 @@ typedef enum {
# include "memline.c.generated.h"
#endif
-/*
- * Open a new memline for "buf".
- *
- * Return FAIL for failure, OK otherwise.
- */
+/// Open a new memline for "buf".
+///
+/// @return FAIL for failure, OK otherwise.
int ml_open(buf_T *buf)
{
bhdr_T *hp = NULL;
@@ -379,10 +377,8 @@ error:
return FAIL;
}
-/*
- * ml_setname() is called when the file name of "buf" has been changed.
- * It may rename the swap file.
- */
+/// ml_setname() is called when the file name of "buf" has been changed.
+/// It may rename the swap file.
void ml_setname(buf_T *buf)
{
bool success = false;
@@ -458,11 +454,9 @@ void ml_setname(buf_T *buf)
}
}
-/*
- * Open a file for the memfile for all buffers that are not readonly or have
- * been modified.
- * Used when 'updatecount' changes from zero to non-zero.
- */
+/// Open a file for the memfile for all buffers that are not readonly or have
+/// been modified.
+/// Used when 'updatecount' changes from zero to non-zero.
void ml_open_files(void)
{
FOR_ALL_BUFFERS(buf) {
@@ -472,11 +466,9 @@ void ml_open_files(void)
}
}
-/*
- * Open a swap file for an existing memfile, if there is no swap file yet.
- * If we are unable to find a file name, mf_fname will be NULL
- * and the memfile will be in memory only (no recovery possible).
- */
+/// Open a swap file for an existing memfile, if there is no swap file yet.
+/// If we are unable to find a file name, mf_fname will be NULL
+/// and the memfile will be in memory only (no recovery possible).
void ml_open_file(buf_T *buf)
{
memfile_T *mfp;
@@ -563,10 +555,9 @@ void check_need_swap(bool newfile)
msg_silent = old_msg_silent;
}
-/*
- * Close memline for buffer 'buf'.
- * If 'del_file' is TRUE, delete the swap file
- */
+/// Close memline for buffer 'buf'.
+///
+/// @param del_file if TRUE, delete the swap file
void ml_close(buf_T *buf, int del_file)
{
if (buf->b_ml.ml_mfp == NULL) { // not open
@@ -585,25 +576,21 @@ void ml_close(buf_T *buf, int del_file)
buf->b_flags &= ~BF_RECOVERED;
}
-/*
- * Close all existing memlines and memfiles.
- * Only used when exiting.
- * When 'del_file' is TRUE, delete the memfiles.
- * But don't delete files that were ":preserve"d when we are POSIX compatible.
- */
-void ml_close_all(int del_file)
+/// Close all existing memlines and memfiles.
+/// Only used when exiting.
+///
+/// @param del_file if true, delete the memfiles.
+void ml_close_all(bool del_file)
{
FOR_ALL_BUFFERS(buf) {
- ml_close(buf, del_file && ((buf->b_flags & BF_PRESERVED) == 0));
+ ml_close(buf, del_file);
}
spell_delete_wordlist(); // delete the internal wordlist
vim_deltempdir(); // delete created temp directory
}
-/*
- * Close all memfiles for not modified buffers.
- * Only use just before exiting!
- */
+/// Close all memfiles for not modified buffers.
+/// Only use just before exiting!
void ml_close_notmod(void)
{
FOR_ALL_BUFFERS(buf) {
@@ -613,10 +600,8 @@ void ml_close_notmod(void)
}
}
-/*
- * Update the timestamp in the .swp file.
- * Used when the file has been written.
- */
+/// Update the timestamp in the .swp file.
+/// Used when the file has been written.
void ml_timestamp(buf_T *buf)
{
ml_upd_block0(buf, UB_FNAME);
@@ -639,9 +624,7 @@ static bool ml_check_b0_strings(ZERO_BL *b0p)
&& memchr(b0p->b0_fname, NUL, B0_FNAME_SIZE_CRYPT)); // -V512
}
-/*
- * Update the timestamp or the B0_SAME_DIR flag of the .swp file.
- */
+/// Update the timestamp or the B0_SAME_DIR flag of the .swp file.
static void ml_upd_block0(buf_T *buf, upd_block0_T what)
{
memfile_T *mfp;
@@ -665,11 +648,9 @@ static void ml_upd_block0(buf_T *buf, upd_block0_T what)
mf_put(mfp, hp, true, false);
}
-/*
- * Write file name and timestamp into block 0 of a swap file.
- * Also set buf->b_mtime.
- * Don't use NameBuff[]!!!
- */
+/// Write file name and timestamp into block 0 of a swap file.
+/// Also set buf->b_mtime.
+/// Don't use NameBuff[]!!!
static void set_b0_fname(ZERO_BL *b0p, buf_T *buf)
{
if (buf->b_ffname == NULL) {
@@ -721,12 +702,10 @@ static void set_b0_fname(ZERO_BL *b0p, buf_T *buf)
add_b0_fenc(b0p, curbuf);
}
-/*
- * Update the B0_SAME_DIR flag of the swap file. It's set if the file and the
- * swapfile for "buf" are in the same directory.
- * This is fail safe: if we are not sure the directories are equal the flag is
- * not set.
- */
+/// Update the B0_SAME_DIR flag of the swap file. It's set if the file and the
+/// swapfile for "buf" are in the same directory.
+/// This is fail safe: if we are not sure the directories are equal the flag is
+/// not set.
static void set_b0_dir_flag(ZERO_BL *b0p, buf_T *buf)
{
if (same_directory(buf->b_ml.ml_mfp->mf_fname, buf->b_ffname)) {
@@ -736,9 +715,7 @@ static void set_b0_dir_flag(ZERO_BL *b0p, buf_T *buf)
}
}
-/*
- * When there is room, add the 'fileencoding' to block zero.
- */
+/// When there is room, add the 'fileencoding' to block zero.
static void add_b0_fenc(ZERO_BL *b0p, buf_T *buf)
{
int n;
@@ -757,8 +734,9 @@ static void add_b0_fenc(ZERO_BL *b0p, buf_T *buf)
/// Try to recover curbuf from the .swp file.
-/// @param checkext If true, check the extension and detect whether it is a
-/// swap file.
+///
+/// @param checkext if true, check the extension and detect whether it is a
+/// swap file.
void ml_recover(bool checkext)
{
buf_T *buf = NULL;
@@ -1465,10 +1443,8 @@ int recover_names(char_u *fname, int list, int nr, char_u **fname_out)
return file_count;
}
-/*
- * Append the full path to name with path separators made into percent
- * signs, to dir. An unnamed buffer is handled as "" (<currentdir>/"")
- */
+/// Append the full path to name with path separators made into percent
+/// signs, to dir. An unnamed buffer is handled as "" (<currentdir>/"")
char *make_percent_swname(const char *dir, const char *name)
FUNC_ATTR_NONNULL_ARG(1)
{
@@ -1490,8 +1466,9 @@ char *make_percent_swname(const char *dir, const char *name)
static bool process_still_running;
-/// Return information found in swapfile "fname" in dictionary "d".
/// This is used by the swapinfo() function.
+///
+/// @return information found in swapfile "fname" in dictionary "d".
void get_b0_dict(const char *fname, dict_T *d)
{
int fd;
@@ -1528,7 +1505,8 @@ void get_b0_dict(const char *fname, dict_T *d)
}
/// Give information about an existing swap file.
-/// Returns timestamp (0 when unknown).
+///
+/// @return timestamp (0 when unknown).
static time_t swapfile_info(char_u *fname)
{
assert(fname != NULL);
@@ -1618,9 +1596,9 @@ static time_t swapfile_info(char_u *fname)
return x;
}
-/// Returns TRUE if the swap file looks OK and there are no changes, thus it
-/// can be safely deleted.
-static time_t swapfile_unchanged(char *fname)
+/// @return true if the swap file looks OK and there are no changes, thus it
+/// can be safely deleted.
+static bool swapfile_unchanged(char *fname)
{
struct block0 b0;
int ret = true;
@@ -1698,13 +1676,12 @@ static int recov_file_names(char_u **names, char_u *path, int prepend_dot)
return num_names;
}
-/*
- * sync all memlines
- *
- * If 'check_file' is TRUE, check if original file exists and was not changed.
- * If 'check_char' is TRUE, stop syncing when character becomes available, but
- * always sync at least one block.
- */
+/// sync all memlines
+///
+/// @param check_file if TRUE, check if original file exists and was not changed.
+/// @param check_char if TRUE, stop syncing when character becomes available, but
+///
+/// always sync at least one block.
void ml_sync_all(int check_file, int check_char, bool do_fsync)
{
FOR_ALL_BUFFERS(buf) {
@@ -1740,16 +1717,14 @@ void ml_sync_all(int check_file, int check_char, bool do_fsync)
}
}
-/*
- * sync one buffer, including negative blocks
- *
- * after this all the blocks are in the swap file
- *
- * Used for the :preserve command and when the original file has been
- * changed or deleted.
- *
- * when message is TRUE the success of preserving is reported
- */
+/// sync one buffer, including negative blocks
+///
+/// after this all the blocks are in the swap file
+///
+/// Used for the :preserve command and when the original file has been
+/// changed or deleted.
+///
+/// @param message if TRUE, the success of preserving is reported.
void ml_preserve(buf_T *buf, int message, bool do_fsync)
{
bhdr_T *hp;
@@ -1825,27 +1800,24 @@ theend:
* line2 = ml_get(2); // line1 is now invalid!
* Make a copy of the line if necessary.
*/
-/*
- * Return a pointer to a (read-only copy of a) line.
- *
- * On failure an error message is given and IObuff is returned (to avoid
- * having to check for error everywhere).
- */
+
+/// @return a pointer to a (read-only copy of a) line.
+///
+/// On failure an error message is given and IObuff is returned (to avoid
+/// having to check for error everywhere).
char_u *ml_get(linenr_T lnum)
{
return ml_get_buf(curbuf, lnum, false);
}
-/*
- * Return pointer to position "pos".
- */
+/// @return pointer to position "pos".
char_u *ml_get_pos(const pos_T *pos)
FUNC_ATTR_NONNULL_ALL
{
return ml_get_buf(curbuf, pos->lnum, false) + pos->col;
}
-/// get codepoint at pos. pos must be either valid or have col set to MAXCOL!
+/// @return codepoint at pos. pos must be either valid or have col set to MAXCOL!
int gchar_pos(pos_T *pos)
FUNC_ATTR_NONNULL_ARG(1)
{
@@ -1856,9 +1828,9 @@ int gchar_pos(pos_T *pos)
return utf_ptr2char(ml_get_pos(pos));
}
-/// Return a pointer to a line in a specific buffer
-///
/// @param will_change true mark the buffer dirty (chars in the line will be changed)
+///
+/// @return a pointer to a line in a specific buffer
char_u *ml_get_buf(buf_T *buf, linenr_T lnum, bool will_change)
FUNC_ATTR_NONNULL_ALL
{
@@ -1931,10 +1903,8 @@ errorret:
return buf->b_ml.ml_line_ptr;
}
-/*
- * Check if a line that was just obtained by a call to ml_get
- * is in allocated memory.
- */
+/// Check if a line that was just obtained by a call to ml_get
+/// is in allocated memory.
int ml_line_alloced(void)
{
return curbuf->b_ml.ml_flags & ML_LINE_DIRTY;
@@ -2491,17 +2461,18 @@ int ml_replace(linenr_T lnum, char_u *line, bool copy)
return ml_replace_buf(curbuf, lnum, line, copy);
}
-// Replace line "lnum", with buffering, in current buffer.
-//
-// If "copy" is true, make a copy of the line, otherwise the line has been
-// copied to allocated memory already.
-// If "copy" is false the "line" may be freed to add text properties!
-// Do not use it after calling ml_replace().
-//
-// Check: The caller of this function should probably also call
-// changed_lines(), unless update_screen(NOT_VALID) is used.
-//
-// return FAIL for failure, OK otherwise
+/// Replace line "lnum", with buffering, in current buffer.
+///
+/// @param copy if true, make a copy of the line, otherwise the line has been
+/// copied to allocated memory already.
+/// if false, the "line" may be freed to add text properties!
+///
+/// Do not use it after calling ml_replace().
+///
+/// Check: The caller of this function should probably also call
+/// changed_lines(), unless update_screen(NOT_VALID) is used.
+///
+/// @return FAIL for failure, OK otherwise
int ml_replace_buf(buf_T *buf, linenr_T lnum, char_u *line, bool copy)
{
if (line == NULL) { // just checking...
@@ -2544,7 +2515,8 @@ int ml_replace_buf(buf_T *buf, linenr_T lnum, char_u *line, bool copy)
/// deleted_lines() after this.
///
/// @param message Show "--No lines in buffer--" message.
-/// @return FAIL for failure, OK otherwise
+///
+/// @return FAIL for failure, OK otherwise
int ml_delete(linenr_T lnum, bool message)
{
ml_flush_line(curbuf);
@@ -2701,9 +2673,7 @@ static int ml_delete_int(buf_T *buf, linenr_T lnum, bool message)
return OK;
}
-/*
- * set the B_MARKED flag for line 'lnum'
- */
+/// set the B_MARKED flag for line 'lnum'
void ml_setmarked(linenr_T lnum)
{
bhdr_T *hp;
@@ -2730,9 +2700,7 @@ void ml_setmarked(linenr_T lnum)
curbuf->b_ml.ml_flags |= ML_LOCKED_DIRTY;
}
-/*
- * find the first line with its B_MARKED flag set
- */
+/// find the first line with its B_MARKED flag set
linenr_T ml_firstmarked(void)
{
bhdr_T *hp;
@@ -2773,9 +2741,7 @@ linenr_T ml_firstmarked(void)
return (linenr_T)0;
}
-/*
- * clear all DB_MARKED flags
- */
+/// clear all DB_MARKED flags
void ml_clearmarked(void)
{
bhdr_T *hp;
@@ -2824,9 +2790,7 @@ size_t ml_flush_deleted_bytes(buf_T *buf, size_t *codepoints, size_t *codeunits)
return ret;
}
-/*
- * flush ml_line if necessary
- */
+/// flush ml_line if necessary
static void ml_flush_line(buf_T *buf)
{
bhdr_T *hp;
@@ -2923,9 +2887,7 @@ static void ml_flush_line(buf_T *buf)
buf->b_ml.ml_line_offset = 0;
}
-/*
- * create a new, empty, data block
- */
+/// create a new, empty, data block
static bhdr_T *ml_new_data(memfile_T *mfp, bool negative, int page_count)
{
assert(page_count >= 0);
@@ -2939,9 +2901,7 @@ static bhdr_T *ml_new_data(memfile_T *mfp, bool negative, int page_count)
return hp;
}
-/*
- * create a new, empty, pointer block
- */
+/// create a new, empty, pointer block
static bhdr_T *ml_new_ptr(memfile_T *mfp)
{
bhdr_T *hp = mf_new(mfp, false, 1);
@@ -2953,21 +2913,19 @@ static bhdr_T *ml_new_ptr(memfile_T *mfp)
return hp;
}
-/*
- * lookup line 'lnum' in a memline
- *
- * action: if ML_DELETE or ML_INSERT the line count is updated while searching
- * if ML_FLUSH only flush a locked block
- * if ML_FIND just find the line
- *
- * If the block was found it is locked and put in ml_locked.
- * The stack is updated to lead to the locked block. The ip_high field in
- * the stack is updated to reflect the last line in the block AFTER the
- * insert or delete, also if the pointer block has not been updated yet. But
- * if ml_locked != NULL ml_locked_lineadd must be added to ip_high.
- *
- * return: NULL for failure, pointer to block header otherwise
- */
+/// lookup line 'lnum' in a memline
+///
+/// @param action: if ML_DELETE or ML_INSERT the line count is updated while searching
+/// if ML_FLUSH only flush a locked block
+/// if ML_FIND just find the line
+///
+/// If the block was found it is locked and put in ml_locked.
+/// The stack is updated to lead to the locked block. The ip_high field in
+/// the stack is updated to reflect the last line in the block AFTER the
+/// insert or delete, also if the pointer block has not been updated yet. But
+/// if ml_locked != NULL ml_locked_lineadd must be added to ip_high.
+///
+/// @return NULL for failure, pointer to block header otherwise
static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action)
{
DATA_BL *dp;
@@ -3148,11 +3106,9 @@ error_noblock:
return NULL;
}
-/*
- * add an entry to the info pointer stack
- *
- * return number of the new entry
- */
+/// add an entry to the info pointer stack
+///
+/// @return number of the new entry
static int ml_add_stack(buf_T *buf)
{
int top = buf->b_ml.ml_stack_top;
@@ -3170,16 +3126,14 @@ static int ml_add_stack(buf_T *buf)
return top;
}
-/*
- * Update the pointer blocks on the stack for inserted/deleted lines.
- * The stack itself is also updated.
- *
- * When an insert/delete line action fails, the line is not inserted/deleted,
- * but the pointer blocks have already been updated. That is fixed here by
- * walking through the stack.
- *
- * Count is the number of lines added, negative if lines have been deleted.
- */
+/// Update the pointer blocks on the stack for inserted/deleted lines.
+/// The stack itself is also updated.
+///
+/// When an insert/delete line action fails, the line is not inserted/deleted,
+/// but the pointer blocks have already been updated. That is fixed here by
+/// walking through the stack.
+///
+/// Count is the number of lines added, negative if lines have been deleted.
static void ml_lineadd(buf_T *buf, int count)
{
int idx;
@@ -3206,13 +3160,13 @@ static void ml_lineadd(buf_T *buf, int count)
}
#if defined(HAVE_READLINK)
-/*
- * Resolve a symlink in the last component of a file name.
- * Note that f_resolve() does it for every part of the path, we don't do that
- * here.
- * If it worked returns OK and the resolved link in "buf[MAXPATHL]".
- * Otherwise returns FAIL.
- */
+
+/// Resolve a symlink in the last component of a file name.
+/// Note that f_resolve() does it for every part of the path, we don't do that
+/// here.
+///
+/// @return OK if it worked and the resolved link in "buf[MAXPATHL]",
+/// FAIL otherwise
int resolve_symlink(const char_u *fname, char_u *buf)
{
char_u tmp[MAXPATHL];
@@ -3276,10 +3230,9 @@ int resolve_symlink(const char_u *fname, char_u *buf)
}
#endif
-/*
- * Make swap file name out of the file name and a directory name.
- * Returns pointer to allocated memory or NULL.
- */
+/// Make swap file name out of the file name and a directory name.
+///
+/// @return pointer to allocated memory or NULL.
char_u *makeswapname(char_u *fname, char_u *ffname, buf_T *buf, char_u *dir_name)
{
char_u *r, *s;
@@ -3409,17 +3362,16 @@ static void attention_message(buf_T *buf, char_u *fname)
}
-/*
- * Trigger the SwapExists autocommands.
- * Returns a value for equivalent to do_dialog() (see below):
- * 0: still need to ask for a choice
- * 1: open read-only
- * 2: edit anyway
- * 3: recover
- * 4: delete it
- * 5: quit
- * 6: abort
- */
+/// Trigger the SwapExists autocommands.
+///
+/// @return a value for equivalent to do_dialog() (see below):
+/// 0: still need to ask for a choice
+/// 1: open read-only
+/// 2: edit anyway
+/// 3: recover
+/// 4: delete it
+/// 5: quit
+/// 6: abort
static int do_swapexists(buf_T *buf, char_u *fname)
{
set_vim_var_string(VV_SWAPNAME, (char *)fname, -1);
@@ -3813,10 +3765,8 @@ static bool fnamecmp_ino(char_u *fname_c, char_u *fname_s, long ino_block0)
return true;
}
-/*
- * Move a long integer into a four byte character array.
- * Used for machine independency in block zero.
- */
+/// Move a long integer into a four byte character array.
+/// Used for machine independency in block zero.
static void long_to_char(long n, char_u *s)
{
s[0] = (char_u)(n & 0xff);
@@ -3843,12 +3793,10 @@ static long char_to_long(char_u *s)
return retval;
}
-/*
- * Set the flags in the first block of the swap file:
- * - file is modified or not: buf->b_changed
- * - 'fileformat'
- * - 'fileencoding'
- */
+/// Set the flags in the first block of the swap file:
+/// - file is modified or not: buf->b_changed
+/// - 'fileformat'
+/// - 'fileencoding'
void ml_setflags(buf_T *buf)
{
bhdr_T *hp;
@@ -3874,13 +3822,13 @@ void ml_setflags(buf_T *buf)
#define MLCS_MAXL 800 // max no of lines in chunk
#define MLCS_MINL 400 // should be half of MLCS_MAXL
-/*
- * Keep information for finding byte offset of a line, updtype may be one of:
- * ML_CHNK_ADDLINE: Add len to parent chunk, possibly splitting it
- * Careful: ML_CHNK_ADDLINE may cause ml_find_line() to be called.
- * ML_CHNK_DELLINE: Subtract len from parent chunk, possibly deleting it
- * ML_CHNK_UPDLINE: Add len to parent chunk, as a signed entity.
- */
+/// Keep information for finding byte offset of a line
+///
+/// @param updtype may be one of:
+/// ML_CHNK_ADDLINE: Add len to parent chunk, possibly splitting it
+/// Careful: ML_CHNK_ADDLINE may cause ml_find_line() to be called.
+/// ML_CHNK_DELLINE: Subtract len from parent chunk, possibly deleting it
+/// ML_CHNK_UPDLINE: Add len to parent chunk, as a signed entity.
static void ml_updatechunk(buf_T *buf, linenr_T line, long len, int updtype)
{
static buf_T *ml_upd_lastbuf = NULL;
@@ -4083,7 +4031,7 @@ static void ml_updatechunk(buf_T *buf, linenr_T line, long len, int updtype)
/// Should be NULL when getting offset of line
/// @param no_ff ignore 'fileformat' option, always use one byte for NL.
///
-/// @return -1 if information is not available
+/// @return -1 if information is not available
long ml_find_line_or_offset(buf_T *buf, linenr_T lnum, long *offp, bool no_ff)
{
linenr_T curline;
@@ -4258,10 +4206,11 @@ void goto_byte(long cnt)
}
/// Increment the line pointer "lp" crossing line boundaries as necessary.
-/// Return 1 when going to the next line.
-/// Return 2 when moving forward onto a NUL at the end of the line).
-/// Return -1 when at the end of file.
-/// Return 0 otherwise.
+///
+/// @return 1 when going to the next line.
+/// 2 when moving forward onto a NUL at the end of the line).
+/// -1 when at the end of file.
+/// 0 otherwise.
int inc(pos_T *lp)
{
// when searching position may be set to end of a line
diff --git a/src/nvim/move.c b/src/nvim/move.c
index 27cc2b341c..751e0046bc 100644
--- a/src/nvim/move.c
+++ b/src/nvim/move.c
@@ -95,11 +95,6 @@ static void comp_botline(win_T *wp)
win_check_anchored_floats(wp);
}
-void reset_cursorline(void)
-{
- curwin->w_last_cursorline = 0;
-}
-
// Redraw when w_cline_row changes and 'relativenumber' or 'cursorline' is set.
void redraw_for_cursorline(win_T *wp)
FUNC_ATTR_NONNULL_ALL
@@ -107,21 +102,8 @@ void redraw_for_cursorline(win_T *wp)
if ((wp->w_p_rnu || win_cursorline_standout(wp))
&& (wp->w_valid & VALID_CROW) == 0
&& !pum_visible()) {
- if (wp->w_p_rnu) {
- // win_line() will redraw the number column only.
- redraw_later(wp, VALID);
- }
- if (win_cursorline_standout(wp)) {
- if (wp->w_redr_type <= VALID && wp->w_last_cursorline != 0) {
- // "w_last_cursorline" may be outdated, worst case we redraw
- // too much. This is optimized for moving the cursor around in
- // the current window.
- redrawWinline(wp, wp->w_last_cursorline);
- redrawWinline(wp, wp->w_cursor.lnum);
- } else {
- redraw_later(wp, SOME_VALID);
- }
- }
+ // win_line() will redraw the number column and cursorline only.
+ redraw_later(wp, VALID);
}
}
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index f402865d2d..e773351d63 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -1296,13 +1296,7 @@ static void normal_redraw(NormalState *s)
}
// Might need to update for 'cursorline'.
- // 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);
- }
+ check_redraw_cursorline();
if (VIsual_active) {
update_curbuf(INVERTED); // update inverted part
diff --git a/src/nvim/option.c b/src/nvim/option.c
index ffd009be89..9fd0e0b222 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -3962,11 +3962,8 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va
} else if ((int *)varp == &p_lnr) {
// 'langnoremap' -> !'langremap'
p_lrm = !p_lnr;
- } else if ((int *)varp == &curwin->w_p_cul && !value && old_value) {
- // 'cursorline'
- reset_cursorline();
- // 'undofile'
} else if ((int *)varp == &curbuf->b_p_udf || (int *)varp == &p_udf) {
+ // 'undofile'
// Only take action when the option was set. When reset we do not
// delete the undo file, the option may be set again without making
// any changes in between.
diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c
index 54cfaee80a..c6c4990334 100644
--- a/src/nvim/os/input.c
+++ b/src/nvim/os/input.c
@@ -522,6 +522,7 @@ static bool input_ready(MultiQueue *events)
// Exit because of an input read error.
static void read_error_exit(void)
+ FUNC_ATTR_NORETURN
{
if (silent_mode) { // Normal way to exit for "nvim -es".
getout(0);
diff --git a/src/nvim/os/signal.c b/src/nvim/os/signal.c
index a8bf68a1a2..327ab6bc48 100644
--- a/src/nvim/os/signal.c
+++ b/src/nvim/os/signal.c
@@ -152,6 +152,7 @@ static char *signal_name(int signum)
// NOTE: Avoid unsafe functions, such as allocating memory, they can result in
// a deadlock.
static void deadly_signal(int signum)
+ FUNC_ATTR_NORETURN
{
// Set the v:dying variable.
set_vim_var_nr(VV_DYING, 1);
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index d868fe8284..9ee7b1cc8a 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -2792,8 +2792,8 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, int
return NOTDONE;
}
- if (old_qf_curlist != qi->qf_curlist
- || old_changetick != qfl->qf_changedtick
+ if (old_qf_curlist != qi->qf_curlist // -V560
+ || old_changetick != qfl->qf_changedtick // -V560
|| !is_qf_entry_present(qfl, qf_ptr)) {
if (qfl_type == QFLT_QUICKFIX) {
emsg(_(e_current_quickfix_list_was_changed));
@@ -2894,7 +2894,7 @@ static int qf_jump_open_window(qf_info_T *qi, qfline_T *qf_ptr, bool newwin, int
}
}
if (old_qf_curlist != qi->qf_curlist
- || old_changetick != qfl->qf_changedtick
+ || old_changetick != qfl->qf_changedtick // -V560
|| !is_qf_entry_present(qfl, qf_ptr)) {
if (qfl_type == QFLT_QUICKFIX) {
emsg(_(e_current_quickfix_list_was_changed));
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index 83d555e584..5a10543559 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -95,6 +95,7 @@
#include "nvim/lua/executor.h"
#include "nvim/main.h"
#include "nvim/mark.h"
+#include "nvim/match.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
@@ -1322,6 +1323,8 @@ static void win_update(win_T *wp, DecorProviders *providers)
DecorProviders line_providers;
decor_providers_invoke_win(wp, providers, &line_providers, &provider_err);
+ bool cursorline_standout = win_cursorline_standout(wp);
+
for (;;) {
/* stop updating when reached the end of the window (check for _past_
* the end of the window is at the end of the loop) */
@@ -1366,8 +1369,8 @@ static void win_update(win_T *wp, DecorProviders *providers)
// if lines were inserted or deleted
|| (wp->w_match_head != NULL
&& buf->b_mod_xlines != 0)))))
- || (wp->w_p_cul && (lnum == wp->w_cursor.lnum
- || lnum == wp->w_last_cursorline))) {
+ || (cursorline_standout && lnum == wp->w_cursor.lnum)
+ || lnum == wp->w_last_cursorline) {
if (lnum == mod_top) {
top_to_mod = false;
}
@@ -1544,17 +1547,17 @@ static void win_update(win_T *wp, DecorProviders *providers)
foldinfo.fi_lines ? srow : wp->w_grid.Rows,
mod_top == 0, false, foldinfo, &line_providers);
- wp->w_lines[idx].wl_folded = foldinfo.fi_lines != 0;
- wp->w_lines[idx].wl_lastlnum = lnum;
- did_update = DID_LINE;
-
- if (foldinfo.fi_lines > 0) {
- did_update = DID_FOLD;
+ if (foldinfo.fi_lines == 0) {
+ wp->w_lines[idx].wl_folded = false;
+ wp->w_lines[idx].wl_lastlnum = lnum;
+ did_update = DID_LINE;
+ syntax_last_parsed = lnum;
+ } else {
foldinfo.fi_lines--;
+ wp->w_lines[idx].wl_folded = true;
wp->w_lines[idx].wl_lastlnum = lnum + foldinfo.fi_lines;
+ did_update = DID_FOLD;
}
-
- syntax_last_parsed = lnum;
}
wp->w_lines[idx].wl_lnum = lnum;
@@ -1600,6 +1603,9 @@ static void win_update(win_T *wp, DecorProviders *providers)
* End of loop over all window lines.
*/
+ // Now that the window has been redrawn with the old and new cursor line,
+ // update w_last_cursorline.
+ wp->w_last_cursorline = cursorline_standout ? wp->w_cursor.lnum : 0;
if (idx > wp->w_lines_valid) {
wp->w_lines_valid = idx;
@@ -2159,7 +2165,8 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc
// To speed up the loop below, set extra_check when there is linebreak,
// trailing white space and/or syntax processing to be done.
extra_check = wp->w_p_lbr;
- if (syntax_present(wp) && !wp->w_s->b_syn_error && !wp->w_s->b_syn_slow) {
+ if (syntax_present(wp) && !wp->w_s->b_syn_error && !wp->w_s->b_syn_slow
+ && !has_fold && !end_fill) {
// Prepare for syntax highlighting in this line. When there is an
// error, stop syntax highlighting.
save_did_emsg = did_emsg;
@@ -2378,8 +2385,6 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc
}
area_highlighting = true;
}
- // Update w_last_cursorline even if Visual mode is active.
- wp->w_last_cursorline = wp->w_cursor.lnum;
}
memset(sattrs, 0, sizeof(sattrs));
@@ -7613,3 +7618,15 @@ 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/sign.c b/src/nvim/sign.c
index 50400852b8..a50b4a5a99 100644
--- a/src/nvim/sign.c
+++ b/src/nvim/sign.c
@@ -119,7 +119,7 @@ static void sign_group_unref(char_u *groupname)
/// @return true if 'sign' is in 'group'.
/// A sign can either be in the global group (sign->group == NULL)
/// or in a named group. If 'group' is '*', then the sign is part of the group.
-bool sign_in_group(sign_entry_T *sign, const char_u *group)
+static bool sign_in_group(sign_entry_T *sign, const char_u *group)
{
return ((group != NULL && STRCMP(group, "*") == 0)
|| (group == NULL && sign->se_group == NULL)
@@ -128,7 +128,7 @@ bool sign_in_group(sign_entry_T *sign, const char_u *group)
}
/// Get the next free sign identifier in the specified group
-int sign_group_get_next_signid(buf_T *buf, const char_u *groupname)
+static int sign_group_get_next_signid(buf_T *buf, const char_u *groupname)
{
int id = 1;
signgroup_T *group = NULL;
@@ -246,8 +246,21 @@ static void insert_sign_by_lnum_prio(buf_T *buf, sign_entry_T *prev, int id, con
insert_sign(buf, prev, sign, id, group, prio, lnum, typenr, has_text_or_icon);
}
+/// Lookup a sign by typenr. Returns NULL if sign is not found.
+static sign_T *find_sign_by_typenr(int typenr)
+{
+ sign_T *sp;
+
+ for (sp = first_sign; sp != NULL; sp = sp->sn_next) {
+ if (sp->sn_typenr == typenr) {
+ return sp;
+ }
+ }
+ return NULL;
+}
+
/// Get the name of a sign by its typenr.
-char_u *sign_typenr2name(int typenr)
+static char_u *sign_typenr2name(int typenr)
{
sign_T *sp;
@@ -260,7 +273,7 @@ char_u *sign_typenr2name(int typenr)
}
/// Return information about a sign in a Dict
-dict_T *sign_get_info(sign_entry_T *sign)
+static dict_T *sign_get_info(sign_entry_T *sign)
{
dict_T *d = tv_dict_alloc();
tv_dict_add_nr(d, S_LEN("id"), sign->se_id);
@@ -358,8 +371,8 @@ static void sign_sort_by_prio_on_line(buf_T *buf, sign_entry_T *sign)
/// @param lnum line number which gets the mark
/// @param typenr typenr of sign we are adding
/// @param has_text_or_icon sign has text or icon
-void buf_addsign(buf_T *buf, int id, const char_u *groupname, int prio, linenr_T lnum, int typenr,
- bool has_text_or_icon)
+static void buf_addsign(buf_T *buf, int id, const char_u *groupname, int prio, linenr_T lnum,
+ int typenr, bool has_text_or_icon)
{
sign_entry_T *sign; // a sign in the signlist
sign_entry_T *prev; // the previous sign
@@ -405,7 +418,8 @@ void buf_addsign(buf_T *buf, int id, const char_u *groupname, int prio, linenr_T
/// @param group sign group
/// @param typenr typenr of sign we are adding
/// @param prio sign priority
-linenr_T buf_change_sign_type(buf_T *buf, int markId, const char_u *group, int typenr, int prio)
+static linenr_T buf_change_sign_type(buf_T *buf, int markId, const char_u *group, int typenr,
+ int prio)
{
sign_entry_T *sign; // a sign in the signlist
@@ -456,19 +470,6 @@ sign_attrs_T *sign_get_attr(SignType type, sign_attrs_T sattrs[], int idx, int m
return NULL;
}
-/// Lookup a sign by typenr. Returns NULL if sign is not found.
-static sign_T *find_sign_by_typenr(int typenr)
-{
- sign_T *sp;
-
- for (sp = first_sign; sp != NULL; sp = sp->sn_next) {
- if (sp->sn_typenr == typenr) {
- return sp;
- }
- }
- return NULL;
-}
-
/// Return the attributes of all the signs placed on line 'lnum' in buffer
/// 'buf'. Used when refreshing the screen. Returns the number of signs.
/// @param buf Buffer in which to search
@@ -536,7 +537,7 @@ int buf_get_signattrs(buf_T *buf, linenr_T lnum, sign_attrs_T sattrs[])
///
/// @return the line number of the deleted sign. If multiple signs are deleted,
/// then returns the line number of the last sign deleted.
-linenr_T buf_delsign(buf_T *buf, linenr_T atlnum, int id, char_u *group)
+static linenr_T buf_delsign(buf_T *buf, linenr_T atlnum, int id, char_u *group)
{
sign_entry_T **lastp; // pointer to pointer to current sign
sign_entry_T *sign; // a sign in a b_signlist
@@ -593,7 +594,7 @@ linenr_T buf_delsign(buf_T *buf, linenr_T atlnum, int id, char_u *group)
/// @param buf buffer to store sign in
/// @param id sign ID
/// @param group sign group
-int buf_findsign(buf_T *buf, int id, char_u *group)
+static int buf_findsign(buf_T *buf, int id, char_u *group)
{
sign_entry_T *sign; // a sign in the signlist
@@ -636,7 +637,7 @@ static sign_entry_T *buf_getsign_at_line(buf_T *buf, linenr_T lnum, char_u *grou
/// @param buf buffer whose sign we are searching for
/// @param lnum line number of sign
/// @param groupname sign group name
-int buf_findsign_id(buf_T *buf, linenr_T lnum, char_u *groupname)
+static int buf_findsign_id(buf_T *buf, linenr_T lnum, char_u *groupname)
{
sign_entry_T *sign; // a sign in the signlist
@@ -681,7 +682,7 @@ void buf_delete_signs(buf_T *buf, char_u *group)
}
/// List placed signs for "rbuf". If "rbuf" is NULL do it for all buffers.
-void sign_list_placed(buf_T *rbuf, char_u *sign_group)
+static void sign_list_placed(buf_T *rbuf, char_u *sign_group)
{
buf_T *buf;
sign_entry_T *sign;
@@ -913,8 +914,8 @@ static int sign_define_init_text(sign_T *sp, char_u *text)
}
/// Define a new sign or update an existing sign
-int sign_define_by_name(char_u *name, char_u *icon, char_u *linehl, char_u *text, char_u *texthl,
- char_u *culhl, char *numhl)
+static int sign_define_by_name(char_u *name, char_u *icon, char_u *linehl, char_u *text,
+ char_u *texthl, char_u *culhl, char *numhl)
{
sign_T *sp_prev;
sign_T *sp;
@@ -987,7 +988,7 @@ int sign_define_by_name(char_u *name, char_u *icon, char_u *linehl, char_u *text
}
/// Free the sign specified by 'name'.
-int sign_undefine_by_name(const char_u *name)
+static int sign_undefine_by_name(const char_u *name)
{
sign_T *sp_prev;
sign_T *sp;
@@ -1002,17 +1003,6 @@ int sign_undefine_by_name(const char_u *name)
return OK;
}
-static void may_force_numberwidth_recompute(buf_T *buf, int unplace)
-{
- FOR_ALL_TAB_WINDOWS(tp, wp)
- if (wp->w_buffer == buf
- && (wp->w_p_nu || wp->w_p_rnu)
- && (unplace || wp->w_nrwidth_width < 2)
- && (*wp->w_p_scl == 'n' && *(wp->w_p_scl + 1) == 'u')) {
- wp->w_nrwidth_line_count = 0;
- }
-}
-
/// List the signs matching 'name'
static void sign_list_by_name(char_u *name)
{
@@ -1026,10 +1016,20 @@ static void sign_list_by_name(char_u *name)
}
}
+static void may_force_numberwidth_recompute(buf_T *buf, int unplace)
+{
+ FOR_ALL_TAB_WINDOWS(tp, wp)
+ if (wp->w_buffer == buf
+ && (wp->w_p_nu || wp->w_p_rnu)
+ && (unplace || wp->w_nrwidth_width < 2)
+ && (*wp->w_p_scl == 'n' && *(wp->w_p_scl + 1) == 'u')) {
+ wp->w_nrwidth_line_count = 0;
+ }
+}
/// Place a sign at the specified file location or update a sign.
-int sign_place(int *sign_id, const char_u *sign_group, const char_u *sign_name, buf_T *buf,
- linenr_T lnum, int prio)
+static int sign_place(int *sign_id, const char_u *sign_group, const char_u *sign_name, buf_T *buf,
+ linenr_T lnum, int prio)
{
sign_T *sp;
@@ -1081,7 +1081,7 @@ int sign_place(int *sign_id, const char_u *sign_group, const char_u *sign_name,
}
/// Unplace the specified sign
-int sign_unplace(int sign_id, char_u *sign_group, buf_T *buf, linenr_T atlnum)
+static int sign_unplace(int sign_id, char_u *sign_group, buf_T *buf, linenr_T atlnum)
{
if (buf->b_signlist == NULL) { // No signs in the buffer
return OK;
@@ -1125,7 +1125,7 @@ static void sign_unplace_at_cursor(char_u *groupname)
}
/// Jump to a sign.
-linenr_T sign_jump(int sign_id, char_u *sign_group, buf_T *buf)
+static linenr_T sign_jump(int sign_id, char_u *sign_group, buf_T *buf)
{
linenr_T lnum;
@@ -1537,7 +1537,7 @@ static void sign_getinfo(sign_T *sp, dict_T *retdict)
/// If 'name' is NULL, return a list of all the defined signs.
/// Otherwise, return information about the specified sign.
-void sign_getlist(const char_u *name, list_T *retlist)
+static void sign_getlist(const char_u *name, list_T *retlist)
{
sign_T *sp = first_sign;
dict_T *dict;
@@ -1607,8 +1607,8 @@ static void sign_get_placed_in_buf(buf_T *buf, linenr_T lnum, int sign_id, const
/// Get a list of signs placed in buffer 'buf'. If 'num' is non-zero, return the
/// sign placed at the line number. If 'lnum' is zero, return all the signs
/// placed in 'buf'. If 'buf' is NULL, return signs placed in all the buffers.
-void sign_get_placed(buf_T *buf, linenr_T lnum, int sign_id, const char_u *sign_group,
- list_T *retlist)
+static void sign_get_placed(buf_T *buf, linenr_T lnum, int sign_id, const char_u *sign_group,
+ list_T *retlist)
{
if (buf != NULL) {
sign_get_placed_in_buf(buf, lnum, sign_id, sign_group, retlist);
@@ -1896,7 +1896,7 @@ void set_context_in_sign_cmd(expand_T *xp, char_u *arg)
/// Define a sign using the attributes in 'dict'. Returns 0 on success and -1 on
/// failure.
-int sign_define_from_dict(const char *name_arg, dict_T *dict)
+static int sign_define_from_dict(const char *name_arg, dict_T *dict)
{
char *name = NULL;
char *icon = NULL;
@@ -1947,7 +1947,7 @@ cleanup:
/// Define multiple signs using attributes from list 'l' and store the return
/// values in 'retlist'.
-void sign_define_multiple(list_T *l, list_T *retlist)
+static void sign_define_multiple(list_T *l, list_T *retlist)
{
int retval;
@@ -1962,10 +1962,156 @@ void sign_define_multiple(list_T *l, list_T *retlist)
});
}
+/// "sign_define()" function
+void f_sign_define(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *name;
+
+ if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_UNKNOWN) {
+ // Define multiple signs
+ tv_list_alloc_ret(rettv, kListLenMayKnow);
+
+ sign_define_multiple(argvars[0].vval.v_list, rettv->vval.v_list);
+ return;
+ }
+
+ // Define a single sign
+ rettv->vval.v_number = -1;
+
+ name = tv_get_string_chk(&argvars[0]);
+ if (name == NULL) {
+ return;
+ }
+
+ if (argvars[1].v_type != VAR_UNKNOWN && argvars[1].v_type != VAR_DICT) {
+ emsg(_(e_dictreq));
+ return;
+ }
+
+ rettv->vval.v_number = sign_define_from_dict(name,
+ argvars[1].v_type ==
+ VAR_DICT ? argvars[1].vval.v_dict : NULL);
+}
+
+/// "sign_getdefined()" function
+void f_sign_getdefined(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *name = NULL;
+
+ tv_list_alloc_ret(rettv, 0);
+
+ if (argvars[0].v_type != VAR_UNKNOWN) {
+ name = tv_get_string(&argvars[0]);
+ }
+
+ sign_getlist((const char_u *)name, rettv->vval.v_list);
+}
+
+/// "sign_getplaced()" function
+void f_sign_getplaced(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ buf_T *buf = NULL;
+ dict_T *dict;
+ dictitem_T *di;
+ linenr_T lnum = 0;
+ int sign_id = 0;
+ const char *group = NULL;
+ bool notanum = false;
+
+ tv_list_alloc_ret(rettv, 0);
+
+ if (argvars[0].v_type != VAR_UNKNOWN) {
+ // get signs placed in the specified buffer
+ buf = get_buf_arg(&argvars[0]);
+ if (buf == NULL) {
+ return;
+ }
+
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ if (argvars[1].v_type != VAR_DICT
+ || ((dict = argvars[1].vval.v_dict) == NULL)) {
+ emsg(_(e_dictreq));
+ return;
+ }
+ if ((di = tv_dict_find(dict, "lnum", -1)) != NULL) {
+ // get signs placed at this line
+ lnum = (linenr_T)tv_get_number_chk(&di->di_tv, &notanum);
+ if (notanum) {
+ return;
+ }
+ (void)lnum;
+ lnum = tv_get_lnum(&di->di_tv);
+ }
+ if ((di = tv_dict_find(dict, "id", -1)) != NULL) {
+ // get sign placed with this identifier
+ sign_id = (int)tv_get_number_chk(&di->di_tv, &notanum);
+ if (notanum) {
+ return;
+ }
+ }
+ if ((di = tv_dict_find(dict, "group", -1)) != NULL) {
+ group = tv_get_string_chk(&di->di_tv);
+ if (group == NULL) {
+ return;
+ }
+ if (*group == '\0') { // empty string means global group
+ group = NULL;
+ }
+ }
+ }
+ }
+
+ sign_get_placed(buf, lnum, sign_id, (const char_u *)group,
+ rettv->vval.v_list);
+}
+
+/// "sign_jump()" function
+void f_sign_jump(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int sign_id;
+ char *sign_group = NULL;
+ buf_T *buf;
+ bool notanum = false;
+
+ rettv->vval.v_number = -1;
+
+ // Sign identifier
+ sign_id = (int)tv_get_number_chk(&argvars[0], &notanum);
+ if (notanum) {
+ return;
+ }
+ if (sign_id <= 0) {
+ emsg(_(e_invarg));
+ return;
+ }
+
+ // Sign group
+ const char *sign_group_chk = tv_get_string_chk(&argvars[1]);
+ if (sign_group_chk == NULL) {
+ return;
+ }
+ if (sign_group_chk[0] == '\0') {
+ sign_group = NULL; // global sign group
+ } else {
+ sign_group = xstrdup(sign_group_chk);
+ }
+
+ // Buffer to place the sign
+ buf = get_buf_arg(&argvars[2]);
+ if (buf == NULL) {
+ goto cleanup;
+ }
+
+ rettv->vval.v_number = sign_jump(sign_id, (char_u *)sign_group, buf);
+
+cleanup:
+ xfree(sign_group);
+}
+
/// Place a new sign using the values specified in dict 'dict'. Returns the sign
/// identifier if successfully placed, otherwise returns 0.
-int sign_place_from_dict(typval_T *id_tv, typval_T *group_tv, typval_T *name_tv, typval_T *buf_tv,
- dict_T *dict)
+static int sign_place_from_dict(typval_T *id_tv, typval_T *group_tv, typval_T *name_tv,
+ typval_T *buf_tv, dict_T *dict)
{
int sign_id = 0;
char_u *group = NULL;
@@ -2077,8 +2223,50 @@ cleanup:
return ret_sign_id;
}
+/// "sign_place()" function
+void f_sign_place(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ dict_T *dict = NULL;
+
+ rettv->vval.v_number = -1;
+
+ if (argvars[4].v_type != VAR_UNKNOWN
+ && (argvars[4].v_type != VAR_DICT
+ || ((dict = argvars[4].vval.v_dict) == NULL))) {
+ emsg(_(e_dictreq));
+ return;
+ }
+
+ rettv->vval.v_number = sign_place_from_dict(&argvars[0], &argvars[1], &argvars[2], &argvars[3],
+ dict);
+}
+
+/// "sign_placelist()" function. Place multiple signs.
+void f_sign_placelist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int sign_id;
+
+ tv_list_alloc_ret(rettv, kListLenMayKnow);
+
+ if (argvars[0].v_type != VAR_LIST) {
+ emsg(_(e_listreq));
+ return;
+ }
+
+ // Process the List of sign attributes
+ TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, {
+ sign_id = -1;
+ if (TV_LIST_ITEM_TV(li)->v_type == VAR_DICT) {
+ sign_id = sign_place_from_dict(NULL, NULL, NULL, NULL, TV_LIST_ITEM_TV(li)->vval.v_dict);
+ } else {
+ emsg(_(e_dictreq));
+ }
+ tv_list_append_number(rettv->vval.v_list, sign_id);
+ });
+}
+
/// Undefine multiple signs
-void sign_undefine_multiple(list_T *l, list_T *retlist)
+static void sign_undefine_multiple(list_T *l, list_T *retlist)
{
char_u *name;
int retval;
@@ -2093,9 +2281,41 @@ void sign_undefine_multiple(list_T *l, list_T *retlist)
});
}
+/// "sign_undefine()" function
+void f_sign_undefine(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *name;
+
+ if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_UNKNOWN) {
+ // Undefine multiple signs
+ tv_list_alloc_ret(rettv, kListLenMayKnow);
+
+ sign_undefine_multiple(argvars[0].vval.v_list, rettv->vval.v_list);
+ return;
+ }
+
+ rettv->vval.v_number = -1;
+
+ if (argvars[0].v_type == VAR_UNKNOWN) {
+ // Free all the signs
+ free_signs();
+ rettv->vval.v_number = 0;
+ } else {
+ // Free only the specified sign
+ name = tv_get_string_chk(&argvars[0]);
+ if (name == NULL) {
+ return;
+ }
+
+ if (sign_undefine_by_name((const char_u *)name) == OK) {
+ rettv->vval.v_number = 0;
+ }
+ }
+}
+
/// Unplace the sign with attributes specified in 'dict'. Returns 0 on success
/// and -1 on failure.
-int sign_unplace_from_dict(typval_T *group_tv, dict_T *dict)
+static int sign_unplace_from_dict(typval_T *group_tv, dict_T *dict)
{
dictitem_T *di;
int sign_id = 0;
@@ -2151,3 +2371,48 @@ cleanup:
return retval;
}
+/// "sign_unplace()" function
+void f_sign_unplace(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ dict_T *dict = NULL;
+
+ rettv->vval.v_number = -1;
+
+ if (argvars[0].v_type != VAR_STRING) {
+ emsg(_(e_invarg));
+ return;
+ }
+
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ if (argvars[1].v_type != VAR_DICT) {
+ emsg(_(e_dictreq));
+ return;
+ }
+ dict = argvars[1].vval.v_dict;
+ }
+
+ rettv->vval.v_number = sign_unplace_from_dict(&argvars[0], dict);
+}
+
+/// "sign_unplacelist()" function
+void f_sign_unplacelist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int retval;
+
+ tv_list_alloc_ret(rettv, kListLenMayKnow);
+
+ if (argvars[0].v_type != VAR_LIST) {
+ emsg(_(e_listreq));
+ return;
+ }
+
+ TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, {
+ retval = -1;
+ if (TV_LIST_ITEM_TV(li)->v_type == VAR_DICT) {
+ retval = sign_unplace_from_dict(NULL, TV_LIST_ITEM_TV(li)->vval.v_dict);
+ } else {
+ emsg(_(e_dictreq));
+ }
+ tv_list_append_number(rettv->vval.v_list, retval);
+ });
+}
diff --git a/src/nvim/sign.h b/src/nvim/sign.h
index 9044c2d0bb..6e75a4e62b 100644
--- a/src/nvim/sign.h
+++ b/src/nvim/sign.h
@@ -4,6 +4,7 @@
#include <stdbool.h>
#include "nvim/buffer_defs.h"
+#include "nvim/eval/funcs.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/sign_defs.h"
diff --git a/src/nvim/testdir/test_cursorline.vim b/src/nvim/testdir/test_cursorline.vim
index 39d8b901ed..bf049ec779 100644
--- a/src/nvim/testdir/test_cursorline.vim
+++ b/src/nvim/testdir/test_cursorline.vim
@@ -268,4 +268,30 @@ END
call delete('Xtextfile')
endfunc
+func Test_cursorline_callback()
+ CheckScreendump
+ CheckFeature timers
+
+ let lines =<< trim END
+ call setline(1, ['aaaaa', 'bbbbb', 'ccccc', 'ddddd'])
+ set cursorline
+ call cursor(4, 1)
+
+ func Func(timer)
+ call cursor(2, 1)
+ endfunc
+
+ call timer_start(300, 'Func')
+ END
+ call writefile(lines, 'Xcul_timer')
+
+ let buf = RunVimInTerminal('-S Xcul_timer', #{rows: 8})
+ call TermWait(buf, 310)
+ call VerifyScreenDump(buf, 'Test_cursorline_callback_1', {})
+
+ call StopVimInTerminal(buf)
+ call delete('Xcul_timer')
+endfunc
+
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_eval_stuff.vim b/src/nvim/testdir/test_eval_stuff.vim
index 95eccde35c..12febfeb93 100644
--- a/src/nvim/testdir/test_eval_stuff.vim
+++ b/src/nvim/testdir/test_eval_stuff.vim
@@ -185,7 +185,7 @@ func Test_let_register()
call Assert_reg('"', 'v', "abc", "['abc']", "abc", "['abc']")
let @" = "abc\n"
call Assert_reg('"', 'V', "abc\n", "['abc']", "abc\n", "['abc']")
- let @" = "abc\<C-m>"
+ let @" = "abc\r"
call Assert_reg('"', 'V', "abc\r\n", "['abc\r']", "abc\r\n", "['abc\r']")
let @= = '"abc"'
call Assert_reg('=', 'v', "abc", "['abc']", '"abc"', "['\"abc\"']")
diff --git a/src/nvim/testdir/test_regex_char_classes.vim b/src/nvim/testdir/test_regex_char_classes.vim
index c1a4202c2b..b0d76a15e2 100644
--- a/src/nvim/testdir/test_regex_char_classes.vim
+++ b/src/nvim/testdir/test_regex_char_classes.vim
@@ -66,22 +66,22 @@ func Test_regex_char_classes()
let save_enc = &encoding
set encoding=utf-8
- let input = "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"
+ let input = "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"
" Format is [cmd_to_run, expected_output]
let tests = [
\ [':s/\%#=0\d//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=1\d//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=2\d//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=0[0-9]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=1[0-9]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=2[0-9]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=0\D//g',
\ "0123456789"],
\ [':s/\%#=1\D//g',
@@ -95,17 +95,17 @@ func Test_regex_char_classes()
\ [':s/\%#=2[^0-9]//g',
\ "0123456789"],
\ [':s/\%#=0\o//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=1\o//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=2\o//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=0[0-7]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=1[0-7]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=2[0-7]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=0\O//g',
\ "01234567"],
\ [':s/\%#=1\O//g',
@@ -119,17 +119,17 @@ func Test_regex_char_classes()
\ [':s/\%#=2[^0-7]//g',
\ "01234567"],
\ [':s/\%#=0\x//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=1\x//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=2\x//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=0[0-9A-Fa-f]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=1[0-9A-Fa-f]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=2[0-9A-Fa-f]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=0\X//g',
\ "0123456789ABCDEFabcdef"],
\ [':s/\%#=1\X//g',
@@ -143,17 +143,17 @@ func Test_regex_char_classes()
\ [':s/\%#=2[^0-9A-Fa-f]//g',
\ "0123456789ABCDEFabcdef"],
\ [':s/\%#=0\w//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=1\w//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=2\w//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=0[0-9A-Za-z_]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=1[0-9A-Za-z_]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=2[0-9A-Za-z_]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=0\W//g',
\ "0123456789ABCDEFGHIXYZ_abcdefghiwxyz"],
\ [':s/\%#=1\W//g',
@@ -167,17 +167,17 @@ func Test_regex_char_classes()
\ [':s/\%#=2[^0-9A-Za-z_]//g',
\ "0123456789ABCDEFGHIXYZ_abcdefghiwxyz"],
\ [':s/\%#=0\h//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=1\h//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=2\h//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=0[A-Za-z_]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=1[A-Za-z_]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=2[A-Za-z_]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=0\H//g',
\ "ABCDEFGHIXYZ_abcdefghiwxyz"],
\ [':s/\%#=1\H//g',
@@ -191,17 +191,17 @@ func Test_regex_char_classes()
\ [':s/\%#=2[^A-Za-z_]//g',
\ "ABCDEFGHIXYZ_abcdefghiwxyz"],
\ [':s/\%#=0\a//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=1\a//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=2\a//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=0[A-Za-z]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=1[A-Za-z]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=2[A-Za-z]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=0\A//g',
\ "ABCDEFGHIXYZabcdefghiwxyz"],
\ [':s/\%#=1\A//g',
@@ -215,17 +215,17 @@ func Test_regex_char_classes()
\ [':s/\%#=2[^A-Za-z]//g',
\ "ABCDEFGHIXYZabcdefghiwxyz"],
\ [':s/\%#=0\l//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=1\l//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=2\l//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=0[a-z]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=1[a-z]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=2[a-z]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=0\L//g',
\ "abcdefghiwxyz"],
\ [':s/\%#=1\L//g',
@@ -239,17 +239,17 @@ func Test_regex_char_classes()
\ [':s/\%#=2[^a-z]//g',
\ "abcdefghiwxyz"],
\ [':s/\%#=0\u//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=1\u//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=2\u//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=0[A-Z]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=1[A-Z]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=2[A-Z]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=0\U//g',
\ "ABCDEFGHIXYZ"],
\ [':s/\%#=1\U//g',
@@ -269,11 +269,11 @@ func Test_regex_char_classes()
\ [':s/\%#=2\%' . line('.') . 'l^\t...//g',
\ "!\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=0[0-z]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=1[0-z]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=2[0-z]//g',
- \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
+ \ "\t\<C-L>\r !\"#$%&'()#+'-./{|}~\<C-?>\u0080\u0082\u0090\u009b¦±¼ÇÓé"],
\ [':s/\%#=0[^0-z]//g',
\ "0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz"],
\ [':s/\%#=1[^0-z]//g',
diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim
index dbb792d2b0..9710a7ab84 100644
--- a/src/nvim/testdir/test_substitute.vim
+++ b/src/nvim/testdir/test_substitute.vim
@@ -177,9 +177,9 @@ func Test_sub_cmd_1()
\ ['I', 's/I/\lII/', ['iI']],
\ ['J', 's/J/\LJ\EJ/', ['jJ']],
\ ['K', 's/K/\Uk\ek/', ['Kk']],
- \ ['lLl', "s/L/\<C-V>\<C-M>/", ["l\<C-V>", 'l']],
+ \ ['lLl', "s/L/\<C-V>\r/", ["l\<C-V>", 'l']],
\ ['mMm', 's/M/\r/', ['m', 'm']],
- \ ['nNn', "s/N/\\\<C-V>\<C-M>/", ["n\<C-V>", 'n']],
+ \ ['nNn', "s/N/\\\<C-V>\r/", ["n\<C-V>", 'n']],
\ ['oOo', 's/O/\n/', ["o\no"]],
\ ['pPp', 's/P/\b/', ["p\<C-H>p"]],
\ ['qQq', 's/Q/\t/', ["q\tq"]],
@@ -208,9 +208,9 @@ func Test_sub_cmd_2()
\ ['I', 's/I/\lII/', ['iI']],
\ ['J', 's/J/\LJ\EJ/', ['jJ']],
\ ['K', 's/K/\Uk\ek/', ['Kk']],
- \ ['lLl', "s/L/\<C-V>\<C-M>/", ["l\<C-V>", 'l']],
+ \ ['lLl', "s/L/\<C-V>\r/", ["l\<C-V>", 'l']],
\ ['mMm', 's/M/\r/', ['m', 'm']],
- \ ['nNn', "s/N/\\\<C-V>\<C-M>/", ["n\<C-V>", 'n']],
+ \ ['nNn', "s/N/\\\<C-V>\r/", ["n\<C-V>", 'n']],
\ ['oOo', 's/O/\n/', ["o\no"]],
\ ['pPp', 's/P/\b/', ["p\<C-H>p"]],
\ ['qQq', 's/Q/\t/', ["q\tq"]],
@@ -230,9 +230,9 @@ func Test_sub_cmd_3()
" List entry format: [input, cmd, output]
let tests = [['aAa', "s/A/\\='\\'/", ['a\a']],
\ ['bBb', "s/B/\\='\\\\'/", ['b\\b']],
- \ ['cCc', "s/C/\\='\<C-V>\<C-M>'/", ["c\<C-V>", 'c']],
- \ ['dDd', "s/D/\\='\\\<C-V>\<C-M>'/", ["d\\\<C-V>", 'd']],
- \ ['eEe', "s/E/\\='\\\\\<C-V>\<C-M>'/", ["e\\\\\<C-V>", 'e']],
+ \ ['cCc', "s/C/\\='\<C-V>\r'/", ["c\<C-V>", 'c']],
+ \ ['dDd', "s/D/\\='\\\<C-V>\r'/", ["d\\\<C-V>", 'd']],
+ \ ['eEe', "s/E/\\='\\\\\<C-V>\r'/", ["e\\\\\<C-V>", 'e']],
\ ['fFf', "s/F/\\='\r'/", ['f', 'f']],
\ ['gGg', "s/G/\\='\<C-V>\<C-J>'/", ["g\<C-V>", 'g']],
\ ['hHh', "s/H/\\='\\\<C-V>\<C-J>'/", ["h\\\<C-V>", 'h']],
@@ -254,11 +254,11 @@ func Test_sub_cmd_4()
\ ['a\a']],
\ ['bBb', "s/B/\\=substitute(submatch(0), '.', '\\', '')/",
\ ['b\b']],
- \ ['cCc', "s/C/\\=substitute(submatch(0), '.', '\<C-V>\<C-M>', '')/",
+ \ ['cCc', "s/C/\\=substitute(submatch(0), '.', '\<C-V>\r', '')/",
\ ["c\<C-V>", 'c']],
- \ ['dDd', "s/D/\\=substitute(submatch(0), '.', '\\\<C-V>\<C-M>', '')/",
+ \ ['dDd', "s/D/\\=substitute(submatch(0), '.', '\\\<C-V>\r', '')/",
\ ["d\<C-V>", 'd']],
- \ ['eEe', "s/E/\\=substitute(submatch(0), '.', '\\\\\<C-V>\<C-M>', '')/",
+ \ ['eEe', "s/E/\\=substitute(submatch(0), '.', '\\\\\<C-V>\r', '')/",
\ ["e\\\<C-V>", 'e']],
\ ['fFf', "s/F/\\=substitute(submatch(0), '.', '\\r', '')/",
\ ['f', 'f']],
@@ -316,7 +316,7 @@ func Test_sub_cmd_7()
set cpo&
" List entry format: [input, cmd, output]
- let tests = [ ["A\<C-V>\<C-M>A", 's/A./\=submatch(0)/', ['A', 'A']],
+ let tests = [ ["A\<C-V>\rA", 's/A./\=submatch(0)/', ['A', 'A']],
\ ["B\<C-V>\<C-J>B", 's/B./\=submatch(0)/', ['B', 'B']],
\ ["C\<C-V>\<C-J>C", 's/C./\=strtrans(string(submatch(0, 1)))/', [strtrans("['C\<C-J>']C")]],
\ ["D\<C-V>\<C-J>\nD", 's/D.\nD/\=strtrans(string(submatch(0, 1)))/', [strtrans("['D\<C-J>', 'D']")]],
@@ -467,11 +467,11 @@ func Test_sub_replace_1()
call assert_equal('iI', substitute('I', 'I', '\lII', ''))
call assert_equal('jJ', substitute('J', 'J', '\LJ\EJ', ''))
call assert_equal('Kk', substitute('K', 'K', '\Uk\ek', ''))
- call assert_equal("l\<C-V>\<C-M>l",
- \ substitute('lLl', 'L', "\<C-V>\<C-M>", ''))
- call assert_equal("m\<C-M>m", substitute('mMm', 'M', '\r', ''))
- call assert_equal("n\<C-V>\<C-M>n",
- \ substitute('nNn', 'N', "\\\<C-V>\<C-M>", ''))
+ call assert_equal("l\<C-V>\rl",
+ \ substitute('lLl', 'L', "\<C-V>\r", ''))
+ call assert_equal("m\rm", substitute('mMm', 'M', '\r', ''))
+ call assert_equal("n\<C-V>\rn",
+ \ substitute('nNn', 'N', "\\\<C-V>\r", ''))
call assert_equal("o\no", substitute('oOo', 'O', '\n', ''))
call assert_equal("p\<C-H>p", substitute('pPp', 'P', '\b', ''))
call assert_equal("q\tq", substitute('qQq', 'Q', '\t', ''))
@@ -480,7 +480,7 @@ func Test_sub_replace_1()
call assert_equal("u\nu", substitute('uUu', 'U', "\n", ''))
call assert_equal("v\<C-H>v", substitute('vVv', 'V', "\b", ''))
call assert_equal("w\\w", substitute('wWw', 'W', "\\", ''))
- call assert_equal("x\<C-M>x", substitute('xXx', 'X', "\r", ''))
+ call assert_equal("x\rx", substitute('xXx', 'X', "\r", ''))
call assert_equal("YyyY", substitute('Y', 'Y', '\L\uyYy\l\EY', ''))
call assert_equal("zZZz", substitute('Z', 'Z', '\U\lZzZ\u\Ez', ''))
endfunc
@@ -500,17 +500,17 @@ func Test_sub_replace_2()
call assert_equal('iI', substitute('I', 'I', '\lII', ''))
call assert_equal('jJ', substitute('J', 'J', '\LJ\EJ', ''))
call assert_equal('Kk', substitute('K', 'K', '\Uk\ek', ''))
- call assert_equal("l\<C-V>\<C-M>l",
- \ substitute('lLl', 'L', "\<C-V>\<C-M>", ''))
- call assert_equal("m\<C-M>m", substitute('mMm', 'M', '\r', ''))
- call assert_equal("n\<C-V>\<C-M>n",
- \ substitute('nNn', 'N', "\\\<C-V>\<C-M>", ''))
+ call assert_equal("l\<C-V>\rl",
+ \ substitute('lLl', 'L', "\<C-V>\r", ''))
+ call assert_equal("m\rm", substitute('mMm', 'M', '\r', ''))
+ call assert_equal("n\<C-V>\rn",
+ \ substitute('nNn', 'N', "\\\<C-V>\r", ''))
call assert_equal("o\no", substitute('oOo', 'O', '\n', ''))
call assert_equal("p\<C-H>p", substitute('pPp', 'P', '\b', ''))
call assert_equal("q\tq", substitute('qQq', 'Q', '\t', ''))
call assert_equal('r\r', substitute('rRr', 'R', '\\', ''))
call assert_equal('scs', substitute('sSs', 'S', '\c', ''))
- call assert_equal("t\<C-M>t", substitute('tTt', 'T', "\r", ''))
+ call assert_equal("t\rt", substitute('tTt', 'T', "\r", ''))
call assert_equal("u\nu", substitute('uUu', 'U', "\n", ''))
call assert_equal("v\<C-H>v", substitute('vVv', 'V', "\b", ''))
call assert_equal('w\w', substitute('wWw', 'W', "\\", ''))
@@ -528,7 +528,7 @@ func Test_sub_replace_3()
call assert_equal("e\\\\\re", substitute('eEe', 'E', "\\=\"\\\\\\\\\r\"", ''))
call assert_equal('f\rf', substitute('fFf', 'F', '\="\\r"', ''))
call assert_equal('j\nj', substitute('jJj', 'J', '\="\\n"', ''))
- call assert_equal("k\<C-M>k", substitute('kKk', 'K', '\="\r"', ''))
+ call assert_equal("k\rk", substitute('kKk', 'K', '\="\r"', ''))
call assert_equal("l\nl", substitute('lLl', 'L', '\="\n"', ''))
endfunc
@@ -540,10 +540,10 @@ func Test_sub_replace_4()
\ '\=substitute(submatch(0), ".", "\\", "")', ''))
call assert_equal('b\b', substitute('bBb', 'B',
\ '\=substitute(submatch(0), ".", "\\\\", "")', ''))
- call assert_equal("c\<C-V>\<C-M>c", substitute('cCc', 'C', '\=substitute(submatch(0), ".", "\<C-V>\<C-M>", "")', ''))
- call assert_equal("d\<C-V>\<C-M>d", substitute('dDd', 'D', '\=substitute(submatch(0), ".", "\\\<C-V>\<C-M>", "")', ''))
- call assert_equal("e\\\<C-V>\<C-M>e", substitute('eEe', 'E', '\=substitute(submatch(0), ".", "\\\\\<C-V>\<C-M>", "")', ''))
- call assert_equal("f\<C-M>f", substitute('fFf', 'F', '\=substitute(submatch(0), ".", "\\r", "")', ''))
+ call assert_equal("c\<C-V>\rc", substitute('cCc', 'C', '\=substitute(submatch(0), ".", "\<C-V>\r", "")', ''))
+ call assert_equal("d\<C-V>\rd", substitute('dDd', 'D', '\=substitute(submatch(0), ".", "\\\<C-V>\r", "")', ''))
+ call assert_equal("e\\\<C-V>\re", substitute('eEe', 'E', '\=substitute(submatch(0), ".", "\\\\\<C-V>\r", "")', ''))
+ call assert_equal("f\rf", substitute('fFf', 'F', '\=substitute(submatch(0), ".", "\\r", "")', ''))
call assert_equal("j\nj", substitute('jJj', 'J', '\=substitute(submatch(0), ".", "\\n", "")', ''))
call assert_equal("k\rk", substitute('kKk', 'K', '\=substitute(submatch(0), ".", "\r", "")', ''))
call assert_equal("l\nl", substitute('lLl', 'L', '\=substitute(submatch(0), ".", "\n", "")', ''))
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index 79a6b9ed0e..bec55174c4 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));
@@ -308,6 +309,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 +370,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 +1385,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 +1393,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 +1948,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 +2072,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;1m");
+ 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.c b/src/nvim/ui.c
index da50f068b7..4fe3e1157c 100644
--- a/src/nvim/ui.c
+++ b/src/nvim/ui.c
@@ -223,10 +223,11 @@ void ui_refresh(void)
ui_default_colors_set();
- int save_p_lz = p_lz;
- p_lz = false; // convince redrawing() to return true ...
if (!ui_client_channel_id) {
+ int save_p_lz = p_lz;
+ p_lz = false; // convince redrawing() to return true ...
screen_resize(width, height);
+ p_lz = save_p_lz;
} else {
Array args = ARRAY_DICT_INIT;
Error err = ERROR_INIT;
@@ -240,8 +241,6 @@ void ui_refresh(void)
api_clear_error(&err);
}
- p_lz = save_p_lz;
-
if (ext_widgets[kUIMessages]) {
p_ch = 0;
command_height();
diff --git a/src/nvim/ui_client.c b/src/nvim/ui_client.c
index 6e45a28e89..e723a30d32 100644
--- a/src/nvim/ui_client.c
+++ b/src/nvim/ui_client.c
@@ -87,6 +87,7 @@ Object ui_client_handle_redraw(uint64_t channel_id, Array args, Error *error)
///
/// This is just a stub. the full version will handle input, resizing, etc
void ui_client_execute(uint64_t chan)
+ FUNC_ATTR_NORETURN
{
while (true) {
loop_poll_events(&main_loop, -1);
diff --git a/src/nvim/window.c b/src/nvim/window.c
index 50a56056bf..fa71a08b94 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -25,9 +25,9 @@
#include "nvim/getchar.h"
#include "nvim/globals.h"
#include "nvim/hashtab.h"
-#include "nvim/highlight_group.h"
#include "nvim/main.h"
#include "nvim/mark.h"
+#include "nvim/match.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
@@ -300,7 +300,7 @@ newwindow:
// move window to new tab page
case 'T':
- if (one_window()) {
+ if (one_window(curwin)) {
msg(_(m_onlyone));
} else {
tabpage_T *oldtab = curtab;
@@ -560,7 +560,7 @@ wingotofile:
config.height = curwin->w_height;
config.external = true;
Error err = ERROR_INIT;
- if (!win_new_float(curwin, config, &err)) {
+ if (!win_new_float(curwin, false, config, &err)) {
emsg(err.msg);
api_clear_error(&err);
beep_flush();
@@ -629,16 +629,18 @@ void win_set_buf(Window window, Buffer buffer, bool noautocmd, Error *err)
/// Create a new float.
///
-/// if wp == NULL allocate a new window, otherwise turn existing window into a
-/// float. It must then already belong to the current tabpage!
-///
-/// config must already have been validated!
-win_T *win_new_float(win_T *wp, FloatConfig fconfig, Error *err)
+/// @param wp if NULL, allocate a new window, otherwise turn existing window into a float.
+/// It must then already belong to the current tabpage!
+/// @param last make the window the last one in the window list.
+/// Only used when allocating the autocommand window.
+/// @param config must already have been validated!
+win_T *win_new_float(win_T *wp, bool last, FloatConfig fconfig, Error *err)
{
if (wp == NULL) {
- wp = win_alloc(lastwin_nofloating(), false);
+ wp = win_alloc(last ? lastwin : lastwin_nofloating(), false);
win_init(wp, curwin, 0);
} else {
+ assert(!last);
assert(!wp->w_floating);
if (firstwin == wp && lastwin_nofloating() == wp) {
// last non-float
@@ -834,7 +836,7 @@ void ui_ext_win_position(win_T *wp)
FloatConfig c = wp->w_float_config;
if (!c.external) {
ScreenGrid *grid = &default_grid;
- float row = c.row, col = c.col;
+ Float row = c.row, col = c.col;
if (c.relative == kFloatRelativeWindow) {
Error dummy = ERROR_INIT;
win_T *win = find_window_by_handle(c.window, &dummy);
@@ -2348,17 +2350,19 @@ void entering_window(win_T *const win)
}
}
-/// Closes all windows for buffer `buf`.
+/// Closes all windows for buffer `buf` unless there is only one non-floating window.
///
-/// @param keep_curwin don't close `curwin`
-void close_windows(buf_T *buf, int keep_curwin)
+/// @param keep_curwin don't close `curwin`
+void close_windows(buf_T *buf, bool keep_curwin)
{
tabpage_T *tp, *nexttp;
int h = tabline_height();
++RedrawingDisabled;
- for (win_T *wp = firstwin; wp != NULL && !ONE_WINDOW;) {
+ // Start from lastwin to close floating windows with the same buffer first.
+ // When the autocommand window is involved win_close() may need to print an error message.
+ for (win_T *wp = lastwin; wp != NULL && (lastwin == aucmd_win || !one_window(wp));) {
if (wp->w_buffer == buf && (!keep_curwin || wp != curwin)
&& !(wp->w_closing || wp->w_buffer->b_locked > 0)) {
if (win_close(wp, false, false) == FAIL) {
@@ -2367,9 +2371,9 @@ void close_windows(buf_T *buf, int keep_curwin)
}
// Start all over, autocommands may change the window layout.
- wp = firstwin;
+ wp = lastwin;
} else {
- wp = wp->w_next;
+ wp = wp->w_prev;
}
}
@@ -2399,23 +2403,24 @@ void close_windows(buf_T *buf, int keep_curwin)
}
}
-/// Check that current window is the last one.
+/// Check that the specified window is the last one.
+/// @param win counted even if floating
///
-/// @return true if the current window is the only window that exists, false if
-/// there is another, possibly in another tab page.
-static bool last_window(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+/// @return true if the specified window is the only window that exists,
+/// false if there is another, possibly in another tab page.
+bool last_window(win_T *win) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
- return one_window() && first_tabpage->tp_next == NULL;
+ return one_window(win) && first_tabpage->tp_next == NULL;
}
-/// Check that current tab page contains no more then one window other than
-/// "aucmd_win". Only counts floating window if it is current.
-bool one_window(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+/// Check that current tab page contains no more then one window other than `aucmd_win`.
+/// @param counted_float counted even if floating, but not if it is `aucmd_win`
+bool one_window(win_T *counted_float) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
bool seen_one = false;
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- if (wp != aucmd_win && (!wp->w_floating || wp == curwin)) {
+ if (wp != aucmd_win && (!wp->w_floating || wp == counted_float)) {
if (seen_one) {
return false;
}
@@ -2439,12 +2444,14 @@ bool last_nonfloat(win_T *wp) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
return wp != NULL && firstwin == wp && !(wp->w_next && !wp->w_floating);
}
-/// Check if floating windows can be closed.
+/// Check if floating windows in the current tab can be closed.
+/// Do not call this when the autocommand window is in use!
///
/// @return true if all floating windows can be closed
-static bool can_close_floating_windows(tabpage_T *tab)
+static bool can_close_floating_windows(void)
{
- FOR_ALL_WINDOWS_IN_TAB(wp, tab) {
+ assert(lastwin != aucmd_win);
+ for (win_T *wp = lastwin; wp->w_floating; wp = wp->w_prev) {
buf_T *buf = wp->w_buffer;
int need_hide = (bufIsChanged(buf) && buf->b_nwindows <= 1);
@@ -2530,7 +2537,7 @@ int win_close(win_T *win, bool free_buf, bool force)
frame_T *win_frame = win->w_floating ? NULL : win->w_frame->fr_parent;
const bool had_diffmode = win->w_p_diff;
- if (last_window() && !win->w_floating) {
+ if (last_window(win)) {
emsg(_("E444: Cannot close last window"));
return FAIL;
}
@@ -2543,18 +2550,18 @@ int win_close(win_T *win, bool free_buf, bool force)
emsg(_(e_autocmd_close));
return FAIL;
}
- if ((firstwin == aucmd_win || lastwin == aucmd_win) && one_window()) {
- emsg(_("E814: Cannot close window, only autocmd window would remain"));
- return FAIL;
- }
- if ((firstwin == win && lastwin_nofloating() == win)
- && lastwin->w_floating) {
- if (force || can_close_floating_windows(curtab)) {
- win_T *nextwp;
- for (win_T *wpp = firstwin; wpp != NULL; wpp = nextwp) {
- nextwp = wpp->w_next;
- if (wpp->w_floating) {
- win_close(wpp, free_buf, force);
+ if (lastwin->w_floating && one_window(win)) {
+ if (lastwin == aucmd_win) {
+ emsg(_("E814: Cannot close window, only autocmd window would remain"));
+ return FAIL;
+ }
+ if (force || can_close_floating_windows()) {
+ // close the last window until the there are no floating windows
+ while (lastwin->w_floating) {
+ // `force` flag isn't actually used when closing a floating window.
+ if (win_close(lastwin, free_buf, true) == FAIL) {
+ // If closing the window fails give up, to avoid looping forever.
+ return FAIL;
}
}
} else {
@@ -2605,7 +2612,7 @@ int win_close(win_T *win, bool free_buf, bool force)
return FAIL;
}
win->w_closing = false;
- if (last_window()) {
+ if (last_window(win)) {
return FAIL;
}
}
@@ -2615,7 +2622,7 @@ int win_close(win_T *win, bool free_buf, bool force)
return FAIL;
}
win->w_closing = false;
- if (last_window()) {
+ if (last_window(win)) {
return FAIL;
}
// autocmds may abort script processing
@@ -2684,7 +2691,7 @@ int win_close(win_T *win, bool free_buf, bool force)
}
if (only_one_window() && win_valid(win) && win->w_buffer == NULL
- && (last_window() || curtab != prev_curtab
+ && (last_window(win) || curtab != prev_curtab
|| close_last_window_tabpage(win, free_buf, prev_curtab))
&& !win->w_floating) {
// Autocommands have closed all windows, quit now. Restore
@@ -2704,7 +2711,7 @@ int win_close(win_T *win, bool free_buf, bool force)
// Autocommands may have closed the window already, or closed the only
// other window or moved to another tab page.
- if (!win_valid(win) || (!win->w_floating && last_window())
+ if (!win_valid(win) || (!win->w_floating && last_window(win))
|| close_last_window_tabpage(win, free_buf, prev_curtab)) {
return FAIL;
}
@@ -3743,7 +3750,7 @@ void close_others(int message, int forceit)
return;
}
- if (one_window() && !lastwin->w_floating) {
+ if (one_nonfloat() && !lastwin->w_floating) {
if (message
&& !autocmd_busy) {
msg(_(m_onlyone));
@@ -3844,7 +3851,7 @@ void win_alloc_aucmd_win(void)
fconfig.width = Columns;
fconfig.height = 5;
fconfig.focusable = false;
- aucmd_win = win_new_float(NULL, fconfig, &err);
+ aucmd_win = win_new_float(NULL, true, fconfig, &err);
aucmd_win->w_buffer->b_nwindows--;
RESET_BINDING(aucmd_win);
}
@@ -6498,7 +6505,7 @@ char_u *file_name_in_line(char_u *line, int col, int options, long count, char_u
void last_status(bool morewin)
{
// Don't make a difference between horizontal or vertical split.
- last_status_rec(topframe, (p_ls == 2 || (p_ls == 1 && (morewin || !one_window()))),
+ last_status_rec(topframe, (p_ls == 2 || (p_ls == 1 && (morewin || !one_nonfloat()))),
global_stl_height() > 0);
}
diff --git a/test/functional/autocmd/autocmd_spec.lua b/test/functional/autocmd/autocmd_spec.lua
index f37f04b32f..c010fb8034 100644
--- a/test/functional/autocmd/autocmd_spec.lua
+++ b/test/functional/autocmd/autocmd_spec.lua
@@ -398,6 +398,25 @@ describe('autocmd', function()
]])
end)
+ describe('closing last non-floating window in tab from `aucmd_win`', function()
+ before_each(function()
+ command('edit Xa.txt')
+ command('tabnew Xb.txt')
+ command('autocmd BufAdd Xa.txt 1close')
+ end)
+
+ it('gives E814 when there are no other floating windows', function()
+ eq('Vim(close):E814: Cannot close window, only autocmd window would remain',
+ pcall_err(command, 'doautoall BufAdd'))
+ end)
+
+ it('gives E814 when there are other floating windows', function()
+ meths.open_win(0, true, {width = 10, height = 10, relative = 'editor', row = 10, col = 10})
+ eq('Vim(close):E814: Cannot close window, only autocmd window would remain',
+ pcall_err(command, 'doautoall BufAdd'))
+ end)
+ end)
+
it(':doautocmd does not warn "No matching autocommands" #10689', function()
local screen = Screen.new(32, 3)
screen:attach()
diff --git a/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua
index d91feb4bc1..4d984af41e 100644
--- a/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua
+++ b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua
@@ -10,6 +10,7 @@ local feed = helpers.feed
local nvim_prog = helpers.nvim_prog
local ok = helpers.ok
local rmdir = helpers.rmdir
+local os_kill = helpers.os_kill
local set_session = helpers.set_session
local spawn = helpers.spawn
local nvim_async = helpers.nvim_async
@@ -62,6 +63,7 @@ describe(':preserve', function()
local swappath1 = eval('g:swapname')
+ os_kill(eval('getpid()'))
-- Start another Nvim instance.
local nvim2 = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed'},
true)
@@ -122,6 +124,7 @@ describe('swapfile detection', function()
feed('isometext<esc>')
command('preserve')
+ os_kill(eval('getpid()'))
-- Start another Nvim instance.
local nvim2 = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed'},
true)
diff --git a/test/functional/legacy/eval_spec.lua b/test/functional/legacy/eval_spec.lua
index 05d853622e..d3c0b4b938 100644
--- a/test/functional/legacy/eval_spec.lua
+++ b/test/functional/legacy/eval_spec.lua
@@ -46,7 +46,7 @@ describe('eval', function()
command('AR "')
command([[let @" = "abc\n"]])
source('AR "')
- command([[let @" = "abc\<C-m>"]])
+ command([[let @" = "abc\r"]])
command('AR "')
command([[let @= = '"abc"']])
command('AR =')
diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua
index 7e71ada02d..29fbe9a93b 100644
--- a/test/functional/ui/decorations_spec.lua
+++ b/test/functional/ui/decorations_spec.lua
@@ -1111,6 +1111,27 @@ if (h->n_buckets < new_n_buckets) { // expand
]]}
end)
+ it('does not cause syntax ml_get error at the end of a buffer #17816', function()
+ command([[syntax region foo keepend start='^foo' end='^$']])
+ command('syntax sync minlines=100')
+ insert('foo')
+ meths.buf_set_extmark(0, ns, 0, 0, {virt_lines = {{{'bar', 'Comment'}}}})
+ screen:expect([[
+ fo^o |
+ {6:bar} |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]])
+ end)
+
it('works with a block scrolling up', function()
screen:try_resize(30, 7)
insert("aa\nbb\ncc\ndd\nee\nff\ngg\nhh")
diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua
index dc26c52f1a..359d1f206a 100644
--- a/test/functional/ui/float_spec.lua
+++ b/test/functional/ui/float_spec.lua
@@ -8,6 +8,7 @@ local command, feed_command = helpers.command, helpers.feed_command
local eval = helpers.eval
local eq = helpers.eq
local neq = helpers.neq
+local expect = helpers.expect
local exec_lua = helpers.exec_lua
local insert = helpers.insert
local meths = helpers.meths
@@ -16,6 +17,7 @@ local funcs = helpers.funcs
local run = helpers.run
local pcall_err = helpers.pcall_err
local tbl_contains = global_helpers.tbl_contains
+local curbuf, curwin, curtab = helpers.curbuf, helpers.curwin, helpers.curtab
describe('float window', function()
before_each(function()
@@ -417,26 +419,285 @@ describe('float window', function()
eq(winids, eval('winids'))
end)
- it('closed when the last non-float window is closed', function()
- local tabpage = exec_lua([[
- vim.cmd('edit ./src/nvim/main.c')
- vim.cmd('tabedit %')
-
- local buf = vim.api.nvim_create_buf(false, true)
- local win = vim.api.nvim_open_win(buf, false, {
- relative = 'win',
- row = 1,
- col = 1,
- width = 10,
- height = 2
- })
-
- vim.cmd('quit')
+ describe('with only one tabpage,', function()
+ local float_opts = {relative = 'editor', row = 1, col = 1, width = 1, height = 1}
+ local old_buf, old_win
+ before_each(function()
+ insert('foo')
+ old_buf = curbuf().id
+ old_win = curwin().id
+ end)
+ describe('closing the last non-floating window gives E444', function()
+ before_each(function()
+ meths.open_win(old_buf, true, float_opts)
+ end)
+ it('if called from non-floating window', function()
+ meths.set_current_win(old_win)
+ eq('Vim:E444: Cannot close last window',
+ pcall_err(meths.win_close, old_win, false))
+ end)
+ it('if called from floating window', function()
+ eq('Vim:E444: Cannot close last window',
+ pcall_err(meths.win_close, old_win, false))
+ end)
+ end)
+ describe("deleting the last non-floating window's buffer", function()
+ describe('leaves one window with an empty buffer when there is only one buffer', function()
+ local same_buf_float
+ before_each(function()
+ same_buf_float = meths.open_win(old_buf, false, float_opts).id
+ end)
+ after_each(function()
+ eq(old_win, curwin().id)
+ expect('')
+ eq(1, #meths.list_wins())
+ end)
+ it('if called from non-floating window', function()
+ meths.buf_delete(old_buf, {force = true})
+ end)
+ it('if called from floating window', function()
+ meths.set_current_win(same_buf_float)
+ command('autocmd WinLeave * let g:win_leave = nvim_get_current_win()')
+ command('autocmd WinEnter * let g:win_enter = nvim_get_current_win()')
+ meths.buf_delete(old_buf, {force = true})
+ eq(same_buf_float, eval('g:win_leave'))
+ eq(old_win, eval('g:win_enter'))
+ end)
+ end)
+ describe('closes other windows with that buffer when there are other buffers', function()
+ local same_buf_float, other_buf, other_buf_float
+ before_each(function()
+ same_buf_float = meths.open_win(old_buf, false, float_opts).id
+ other_buf = meths.create_buf(true, false).id
+ other_buf_float = meths.open_win(other_buf, true, float_opts).id
+ insert('bar')
+ meths.set_current_win(old_win)
+ end)
+ after_each(function()
+ eq(other_buf, curbuf().id)
+ expect('bar')
+ eq(2, #meths.list_wins())
+ end)
+ it('if called from non-floating window', function()
+ meths.buf_delete(old_buf, {force = true})
+ eq(old_win, curwin().id)
+ end)
+ it('if called from floating window with the same buffer', function()
+ meths.set_current_win(same_buf_float)
+ command('autocmd WinLeave * let g:win_leave = nvim_get_current_win()')
+ command('autocmd WinEnter * let g:win_enter = nvim_get_current_win()')
+ meths.buf_delete(old_buf, {force = true})
+ eq(same_buf_float, eval('g:win_leave'))
+ eq(old_win, eval('g:win_enter'))
+ eq(old_win, curwin().id)
+ end)
+ -- TODO: this case is too hard to deal with
+ pending('if called from floating window with another buffer', function()
+ meths.set_current_win(other_buf_float)
+ meths.buf_delete(old_buf, {force = true})
+ end)
+ end)
+ describe('creates an empty buffer when there is only one listed buffer', function()
+ local same_buf_float, unlisted_buf_float
+ before_each(function()
+ same_buf_float = meths.open_win(old_buf, false, float_opts).id
+ local unlisted_buf = meths.create_buf(true, false).id
+ unlisted_buf_float = meths.open_win(unlisted_buf, true, float_opts).id
+ insert('unlisted')
+ command('set nobuflisted')
+ meths.set_current_win(old_win)
+ end)
+ after_each(function()
+ expect('')
+ eq(2, #meths.list_wins())
+ end)
+ it('if called from non-floating window', function()
+ meths.buf_delete(old_buf, {force = true})
+ eq(old_win, curwin().id)
+ end)
+ it('if called from floating window with the same buffer', function()
+ meths.set_current_win(same_buf_float)
+ command('autocmd WinLeave * let g:win_leave = nvim_get_current_win()')
+ command('autocmd WinEnter * let g:win_enter = nvim_get_current_win()')
+ meths.buf_delete(old_buf, {force = true})
+ eq(same_buf_float, eval('g:win_leave'))
+ eq(old_win, eval('g:win_enter'))
+ eq(old_win, curwin().id)
+ end)
+ -- TODO: this case is too hard to deal with
+ pending('if called from floating window with an unlisted buffer', function()
+ meths.set_current_win(unlisted_buf_float)
+ meths.buf_delete(old_buf, {force = true})
+ end)
+ end)
+ end)
+ describe('with splits, deleting the last listed buffer creates an empty buffer', function()
+ describe('when a non-floating window has an unlisted buffer', function()
+ local same_buf_float
+ before_each(function()
+ command('botright vnew')
+ insert('unlisted')
+ command('set nobuflisted')
+ meths.set_current_win(old_win)
+ same_buf_float = meths.open_win(old_buf, false, float_opts).id
+ end)
+ after_each(function()
+ expect('')
+ eq(2, #meths.list_wins())
+ end)
+ it('if called from non-floating window with the deleted buffer', function()
+ meths.buf_delete(old_buf, {force = true})
+ eq(old_win, curwin().id)
+ end)
+ it('if called from floating window with the deleted buffer', function()
+ meths.set_current_win(same_buf_float)
+ meths.buf_delete(old_buf, {force = true})
+ eq(same_buf_float, curwin().id)
+ end)
+ end)
+ end)
+ end)
- return vim.api.nvim_get_current_tabpage()
- ]])
+ describe('with mulitple tabpages but only one listed buffer,', function()
+ local float_opts = {relative = 'editor', row = 1, col = 1, width = 1, height = 1}
+ local unlisted_buf, old_buf, old_win
+ before_each(function()
+ insert('unlisted')
+ command('set nobuflisted')
+ unlisted_buf = curbuf().id
+ command('tabnew')
+ insert('foo')
+ old_buf = curbuf().id
+ old_win = curwin().id
+ end)
+ describe('without splits, deleting the last listed buffer creates an empty buffer', function()
+ local same_buf_float
+ before_each(function()
+ meths.set_current_win(old_win)
+ same_buf_float = meths.open_win(old_buf, false, float_opts).id
+ end)
+ after_each(function()
+ expect('')
+ eq(2, #meths.list_wins())
+ eq(2, #meths.list_tabpages())
+ end)
+ it('if called from non-floating window', function()
+ meths.buf_delete(old_buf, {force = true})
+ eq(old_win, curwin().id)
+ end)
+ it('if called from floating window with the same buffer', function()
+ meths.set_current_win(same_buf_float)
+ command('autocmd WinLeave * let g:win_leave = nvim_get_current_win()')
+ command('autocmd WinEnter * let g:win_enter = nvim_get_current_win()')
+ meths.buf_delete(old_buf, {force = true})
+ eq(same_buf_float, eval('g:win_leave'))
+ eq(old_win, eval('g:win_enter'))
+ eq(old_win, curwin().id)
+ end)
+ end)
+ describe('with splits, deleting the last listed buffer creates an empty buffer', function()
+ local same_buf_float
+ before_each(function()
+ command('botright vsplit')
+ meths.set_current_buf(unlisted_buf)
+ meths.set_current_win(old_win)
+ same_buf_float = meths.open_win(old_buf, false, float_opts).id
+ end)
+ after_each(function()
+ expect('')
+ eq(3, #meths.list_wins())
+ eq(2, #meths.list_tabpages())
+ end)
+ it('if called from non-floating window with the deleted buffer', function()
+ meths.buf_delete(old_buf, {force = true})
+ eq(old_win, curwin().id)
+ end)
+ it('if called from floating window with the deleted buffer', function()
+ meths.set_current_win(same_buf_float)
+ meths.buf_delete(old_buf, {force = true})
+ eq(same_buf_float, curwin().id)
+ end)
+ end)
+ end)
- eq(1, tabpage)
+ describe('with multiple tabpages and multiple listed buffers,', function()
+ local float_opts = {relative = 'editor', row = 1, col = 1, width = 1, height = 1}
+ local old_tabpage, old_buf, old_win
+ before_each(function()
+ old_tabpage = curtab().id
+ insert('oldtab')
+ command('tabnew')
+ old_buf = curbuf().id
+ old_win = curwin().id
+ end)
+ describe('closing the last non-floating window', function()
+ describe('closes the tabpage when all floating windows are closeable', function()
+ local same_buf_float
+ before_each(function()
+ same_buf_float = meths.open_win(old_buf, false, float_opts).id
+ end)
+ after_each(function()
+ eq(old_tabpage, curtab().id)
+ expect('oldtab')
+ eq(1, #meths.list_tabpages())
+ end)
+ it('if called from non-floating window', function()
+ meths.win_close(old_win, false)
+ end)
+ it('if called from floating window', function()
+ meths.set_current_win(same_buf_float)
+ meths.win_close(old_win, false)
+ end)
+ end)
+ describe('gives E5601 when there are non-closeable floating windows', function()
+ local other_buf_float
+ before_each(function()
+ command('set nohidden')
+ local other_buf = meths.create_buf(true, false).id
+ other_buf_float = meths.open_win(other_buf, true, float_opts).id
+ insert('foo')
+ meths.set_current_win(old_win)
+ end)
+ it('if called from non-floating window', function()
+ eq('Vim:E5601: Cannot close window, only floating window would remain',
+ pcall_err(meths.win_close, old_win, false))
+ end)
+ it('if called from floating window', function()
+ meths.set_current_win(other_buf_float)
+ eq('Vim:E5601: Cannot close window, only floating window would remain',
+ pcall_err(meths.win_close, old_win, false))
+ end)
+ end)
+ end)
+ describe("deleting the last non-floating window's buffer", function()
+ describe('closes the tabpage when all floating windows are closeable', function()
+ local same_buf_float, other_buf, other_buf_float
+ before_each(function()
+ same_buf_float = meths.open_win(old_buf, false, float_opts).id
+ other_buf = meths.create_buf(true, false).id
+ other_buf_float = meths.open_win(other_buf, true, float_opts).id
+ meths.set_current_win(old_win)
+ end)
+ after_each(function()
+ eq(old_tabpage, curtab().id)
+ expect('oldtab')
+ eq(1, #meths.list_tabpages())
+ end)
+ it('if called from non-floating window', function()
+ meths.buf_delete(old_buf, {force = false})
+ end)
+ it('if called from floating window with the same buffer', function()
+ meths.set_current_win(same_buf_float)
+ meths.buf_delete(old_buf, {force = false})
+ end)
+ -- TODO: this case is too hard to deal with
+ pending('if called from floating window with another buffer', function()
+ meths.set_current_win(other_buf_float)
+ meths.buf_delete(old_buf, {force = false})
+ end)
+ end)
+ -- TODO: what to do when there are non-closeable floating windows?
+ end)
end)
local function with_ext_multigrid(multigrid)
@@ -5596,7 +5857,7 @@ describe('float window', function()
[2:----------------------------------------]|
[2:----------------------------------------]|
[2:----------------------------------------]|
- {5:[No Name] [+] }|
+ [2:----------------------------------------]|
[3:----------------------------------------]|
## grid 2
x |
@@ -5604,6 +5865,7 @@ describe('float window', function()
{0:~ }|
{0:~ }|
{0:~ }|
+ {0:~ }|
## grid 3
:quit |
## grid 4
@@ -5619,7 +5881,7 @@ describe('float window', function()
{0:~ }{1:^y }{0: }|
{0:~ }{2:~ }{0: }|
{0:~ }|
- {5:[No Name] [+] }|
+ {0:~ }|
:quit |
]])
end
diff --git a/test/functional/ui/highlight_spec.lua b/test/functional/ui/highlight_spec.lua
index 64f0ba3419..559738ddab 100644
--- a/test/functional/ui/highlight_spec.lua
+++ b/test/functional/ui/highlight_spec.lua
@@ -1081,6 +1081,46 @@ describe('CursorLine and CursorLineNr highlights', function()
]])
end)
+ it('is updated if cursor is moved up from timer vim-patch:8.2.4591', function()
+ local screen = Screen.new(50, 8)
+ screen:set_default_attr_ids({
+ [1] = {background = Screen.colors.Gray90}, -- CursorLine
+ [2] = {bold = true, foreground = Screen.colors.Blue1}, -- NonText
+ })
+ screen:attach()
+ exec([[
+ call setline(1, ['aaaaa', 'bbbbb', 'ccccc', 'ddddd'])
+ set cursorline
+ call cursor(4, 1)
+
+ func Func(timer)
+ call cursor(2, 1)
+ endfunc
+
+ call timer_start(300, 'Func')
+ ]])
+ screen:expect({grid = [[
+ aaaaa |
+ bbbbb |
+ ccccc |
+ {1:^ddddd }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ |
+ ]], timeout = 100})
+ screen:expect({grid = [[
+ aaaaa |
+ {1:^bbbbb }|
+ ccccc |
+ ddddd |
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ |
+ ]]})
+ end)
+
it('with split windows in diff mode', function()
local screen = Screen.new(50,12)
screen:set_default_attr_ids({
diff --git a/test/functional/ui/input_spec.lua b/test/functional/ui/input_spec.lua
index 7000505909..f5ae228b1e 100644
--- a/test/functional/ui/input_spec.lua
+++ b/test/functional/ui/input_spec.lua
@@ -3,6 +3,7 @@ local clear, feed_command = helpers.clear, helpers.feed_command
local feed, next_msg, eq = helpers.feed, helpers.next_msg, helpers.eq
local command = helpers.command
local expect = helpers.expect
+local curbuf_contents = helpers.curbuf_contents
local meths = helpers.meths
local exec_lua = helpers.exec_lua
local write_file = helpers.write_file
@@ -159,6 +160,50 @@ describe('input split utf sequences', function()
end)
end)
+describe('input pairs', function()
+ describe('<tab> / <c-i>', function()
+ it('ok', function()
+ feed('i<tab><c-i><esc>')
+ eq('\t\t', curbuf_contents())
+ end)
+
+ it('can be mapped', function()
+ command('inoremap <tab> TAB!')
+ command('inoremap <c-i> CTRL-I!')
+ feed('i<tab><c-i><esc>')
+ eq('TAB!CTRL-I!', curbuf_contents())
+ end)
+ end)
+
+ describe('<cr> / <c-m>', function()
+ it('ok', function()
+ feed('iunos<c-m>dos<cr>tres<esc>')
+ eq('unos\ndos\ntres', curbuf_contents())
+ end)
+
+ it('can be mapped', function()
+ command('inoremap <c-m> SNIPPET!')
+ command('inoremap <cr> , and then<cr>')
+ feed('iunos<c-m>dos<cr>tres<esc>')
+ eq('unosSNIPPET!dos, and then\ntres', curbuf_contents())
+ end)
+ end)
+
+ describe('<esc> / <c-[>', function()
+ it('ok', function()
+ feed('2adouble<c-[>asingle<esc>')
+ eq('doubledoublesingle', curbuf_contents())
+ end)
+
+ it('can be mapped', function()
+ command('inoremap <c-[> HALLOJ!')
+ command('inoremap <esc> ,<esc>')
+ feed('2adubbel<c-[>upp<esc>')
+ eq('dubbelHALLOJ!upp,dubbelHALLOJ!upp,', curbuf_contents())
+ end)
+ end)
+end)
+
describe('input non-printable chars', function()
after_each(function()
os.remove('Xtest-overwrite')