aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rwxr-xr-xsrc/clint.py10
-rw-r--r--src/nvim/CMakeLists.txt21
-rw-r--r--src/nvim/README.md10
-rw-r--r--src/nvim/api/buffer.c22
-rw-r--r--src/nvim/api/private/helpers.c8
-rw-r--r--src/nvim/api/private/helpers.h4
-rw-r--r--src/nvim/api/ui.c58
-rw-r--r--src/nvim/api/ui_events.in.h4
-rw-r--r--src/nvim/api/vim.c49
-rw-r--r--src/nvim/ascii.h26
-rw-r--r--src/nvim/auevents.lua1
-rw-r--r--src/nvim/buffer.c145
-rw-r--r--src/nvim/buffer_defs.h532
-rw-r--r--src/nvim/buffer_updates.c5
-rw-r--r--src/nvim/change.c4
-rw-r--r--src/nvim/channel.c1
-rw-r--r--src/nvim/channel.h2
-rw-r--r--src/nvim/charset.c2
-rw-r--r--src/nvim/cursor_shape.h8
-rw-r--r--src/nvim/diff.c25
-rw-r--r--src/nvim/edit.c1444
-rw-r--r--src/nvim/edit.h3
-rw-r--r--src/nvim/eval.c15375
-rw-r--r--src/nvim/eval.h113
-rw-r--r--src/nvim/eval.lua8
-rw-r--r--src/nvim/eval/encode.c2
-rw-r--r--src/nvim/eval/funcs.c11192
-rw-r--r--src/nvim/eval/funcs.h24
-rw-r--r--src/nvim/eval/typval.c60
-rw-r--r--src/nvim/eval/typval.h37
-rw-r--r--src/nvim/eval/typval_encode.c.h2
-rw-r--r--src/nvim/eval/userfunc.c3385
-rw-r--r--src/nvim/eval/userfunc.h36
-rw-r--r--src/nvim/event/loop.c36
-rw-r--r--src/nvim/event/stream.c5
-rw-r--r--src/nvim/ex_cmds.c41
-rw-r--r--src/nvim/ex_cmds.lua39
-rw-r--r--src/nvim/ex_cmds2.c15
-rw-r--r--src/nvim/ex_cmds_defs.h61
-rw-r--r--src/nvim/ex_docmd.c1311
-rw-r--r--src/nvim/ex_docmd.h14
-rw-r--r--src/nvim/ex_eval.c4
-rw-r--r--src/nvim/ex_getln.c61
-rw-r--r--src/nvim/ex_getln.h3
-rw-r--r--src/nvim/ex_session.c1047
-rw-r--r--src/nvim/ex_session.h13
-rw-r--r--src/nvim/extmark.c66
-rw-r--r--src/nvim/extmark.h2
-rw-r--r--src/nvim/fileio.c53
-rw-r--r--src/nvim/fold.c348
-rw-r--r--src/nvim/generators/gen_api_dispatch.lua6
-rw-r--r--src/nvim/getchar.c54
-rw-r--r--src/nvim/getchar.h4
-rw-r--r--src/nvim/globals.h552
-rw-r--r--src/nvim/grid_defs.h2
-rw-r--r--src/nvim/hardcopy.c587
-rw-r--r--src/nvim/if_cscope.c372
-rw-r--r--src/nvim/if_cscope_defs.h31
-rw-r--r--src/nvim/indent.c36
-rw-r--r--src/nvim/indent_c.c9
-rw-r--r--src/nvim/keymap.h24
-rw-r--r--src/nvim/lua/executor.c160
-rw-r--r--src/nvim/lua/treesitter.c41
-rw-r--r--src/nvim/lua/vim.lua29
-rw-r--r--src/nvim/macros.h42
-rw-r--r--src/nvim/main.c146
-rw-r--r--src/nvim/marktree.c9
-rw-r--r--src/nvim/mbyte.c126
-rw-r--r--src/nvim/memline.c87
-rw-r--r--src/nvim/menu.c348
-rw-r--r--src/nvim/menu.h34
-rw-r--r--src/nvim/message.c6
-rw-r--r--src/nvim/misc1.c26
-rw-r--r--src/nvim/mouse.c52
-rw-r--r--src/nvim/mouse.h1
-rw-r--r--src/nvim/move.c131
-rw-r--r--src/nvim/msgpack_rpc/channel.c2
-rw-r--r--src/nvim/msgpack_rpc/channel.h2
-rw-r--r--src/nvim/msgpack_rpc/server.c2
-rw-r--r--src/nvim/normal.c132
-rw-r--r--src/nvim/ops.c141
-rw-r--r--src/nvim/option.c637
-rw-r--r--src/nvim/option_defs.h7
-rw-r--r--src/nvim/options.lua13
-rw-r--r--src/nvim/os/env.c21
-rw-r--r--src/nvim/os/fs.c69
-rw-r--r--src/nvim/os/shell.c498
-rw-r--r--src/nvim/os/signal.c33
-rw-r--r--src/nvim/os/time.c55
-rw-r--r--src/nvim/os_unix.c531
-rw-r--r--src/nvim/path.c24
-rw-r--r--src/nvim/popupmnu.c76
-rw-r--r--src/nvim/quickfix.c30
-rw-r--r--src/nvim/regexp.c36
-rw-r--r--src/nvim/regexp_defs.h20
-rw-r--r--src/nvim/screen.c303
-rw-r--r--src/nvim/search.c36
-rw-r--r--src/nvim/shada.c12
-rw-r--r--src/nvim/sign.c210
-rw-r--r--src/nvim/spell.c76
-rw-r--r--src/nvim/spell_defs.h13
-rw-r--r--src/nvim/spellfile.c78
-rw-r--r--src/nvim/syntax.c4
-rw-r--r--src/nvim/tag.c60
-rw-r--r--src/nvim/terminal.c18
-rw-r--r--src/nvim/testdir/Makefile2
-rw-r--r--src/nvim/testdir/runtest.vim5
-rw-r--r--src/nvim/testdir/screendump.vim2
-rw-r--r--src/nvim/testdir/shared.vim15
-rw-r--r--src/nvim/testdir/term_util.vim11
-rw-r--r--src/nvim/testdir/test49.vim4
-rw-r--r--src/nvim/testdir/test_autocmd.vim77
-rw-r--r--src/nvim/testdir/test_breakindent.vim10
-rw-r--r--src/nvim/testdir/test_bufline.vim2
-rw-r--r--src/nvim/testdir/test_bufwintabinfo.vim7
-rw-r--r--src/nvim/testdir/test_cmdline.vim59
-rw-r--r--src/nvim/testdir/test_compiler.vim9
-rw-r--r--src/nvim/testdir/test_debugger.vim2
-rw-r--r--src/nvim/testdir/test_diffmode.vim12
-rw-r--r--src/nvim/testdir/test_digraph.vim12
-rw-r--r--src/nvim/testdir/test_edit.vim17
-rw-r--r--src/nvim/testdir/test_escaped_glob.vim2
-rw-r--r--src/nvim/testdir/test_excmd.vim32
-rw-r--r--src/nvim/testdir/test_expr.vim2
-rw-r--r--src/nvim/testdir/test_filetype.vim10
-rw-r--r--src/nvim/testdir/test_fold.vim1
-rw-r--r--src/nvim/testdir/test_functions.vim66
-rw-r--r--src/nvim/testdir/test_hardcopy.vim169
-rw-r--r--src/nvim/testdir/test_highlight.vim1
-rw-r--r--src/nvim/testdir/test_ins_complete.vim17
-rw-r--r--src/nvim/testdir/test_listchars.vim20
-rw-r--r--src/nvim/testdir/test_maparg.vim7
-rw-r--r--src/nvim/testdir/test_mapping.vim74
-rw-r--r--src/nvim/testdir/test_messages.vim34
-rw-r--r--src/nvim/testdir/test_mksession.vim33
-rw-r--r--src/nvim/testdir/test_number.vim11
-rw-r--r--src/nvim/testdir/test_options.vim58
-rw-r--r--src/nvim/testdir/test_plus_arg_edit.vim28
-rw-r--r--src/nvim/testdir/test_popup.vim14
-rw-r--r--src/nvim/testdir/test_profile.vim26
-rw-r--r--src/nvim/testdir/test_quickfix.vim15
-rw-r--r--src/nvim/testdir/test_quotestar.vim3
-rw-r--r--src/nvim/testdir/test_regexp_utf8.vim141
-rw-r--r--src/nvim/testdir/test_restricted.vim103
-rw-r--r--src/nvim/testdir/test_search.vim6
-rw-r--r--src/nvim/testdir/test_signs.vim413
-rw-r--r--src/nvim/testdir/test_spell.vim22
-rw-r--r--src/nvim/testdir/test_startup.vim2
-rw-r--r--src/nvim/testdir/test_startup_utf8.vim2
-rw-r--r--src/nvim/testdir/test_statusline.vim21
-rw-r--r--src/nvim/testdir/test_substitute.vim19
-rw-r--r--src/nvim/testdir/test_suspend.vim3
-rw-r--r--src/nvim/testdir/test_tabpage.vim2
-rw-r--r--src/nvim/testdir/test_tagjump.vim22
-rw-r--r--src/nvim/testdir/test_taglist.vim17
-rw-r--r--src/nvim/testdir/test_textformat.vim423
-rw-r--r--src/nvim/testdir/test_timers.vim6
-rw-r--r--src/nvim/testdir/test_user_func.vim4
-rw-r--r--src/nvim/testdir/test_utf8.vim43
-rw-r--r--src/nvim/testdir/test_writefile.vim7
-rw-r--r--src/nvim/testdir/view_util.vim13
-rw-r--r--src/nvim/tui/input.c26
-rw-r--r--src/nvim/tui/input.h2
-rw-r--r--src/nvim/tui/terminfo.c9
-rw-r--r--src/nvim/tui/tui.c189
-rw-r--r--src/nvim/ui.c19
-rw-r--r--src/nvim/ui.h7
-rw-r--r--src/nvim/undo.c40
-rw-r--r--src/nvim/version.c44
-rw-r--r--src/nvim/vim.h5
-rw-r--r--src/nvim/viml/parser/expressions.c3
-rw-r--r--src/nvim/viml/parser/expressions.h4
-rw-r--r--src/nvim/window.c139
-rw-r--r--src/nvim/xdiff/xdiff.h24
-rw-r--r--src/nvim/xdiff/xdiffi.c44
-rw-r--r--src/nvim/xdiff/xdiffi.h2
-rw-r--r--src/nvim/xdiff/xemit.c12
-rw-r--r--src/nvim/xdiff/xemit.h2
-rw-r--r--src/nvim/xdiff/xhistogram.c12
-rw-r--r--src/nvim/xdiff/xinclude.h8
-rw-r--r--src/nvim/xdiff/xmacros.h2
-rw-r--r--src/nvim/xdiff/xpatience.c30
-rw-r--r--src/nvim/xdiff/xprepare.h2
-rw-r--r--src/nvim/xdiff/xtypes.h2
-rw-r--r--src/nvim/xdiff/xutils.c10
-rw-r--r--src/nvim/xdiff/xutils.h2
-rw-r--r--src/tree_sitter/api.h37
-rw-r--r--src/tree_sitter/array.h28
-rw-r--r--src/tree_sitter/atomic.h4
-rw-r--r--src/tree_sitter/bits.h2
-rw-r--r--src/tree_sitter/language.c6
-rw-r--r--src/tree_sitter/language.h14
-rw-r--r--src/tree_sitter/lexer.c13
-rw-r--r--src/tree_sitter/lexer.h2
-rw-r--r--src/tree_sitter/node.c4
-rw-r--r--src/tree_sitter/parser.c75
-rw-r--r--src/tree_sitter/query.c590
-rw-r--r--src/tree_sitter/stack.c2
-rw-r--r--src/tree_sitter/subtree.c13
-rw-r--r--src/tree_sitter/subtree.h4
-rw-r--r--src/tree_sitter/utf16.c33
-rw-r--r--src/tree_sitter/utf16.h21
202 files changed, 24886 insertions, 20530 deletions
diff --git a/src/clint.py b/src/clint.py
index 675b67ccef..8dc41fdb93 100755
--- a/src/clint.py
+++ b/src/clint.py
@@ -270,6 +270,8 @@ _line_length = 80
# This is set by --extensions flag.
_valid_extensions = set(['c', 'h'])
+_RE_COMMENTLINE = re.compile(r'^\s*//')
+
def ParseNolintSuppressions(filename, raw_line, linenum, error):
"""Updates the global list of error-suppressions.
@@ -1358,7 +1360,9 @@ def CheckForOldStyleComments(filename, line, linenum, error):
linenum: The number of the line to check.
error: The function to call with any errors found.
"""
- if line.find('/*') >= 0 and line[-1] != '\\':
+ # hack: allow /* inside comment line. Could be extended to allow them inside
+ # any // comment.
+ if line.find('/*') >= 0 and line[-1] != '\\' and not _RE_COMMENTLINE.match(line):
error(filename, linenum, 'readability/old_style_comment', 5,
'/*-style comment found, it should be replaced with //-style. '
'/*-style comments are only allowed inside macros. '
@@ -1769,7 +1773,7 @@ def CheckSpacingForFunctionCall(filename, line, linenum, error):
fncall = match.group(1)
break
- # Except in if/for/while/switch, there should never be space
+ # Except in if/for/while/switch/case, there should never be space
# immediately inside parens (eg "f( 3, 4 )"). We make an exception
# for nested parens ( (a+b) + c ). Likewise, there should never be
# a space before a ( when it's a function argument. I assume it's a
@@ -1783,7 +1787,7 @@ def CheckSpacingForFunctionCall(filename, line, linenum, error):
# Note that we assume the contents of [] to be short enough that
# they'll never need to wrap.
if ( # Ignore control structures.
- not Search(r'\b(if|for|while|switch|return|sizeof)\b', fncall) and
+ not Search(r'\b(if|for|while|switch|case|return|sizeof)\b', fncall) and
# Ignore pointers/references to functions.
not Search(r' \([^)]+\)\([^)]*(\)|,$)', fncall) and
# Ignore pointers/references to arrays.
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt
index 089dd537e9..982fab173f 100644
--- a/src/nvim/CMakeLists.txt
+++ b/src/nvim/CMakeLists.txt
@@ -23,6 +23,7 @@ endif()
set(TOUCHES_DIR ${PROJECT_BINARY_DIR}/touches)
set(GENERATOR_DIR ${CMAKE_CURRENT_LIST_DIR}/generators)
set(GENERATED_DIR ${PROJECT_BINARY_DIR}/src/nvim/auto)
+set(BINARY_LIB_DIR ${PROJECT_BINARY_DIR}/lib/nvim/)
set(API_DISPATCH_GENERATOR ${GENERATOR_DIR}/gen_api_dispatch.lua)
set(API_UI_EVENTS_GENERATOR ${GENERATOR_DIR}/gen_api_ui_events.lua)
set(GENERATOR_C_GRAMMAR ${GENERATOR_DIR}/c_grammar.lua)
@@ -149,6 +150,8 @@ set(CONV_SOURCES
diff.c
edit.c
eval.c
+ eval/funcs.c
+ eval/userfunc.c
ex_cmds.c
ex_docmd.c
fileio.c
@@ -179,10 +182,10 @@ if(NOT MSVC)
check_c_compiler_flag(-Wstatic-in-inline HAS_WSTATIC_IN_INLINE)
if(HAS_WSTATIC_IN_INLINE)
set_source_files_properties(
- eval.c PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-static-in-inline -Wno-conversion")
+ eval/funcs.c PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-static-in-inline -Wno-conversion")
else()
set_source_files_properties(
- eval.c PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-conversion")
+ eval/funcs.c PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-conversion")
endif()
# tree-sitter: inlined external project, we don't maintain it. #10124
@@ -218,7 +221,7 @@ set(gen_cflags ${gen_cflags} ${C_FLAGS_${build_type}_ARRAY} ${C_FLAGS_ARRAY})
function(get_preproc_output varname iname)
if(MSVC)
- set(${varname} /P /Fi${iname} PARENT_SCOPE)
+ set(${varname} /P /Fi${iname} /nologo PARENT_SCOPE)
else()
set(${varname} -E -o ${iname} PARENT_SCOPE)
endif()
@@ -546,6 +549,17 @@ else()
endif()
set_target_properties(nvim_runtime_deps PROPERTIES FOLDER deps)
+file(MAKE_DIRECTORY ${BINARY_LIB_DIR})
+
+# install treesitter parser if bundled
+if(EXISTS ${DEPS_PREFIX}/lib/nvim/parser)
+ file(COPY ${DEPS_PREFIX}/lib/nvim/parser DESTINATION ${BINARY_LIB_DIR})
+endif()
+
+install(DIRECTORY ${BINARY_LIB_DIR}
+ DESTINATION ${CMAKE_INSTALL_LIBDIR}/nvim/
+ USE_SOURCE_PERMISSIONS)
+
add_library(
libnvim
STATIC
@@ -560,7 +574,6 @@ set_target_properties(
libnvim
PROPERTIES
POSITION_INDEPENDENT_CODE ON
- OUTPUT_NAME nvim
)
set_property(
TARGET libnvim
diff --git a/src/nvim/README.md b/src/nvim/README.md
index 3f7c05757a..d14ba85546 100644
--- a/src/nvim/README.md
+++ b/src/nvim/README.md
@@ -23,14 +23,18 @@ Logs
Low-level log messages sink to `$NVIM_LOG_FILE`.
-Use `LOG_CALLSTACK()` (Linux only) to log the current stacktrace. To log to an
-alternate file (e.g. stderr) use `LOG_CALLSTACK_TO_FILE(FILE*)`.
-
UI events are logged at DEBUG level (`DEBUG_LOG_LEVEL`).
rm -rf build/
make CMAKE_EXTRA_FLAGS="-DMIN_LOG_LEVEL=0"
+Use `LOG_CALLSTACK()` (Linux only) to log the current stacktrace. To log to an
+alternate file (e.g. stderr) use `LOG_CALLSTACK_TO_FILE(FILE*)`. Requires
+`-no-pie` ([ref](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=860394#15)):
+
+ rm -rf build/
+ make CMAKE_EXTRA_FLAGS="-DMIN_LOG_LEVEL=0 -DCMAKE_C_FLAGS=-no-pie"
+
Many log messages have a shared prefix, such as "UI" or "RPC". Use the shell to
filter the log, e.g. at DEBUG level you might want to exclude UI messages:
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index a666ed92da..b345dcaccd 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -1123,7 +1123,7 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id,
}
if (!ns_initialized((uint64_t)ns_id)) {
- api_set_error(err, kErrorTypeValidation, _("Invalid ns_id"));
+ api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
return rv;
}
@@ -1190,7 +1190,7 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start,
}
if (!ns_initialized((uint64_t)ns_id)) {
- api_set_error(err, kErrorTypeValidation, _("Invalid ns_id"));
+ api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
return rv;
}
Integer limit = -1;
@@ -1280,7 +1280,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer id,
}
if (!ns_initialized((uint64_t)ns_id)) {
- api_set_error(err, kErrorTypeValidation, _("Invalid ns_id"));
+ api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
return 0;
}
@@ -1308,7 +1308,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer id,
if (id >= 0) {
id_num = (uint64_t)id;
} else {
- api_set_error(err, kErrorTypeValidation, _("Invalid mark id"));
+ api_set_error(err, kErrorTypeValidation, "Invalid mark id");
return 0;
}
@@ -1337,7 +1337,7 @@ Boolean nvim_buf_del_extmark(Buffer buffer,
return false;
}
if (!ns_initialized((uint64_t)ns_id)) {
- api_set_error(err, kErrorTypeValidation, _("Invalid ns_id"));
+ api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
return false;
}
@@ -1402,7 +1402,7 @@ Integer nvim_buf_add_highlight(Buffer buffer,
uint64_t ns_id = src2ns(&src_id);
- if (!(0 <= line && line < buf->b_ml.ml_line_count)) {
+ if (!(line < buf->b_ml.ml_line_count)) {
// safety check, we can't add marks outside the range
return src_id;
}
@@ -1420,10 +1420,10 @@ Integer nvim_buf_add_highlight(Buffer buffer,
end_line++;
}
- ns_id = extmark_add_decoration(buf, ns_id, hlg_id,
- (int)line, (colnr_T)col_start,
- end_line, (colnr_T)col_end,
- VIRTTEXT_EMPTY);
+ extmark_add_decoration(buf, ns_id, hlg_id,
+ (int)line, (colnr_T)col_start,
+ end_line, (colnr_T)col_end,
+ VIRTTEXT_EMPTY);
return src_id;
}
@@ -1655,7 +1655,7 @@ Integer nvim__buf_add_decoration(Buffer buffer, Integer ns_id, String hl_group,
}
if (!ns_initialized((uint64_t)ns_id)) {
- api_set_error(err, kErrorTypeValidation, _("Invalid ns_id"));
+ api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
return 0;
}
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index a1745ef777..a458762cc6 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -1544,7 +1544,7 @@ bool extmark_get_index_from_obj(buf_T *buf, Integer ns_id, Object obj, int
*col = MAXCOL;
return true;
} else if (id < 0) {
- api_set_error(err, kErrorTypeValidation, _("Mark id must be positive"));
+ api_set_error(err, kErrorTypeValidation, "Mark id must be positive");
return false;
}
@@ -1554,7 +1554,7 @@ bool extmark_get_index_from_obj(buf_T *buf, Integer ns_id, Object obj, int
*col = extmark.col;
return true;
} else {
- api_set_error(err, kErrorTypeValidation, _("No mark with requested id"));
+ api_set_error(err, kErrorTypeValidation, "No mark with requested id");
return false;
}
@@ -1565,7 +1565,7 @@ bool extmark_get_index_from_obj(buf_T *buf, Integer ns_id, Object obj, int
|| pos.items[0].type != kObjectTypeInteger
|| pos.items[1].type != kObjectTypeInteger) {
api_set_error(err, kErrorTypeValidation,
- _("Position must have 2 integer elements"));
+ "Position must have 2 integer elements");
return false;
}
Integer pos_row = pos.items[0].data.integer;
@@ -1575,7 +1575,7 @@ bool extmark_get_index_from_obj(buf_T *buf, Integer ns_id, Object obj, int
return true;
} else {
api_set_error(err, kErrorTypeValidation,
- _("Position must be a mark id Integer or position Array"));
+ "Position must be a mark id Integer or position Array");
return false;
}
}
diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h
index 048b937136..df3a263dcf 100644
--- a/src/nvim/api/private/helpers.h
+++ b/src/nvim/api/private/helpers.h
@@ -63,8 +63,8 @@
#define FIXED_TEMP_ARRAY(name, fixsize) \
Array name = ARRAY_DICT_INIT; \
Object name##__items[fixsize]; \
- args.size = fixsize; \
- args.items = name##__items; \
+ name.size = fixsize; \
+ name.items = name##__items; \
#define STATIC_CSTR_AS_STRING(s) ((String) {.data = s, .size = sizeof(s) - 1})
diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c
index 75ee05761b..717713b948 100644
--- a/src/nvim/api/ui.c
+++ b/src/nvim/api/ui.c
@@ -109,7 +109,12 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height,
UI *ui = xcalloc(1, sizeof(UI));
ui->width = (int)width;
ui->height = (int)height;
- ui->pum_height = 0;
+ ui->pum_nlines = 0;
+ ui->pum_pos = false;
+ ui->pum_width = 0.0;
+ ui->pum_height = 0.0;
+ ui->pum_row = -1.0;
+ ui->pum_col = -1.0;
ui->rgb = true;
ui->override = false;
ui->grid_resize = remote_ui_grid_resize;
@@ -340,7 +345,56 @@ void nvim_ui_pum_set_height(uint64_t channel_id, Integer height, Error *err)
"It must support the ext_popupmenu option");
return;
}
- ui->pum_height = (int)height;
+
+ ui->pum_nlines = (int)height;
+}
+
+/// Tells Nvim the geometry of the popumenu, to align floating windows with an
+/// external popup menu.
+///
+/// Note that this method is not to be confused with |nvim_ui_pum_set_height()|,
+/// which sets the number of visible items in the popup menu, while this
+/// function sets the bounding box of the popup menu, including visual
+/// decorations such as boarders and sliders. Floats need not use the same font
+/// size, nor be anchored to exact grid corners, so one can set floating-point
+/// numbers to the popup menu geometry.
+///
+/// @param channel_id
+/// @param width Popupmenu width.
+/// @param height Popupmenu height.
+/// @param row Popupmenu row.
+/// @param col Popupmenu height.
+/// @param[out] err Error details, if any.
+void nvim_ui_pum_set_bounds(uint64_t channel_id, Float width, Float height,
+ Float row, Float col, Error *err)
+ FUNC_API_SINCE(7) FUNC_API_REMOTE_ONLY
+{
+ if (!pmap_has(uint64_t)(connected_uis, channel_id)) {
+ api_set_error(err, kErrorTypeException,
+ "UI not attached to channel: %" PRId64, channel_id);
+ return;
+ }
+
+ UI *ui = pmap_get(uint64_t)(connected_uis, channel_id);
+ if (!ui->ui_ext[kUIPopupmenu]) {
+ api_set_error(err, kErrorTypeValidation,
+ "UI must support the ext_popupmenu option");
+ return;
+ }
+
+ if (width <= 0) {
+ api_set_error(err, kErrorTypeValidation, "Expected width > 0");
+ return;
+ } else if (height <= 0) {
+ api_set_error(err, kErrorTypeValidation, "Expected height > 0");
+ return;
+ }
+
+ ui->pum_row = (double)row;
+ ui->pum_col = (double)col;
+ ui->pum_width = (double)width;
+ ui->pum_height = (double)height;
+ ui->pum_pos = true;
}
/// Pushes data into UI.UIData, to be consumed later by remote_ui_flush().
diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h
index 6677e248cf..ab31db39e9 100644
--- a/src/nvim/api/ui_events.in.h
+++ b/src/nvim/api/ui_events.in.h
@@ -115,6 +115,10 @@ void win_close(Integer grid)
void msg_set_pos(Integer grid, Integer row, Boolean scrolled, String sep_char)
FUNC_API_SINCE(6) FUNC_API_BRIDGE_IMPL FUNC_API_COMPOSITOR_IMPL;
+void win_viewport(Integer grid, Window win, Integer topline,
+ Integer botline, Integer curline, Integer curcol)
+ FUNC_API_SINCE(7) FUNC_API_REMOTE_ONLY;
+
void popupmenu_show(Array items, Integer selected,
Integer row, Integer col, Integer grid)
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 9c58ce853b..087ab37296 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -36,6 +36,7 @@
#include "nvim/edit.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
+#include "nvim/eval/userfunc.h"
#include "nvim/fileio.h"
#include "nvim/ops.h"
#include "nvim/option.h"
@@ -253,7 +254,7 @@ void nvim_feedkeys(String keys, String mode, Boolean escape_csi)
if (execute) {
int save_msg_scroll = msg_scroll;
- /* Avoid a 1 second delay when the keys start Insert mode. */
+ // Avoid a 1 second delay when the keys start Insert mode.
msg_scroll = false;
if (!dangerous) {
ex_normal_busy++;
@@ -703,6 +704,40 @@ ArrayOf(String) nvim_list_runtime_paths(void)
return rv;
}
+/// Find files in runtime directories
+///
+/// 'name' can contain wildcards. For example
+/// nvim_get_runtime_file("colors/*.vim", true) will return all color
+/// scheme files.
+///
+/// It is not an error to not find any files. An empty array is returned then.
+///
+/// @param name pattern of files to search for
+/// @param all whether to return all matches or only the first
+/// @return list of absolute paths to the found files
+ArrayOf(String) nvim_get_runtime_file(String name, Boolean all)
+ FUNC_API_SINCE(7)
+{
+ Array rv = ARRAY_DICT_INIT;
+ if (!name.data) {
+ return rv;
+ }
+ int flags = DIP_START | (all ? DIP_ALL : 0);
+ do_in_runtimepath((char_u *)name.data, flags, find_runtime_cb, &rv);
+ return rv;
+}
+
+static void find_runtime_cb(char_u *fname, void *cookie)
+{
+ Array *rv = (Array *)cookie;
+ ADD(*rv, STRING_OBJ(cstr_to_string((char *)fname)));
+}
+
+String nvim__get_lib_dir(void)
+{
+ return cstr_as_string(get_lib_dir());
+}
+
/// Changes the global working directory.
///
/// @param dir Directory path
@@ -2564,7 +2599,8 @@ Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Error *err)
/// interface should probably be derived from a reformed
/// bufhl/virttext interface with full support for multi-line
/// ranges etc
-void nvim__put_attr(Integer id, Integer c0, Integer c1)
+void nvim__put_attr(Integer id, Integer start_row, Integer start_col,
+ Integer end_row, Integer end_col)
FUNC_API_LUA_ONLY
{
if (!lua_attr_active) {
@@ -2574,10 +2610,9 @@ void nvim__put_attr(Integer id, Integer c0, Integer c1)
return;
}
int attr = syn_id2attr((int)id);
- c0 = MAX(c0, 0);
- c1 = MIN(c1, (Integer)lua_attr_bufsize);
- for (Integer c = c0; c < c1; c++) {
- lua_attr_buf[c] = (sattr_T)hl_combine_attr(lua_attr_buf[c], (int)attr);
+ if (attr == 0) {
+ return;
}
- return;
+ decorations_add_luahl_attr(attr, (int)start_row, (colnr_T)start_col,
+ (int)end_row, (colnr_T)end_col);
}
diff --git a/src/nvim/ascii.h b/src/nvim/ascii.h
index ff6840d690..31423e79af 100644
--- a/src/nvim/ascii.h
+++ b/src/nvim/ascii.h
@@ -23,26 +23,28 @@
#define NL '\012'
#define NL_STR "\012"
#define FF '\014'
-#define CAR '\015' /* CR is used by Mac OS X */
+#define CAR '\015' // CR is used by Mac OS X
#define ESC '\033'
#define ESC_STR "\033"
#define DEL 0x7f
#define DEL_STR "\177"
#define CSI 0x9b // Control Sequence Introducer
#define CSI_STR "\233"
-#define DCS 0x90 /* Device Control String */
-#define STERM 0x9c /* String Terminator */
+#define DCS 0x90 // Device Control String
+#define DCS_STR "\033P"
+#define STERM 0x9c // String Terminator
+#define STERM_STR "\033\\"
#define POUND 0xA3
-#define Ctrl_chr(x) (TOUPPER_ASC(x) ^ 0x40) /* '?' -> DEL, '@' -> ^@, etc. */
+#define Ctrl_chr(x) (TOUPPER_ASC(x) ^ 0x40) // '?' -> DEL, '@' -> ^@, etc.
#define Meta(x) ((x) | 0x80)
#define CTRL_F_STR "\006"
#define CTRL_H_STR "\010"
#define CTRL_V_STR "\026"
-#define Ctrl_AT 0 /* @ */
+#define Ctrl_AT 0 // @
#define Ctrl_A 1
#define Ctrl_B 2
#define Ctrl_C 3
@@ -69,16 +71,14 @@
#define Ctrl_X 24
#define Ctrl_Y 25
#define Ctrl_Z 26
-/* CTRL- [ Left Square Bracket == ESC*/
-#define Ctrl_BSL 28 /* \ BackSLash */
-#define Ctrl_RSB 29 /* ] Right Square Bracket */
-#define Ctrl_HAT 30 /* ^ */
+// CTRL- [ Left Square Bracket == ESC
+#define Ctrl_BSL 28 // \ BackSLash
+#define Ctrl_RSB 29 // ] Right Square Bracket
+#define Ctrl_HAT 30 // ^
#define Ctrl__ 31
-/*
- * Character that separates dir names in a path.
- */
+// Character that separates dir names in a path.
#ifdef BACKSLASH_IN_FILENAME
# define PATHSEP psepc
# define PATHSEPSTR pseps
@@ -168,4 +168,4 @@ static inline bool ascii_isspace(int c)
return (c >= 9 && c <= 13) || c == ' ';
}
-#endif /* NVIM_ASCII_H */
+#endif // NVIM_ASCII_H
diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua
index 96e170a9e1..4391d997a7 100644
--- a/src/nvim/auevents.lua
+++ b/src/nvim/auevents.lua
@@ -31,6 +31,7 @@ return {
'ColorSchemePre', -- before loading a colorscheme
'CompleteChanged', -- after popup menu changed
'CompleteDone', -- after finishing insert complete
+ 'CompleteDonePre', -- idem, before clearing info
'CursorHold', -- cursor in same position for a while
'CursorHoldI', -- idem, in Insert mode
'CursorMoved', -- cursor was moved
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index 5083780719..7c8f93163a 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -763,6 +763,9 @@ static void free_buffer(buf_T *buf)
unref_var_dict(buf->b_vars);
aubuflocal_remove(buf);
tv_dict_unref(buf->additional_data);
+ xfree(buf->b_prompt_text);
+ callback_free(&buf->b_prompt_callback);
+ callback_free(&buf->b_prompt_interrupt);
clear_fmark(&buf->b_last_cursor);
clear_fmark(&buf->b_last_insert);
clear_fmark(&buf->b_last_change);
@@ -1615,6 +1618,7 @@ void enter_buffer(buf_T *buf)
if (!curbuf->b_help && curwin->w_p_spell && *curwin->w_s->b_p_spl != NUL) {
(void)did_set_spelllang(curwin);
}
+ curbuf->b_last_used = time(NULL);
redraw_later(NOT_VALID);
}
@@ -1747,6 +1751,7 @@ buf_T * buflist_new(char_u *ffname, char_u *sfname, linenr_T lnum, int flags)
apply_autocmds(EVENT_BUFWIPEOUT, NULL, NULL, false, curbuf);
}
if (aborting()) { // autocmds may abort script processing
+ xfree(ffname);
return NULL;
}
if (buf == curbuf) {
@@ -1876,6 +1881,10 @@ buf_T * buflist_new(char_u *ffname, char_u *sfname, linenr_T lnum, int flags)
}
}
+ buf->b_prompt_callback.type = kCallbackNone;
+ buf->b_prompt_interrupt.type = kCallbackNone;
+ buf->b_prompt_text = NULL;
+
return buf;
}
@@ -2242,6 +2251,23 @@ int buflist_findpat(
return match;
}
+typedef struct {
+ buf_T *buf;
+ char_u *match;
+} bufmatch_T;
+
+/// Compare functions for qsort() below, that compares b_last_used.
+static int
+buf_time_compare(const void *s1, const void *s2)
+{
+ buf_T *buf1 = *(buf_T **)s1;
+ buf_T *buf2 = *(buf_T **)s2;
+
+ if (buf1->b_last_used == buf2->b_last_used) {
+ return 0;
+ }
+ return buf1->b_last_used > buf2->b_last_used ? -1 : 1;
+}
/*
* Find all buffer names that match.
@@ -2255,6 +2281,7 @@ int ExpandBufnames(char_u *pat, int *num_file, char_u ***file, int options)
char_u *p;
int attempt;
char_u *patc;
+ bufmatch_T *matches = NULL;
*num_file = 0; // return values in case of FAIL
*file = NULL;
@@ -2305,7 +2332,13 @@ int ExpandBufnames(char_u *pat, int *num_file, char_u ***file, int options)
} else {
p = vim_strsave(p);
}
- (*file)[count++] = p;
+ if (matches != NULL) {
+ matches[count].buf = buf;
+ matches[count].match = p;
+ count++;
+ } else {
+ (*file)[count++] = p;
+ }
}
}
}
@@ -2314,6 +2347,10 @@ int ExpandBufnames(char_u *pat, int *num_file, char_u ***file, int options)
}
if (round == 1) {
*file = xmalloc((size_t)count * sizeof(**file));
+
+ if (options & WILD_BUFLASTUSED) {
+ matches = xmalloc((size_t)count * sizeof(*matches));
+ }
}
}
vim_regfree(regmatch.regprog);
@@ -2326,6 +2363,25 @@ int ExpandBufnames(char_u *pat, int *num_file, char_u ***file, int options)
xfree(patc);
}
+ if (matches != NULL) {
+ if (count > 1) {
+ qsort(matches, (size_t)count, sizeof(bufmatch_T), buf_time_compare);
+ }
+
+ // if the current buffer is first in the list, place it at the end
+ if (matches[0].buf == curbuf) {
+ for (int i = 1; i < count; i++) {
+ (*file)[i-1] = matches[i].match;
+ }
+ (*file)[count-1] = matches[0].match;
+ } else {
+ for (int i = 0; i < count; i++) {
+ (*file)[i] = matches[i].match;
+ }
+ }
+ xfree(matches);
+ }
+
*num_file = count;
return count == 0 ? FAIL : OK;
}
@@ -2586,11 +2642,35 @@ linenr_T buflist_findlnum(buf_T *buf)
// List all known file names (for :files and :buffers command).
void buflist_list(exarg_T *eap)
{
- buf_T *buf;
+ buf_T *buf = firstbuf;
int len;
int i;
- for (buf = firstbuf; buf != NULL && !got_int; buf = buf->b_next) {
+ garray_T buflist;
+ buf_T **buflist_data = NULL, **p;
+
+ if (vim_strchr(eap->arg, 't')) {
+ ga_init(&buflist, sizeof(buf_T *), 50);
+ for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
+ ga_grow(&buflist, 1);
+ ((buf_T **)buflist.ga_data)[buflist.ga_len++] = buf;
+ }
+
+ qsort(buflist.ga_data, (size_t)buflist.ga_len,
+ sizeof(buf_T *), buf_time_compare);
+
+ p = buflist_data = (buf_T **)buflist.ga_data;
+ buf = *p;
+ }
+
+ for (;
+ buf != NULL && !got_int;
+ buf = buflist_data
+ ? (++p < buflist_data + buflist.ga_len ? *p : NULL)
+ : buf->b_next) {
+ const bool is_terminal = buf->terminal;
+ const bool job_running = buf->terminal && terminal_running(buf->terminal);
+
// skip unspecified buffers
if ((!buf->b_p_bl && !eap->forceit && !strchr((char *)eap->arg, 'u'))
|| (strchr((char *)eap->arg, 'u') && buf->b_p_bl)
@@ -2600,6 +2680,8 @@ void buflist_list(exarg_T *eap)
&& (buf->b_ml.ml_mfp == NULL || buf->b_nwindows == 0))
|| (strchr((char *)eap->arg, 'h')
&& (buf->b_ml.ml_mfp == NULL || buf->b_nwindows != 0))
+ || (strchr((char *)eap->arg, 'R') && (!is_terminal || !job_running))
+ || (strchr((char *)eap->arg, 'F') && (!is_terminal || job_running))
|| (strchr((char *)eap->arg, '-') && buf->b_p_ma)
|| (strchr((char *)eap->arg, '=') && !buf->b_p_ro)
|| (strchr((char *)eap->arg, 'x') && !(buf->b_flags & BF_READERR))
@@ -2646,13 +2728,22 @@ void buflist_list(exarg_T *eap)
do {
IObuff[len++] = ' ';
} while (--i > 0 && len < IOSIZE - 18);
- vim_snprintf((char *)IObuff + len, (size_t)(IOSIZE - len),
- _("line %" PRId64),
- buf == curbuf ? (int64_t)curwin->w_cursor.lnum
- : (int64_t)buflist_findlnum(buf));
+ if (vim_strchr(eap->arg, 't') && buf->b_last_used) {
+ add_time(IObuff + len, (size_t)(IOSIZE - len), buf->b_last_used);
+ } else {
+ vim_snprintf((char *)IObuff + len, (size_t)(IOSIZE - len),
+ _("line %" PRId64),
+ buf == curbuf ? (int64_t)curwin->w_cursor.lnum
+ : (int64_t)buflist_findlnum(buf));
+ }
+
msg_outtrans(IObuff);
line_breakcheck();
}
+
+ if (buflist_data) {
+ ga_clear(&buflist);
+ }
}
/*
@@ -3390,14 +3481,27 @@ int build_stl_str_hl(
fillchar = '-';
}
+ // The cursor in windows other than the current one isn't always
+ // up-to-date, esp. because of autocommands and timers.
+ linenr_T lnum = wp->w_cursor.lnum;
+ if (lnum > wp->w_buffer->b_ml.ml_line_count) {
+ lnum = wp->w_buffer->b_ml.ml_line_count;
+ wp->w_cursor.lnum = lnum;
+ }
+
// Get line & check if empty (cursorpos will show "0-1").
- char_u *line_ptr = ml_get_buf(wp->w_buffer, wp->w_cursor.lnum, false);
+ const char_u *line_ptr = ml_get_buf(wp->w_buffer, lnum, false);
bool empty_line = (*line_ptr == NUL);
// Get the byte value now, in case we need it below. This is more
// efficient than making a copy of the line.
int byteval;
- if (wp->w_cursor.col > (colnr_T)STRLEN(line_ptr)) {
+ const size_t len = STRLEN(line_ptr);
+ if (wp->w_cursor.col > (colnr_T)len) {
+ // Line may have changed since checking the cursor column, or the lnum
+ // was adjusted above.
+ wp->w_cursor.col = (colnr_T)len;
+ wp->w_cursor.coladd = 0;
byteval = 0;
} else {
byteval = utf_ptr2char(line_ptr + wp->w_cursor.col);
@@ -3531,6 +3635,12 @@ int build_stl_str_hl(
if (n == curitem && group_start_userhl == group_end_userhl) {
out_p = t;
group_len = 0;
+ // do not use the highlighting from the removed group
+ for (n = groupitems[groupdepth] + 1; n < curitem; n++) {
+ if (items[n].type == Highlight) {
+ items[n].type = Empty;
+ }
+ }
}
}
@@ -4818,6 +4928,12 @@ do_arg_all(
xfree(opened);
}
+// Return TRUE if "buf" is a prompt buffer.
+int bt_prompt(buf_T *buf)
+{
+ return buf != NULL && buf->b_p_bt[0] == 'p';
+}
+
/*
* Open a window for a number of buffers.
*/
@@ -5212,14 +5328,18 @@ bool bt_nofile(const buf_T *const buf)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
return buf != NULL && ((buf->b_p_bt[0] == 'n' && buf->b_p_bt[2] == 'f')
- || buf->b_p_bt[0] == 'a' || buf->terminal);
+ || buf->b_p_bt[0] == 'a'
+ || buf->terminal
+ || buf->b_p_bt[0] == 'p');
}
// Return true if "buf" is a "nowrite", "nofile" or "terminal" buffer.
bool bt_dontwrite(const buf_T *const buf)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
- return buf != NULL && (buf->b_p_bt[0] == 'n' || buf->terminal);
+ return buf != NULL && (buf->b_p_bt[0] == 'n'
+ || buf->terminal
+ || buf->b_p_bt[0] == 'p');
}
bool bt_dontwrite_msg(const buf_T *const buf)
@@ -5272,6 +5392,9 @@ char_u *buf_spname(buf_T *buf)
if (buf->b_fname != NULL) {
return buf->b_fname;
}
+ if (bt_prompt(buf)) {
+ return (char_u *)_("[Prompt]");
+ }
return (char_u *)_("[Scratch]");
}
if (buf->b_fname == NULL) {
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index a0379740b6..d696eedbb7 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -67,14 +67,14 @@ typedef struct {
* off off w_botline not valid
* on off not possible
*/
-#define VALID_WROW 0x01 /* w_wrow (window row) is valid */
-#define VALID_WCOL 0x02 /* w_wcol (window col) is valid */
-#define VALID_VIRTCOL 0x04 /* w_virtcol (file col) is valid */
-#define VALID_CHEIGHT 0x08 /* w_cline_height and w_cline_folded valid */
-#define VALID_CROW 0x10 /* w_cline_row is valid */
-#define VALID_BOTLINE 0x20 /* w_botine and w_empty_rows are valid */
-#define VALID_BOTLINE_AP 0x40 /* w_botine is approximated */
-#define VALID_TOPLINE 0x80 /* w_topline is valid (for cursor position) */
+#define VALID_WROW 0x01 // w_wrow (window row) is valid
+#define VALID_WCOL 0x02 // w_wcol (window col) is valid
+#define VALID_VIRTCOL 0x04 // w_virtcol (file col) is valid
+#define VALID_CHEIGHT 0x08 // w_cline_height and w_cline_folded valid
+#define VALID_CROW 0x10 // w_cline_row is valid
+#define VALID_BOTLINE 0x20 // w_botine and w_empty_rows are valid
+#define VALID_BOTLINE_AP 0x40 // w_botine is approximated
+#define VALID_TOPLINE 0x80 // w_topline is valid (for cursor position)
// flags for b_flags
#define BF_RECOVERED 0x01 // buffer has been recovered
@@ -92,7 +92,7 @@ typedef struct {
#define BF_DUMMY 0x80 // dummy buffer, only used internally
#define BF_PRESERVED 0x100 // ":preserve" was used
-/* Mask to check for flags that prevent normal writing */
+// Mask to check for flags that prevent normal writing
#define BF_WRITE_MASK (BF_NOTEDITED + BF_NEW + BF_READERR)
typedef struct window_S win_T;
@@ -160,90 +160,92 @@ typedef struct
*/
typedef struct {
int wo_arab;
-# define w_p_arab w_onebuf_opt.wo_arab /* 'arabic' */
+# define w_p_arab w_onebuf_opt.wo_arab // 'arabic'
int wo_bri;
# define w_p_bri w_onebuf_opt.wo_bri // 'breakindent'
char_u *wo_briopt;
-# define w_p_briopt w_onebuf_opt.wo_briopt /* 'breakindentopt' */
+# define w_p_briopt w_onebuf_opt.wo_briopt // 'breakindentopt'
int wo_diff;
-# define w_p_diff w_onebuf_opt.wo_diff /* 'diff' */
- long wo_fdc;
-# define w_p_fdc w_onebuf_opt.wo_fdc /* 'foldcolumn' */
- int wo_fdc_save;
-# define w_p_fdc_save w_onebuf_opt.wo_fdc_save /* 'foldenable' saved for diff mode */
+# define w_p_diff w_onebuf_opt.wo_diff // 'diff'
+ char_u *wo_fdc;
+# define w_p_fdc w_onebuf_opt.wo_fdc // 'foldcolumn'
+ char_u *wo_fdc_save;
+# define w_p_fdc_save w_onebuf_opt.wo_fdc_save // 'fdc' saved for diff mode
int wo_fen;
-# define w_p_fen w_onebuf_opt.wo_fen /* 'foldenable' */
+# define w_p_fen w_onebuf_opt.wo_fen // 'foldenable'
int wo_fen_save;
-# define w_p_fen_save w_onebuf_opt.wo_fen_save /* 'foldenable' saved for diff mode */
+ // 'foldenable' saved for diff mode
+# define w_p_fen_save w_onebuf_opt.wo_fen_save
char_u *wo_fdi;
-# define w_p_fdi w_onebuf_opt.wo_fdi /* 'foldignore' */
+# define w_p_fdi w_onebuf_opt.wo_fdi // 'foldignore'
long wo_fdl;
-# define w_p_fdl w_onebuf_opt.wo_fdl /* 'foldlevel' */
+# define w_p_fdl w_onebuf_opt.wo_fdl // 'foldlevel'
int wo_fdl_save;
-# define w_p_fdl_save w_onebuf_opt.wo_fdl_save /* 'foldlevel' state saved for diff mode */
+ // 'foldlevel' state saved for diff mode
+# define w_p_fdl_save w_onebuf_opt.wo_fdl_save
char_u *wo_fdm;
-# define w_p_fdm w_onebuf_opt.wo_fdm /* 'foldmethod' */
+# define w_p_fdm w_onebuf_opt.wo_fdm // 'foldmethod'
char_u *wo_fdm_save;
-# define w_p_fdm_save w_onebuf_opt.wo_fdm_save /* 'fdm' saved for diff mode */
+# define w_p_fdm_save w_onebuf_opt.wo_fdm_save // 'fdm' saved for diff mode
long wo_fml;
-# define w_p_fml w_onebuf_opt.wo_fml /* 'foldminlines' */
+# define w_p_fml w_onebuf_opt.wo_fml // 'foldminlines'
long wo_fdn;
-# define w_p_fdn w_onebuf_opt.wo_fdn /* 'foldnestmax' */
+# define w_p_fdn w_onebuf_opt.wo_fdn // 'foldnestmax'
char_u *wo_fde;
-# define w_p_fde w_onebuf_opt.wo_fde /* 'foldexpr' */
+# define w_p_fde w_onebuf_opt.wo_fde // 'foldexpr'
char_u *wo_fdt;
-# define w_p_fdt w_onebuf_opt.wo_fdt /* 'foldtext' */
+# define w_p_fdt w_onebuf_opt.wo_fdt // 'foldtext'
char_u *wo_fmr;
-# define w_p_fmr w_onebuf_opt.wo_fmr /* 'foldmarker' */
+# define w_p_fmr w_onebuf_opt.wo_fmr // 'foldmarker'
int wo_lbr;
-# define w_p_lbr w_onebuf_opt.wo_lbr /* 'linebreak' */
+# define w_p_lbr w_onebuf_opt.wo_lbr // 'linebreak'
int wo_list;
-#define w_p_list w_onebuf_opt.wo_list /* 'list' */
+#define w_p_list w_onebuf_opt.wo_list // 'list'
int wo_nu;
-#define w_p_nu w_onebuf_opt.wo_nu /* 'number' */
+#define w_p_nu w_onebuf_opt.wo_nu // 'number'
int wo_rnu;
-#define w_p_rnu w_onebuf_opt.wo_rnu /* 'relativenumber' */
+#define w_p_rnu w_onebuf_opt.wo_rnu // 'relativenumber'
long wo_nuw;
-# define w_p_nuw w_onebuf_opt.wo_nuw /* 'numberwidth' */
+# define w_p_nuw w_onebuf_opt.wo_nuw // 'numberwidth'
int wo_wfh;
-# define w_p_wfh w_onebuf_opt.wo_wfh /* 'winfixheight' */
+# define w_p_wfh w_onebuf_opt.wo_wfh // 'winfixheight'
int wo_wfw;
-# define w_p_wfw w_onebuf_opt.wo_wfw /* 'winfixwidth' */
+# define w_p_wfw w_onebuf_opt.wo_wfw // 'winfixwidth'
int wo_pvw;
-# define w_p_pvw w_onebuf_opt.wo_pvw /* 'previewwindow' */
+# define w_p_pvw w_onebuf_opt.wo_pvw // 'previewwindow'
int wo_rl;
-# define w_p_rl w_onebuf_opt.wo_rl /* 'rightleft' */
+# define w_p_rl w_onebuf_opt.wo_rl // 'rightleft'
char_u *wo_rlc;
-# define w_p_rlc w_onebuf_opt.wo_rlc /* 'rightleftcmd' */
+# define w_p_rlc w_onebuf_opt.wo_rlc // 'rightleftcmd'
long wo_scr;
-#define w_p_scr w_onebuf_opt.wo_scr /* 'scroll' */
+#define w_p_scr w_onebuf_opt.wo_scr // 'scroll'
int wo_spell;
-# define w_p_spell w_onebuf_opt.wo_spell /* 'spell' */
+# define w_p_spell w_onebuf_opt.wo_spell // 'spell'
int wo_cuc;
-# define w_p_cuc w_onebuf_opt.wo_cuc /* 'cursorcolumn' */
+# define w_p_cuc w_onebuf_opt.wo_cuc // 'cursorcolumn'
int wo_cul;
-# define w_p_cul w_onebuf_opt.wo_cul /* 'cursorline' */
+# define w_p_cul w_onebuf_opt.wo_cul // 'cursorline'
char_u *wo_cc;
-# define w_p_cc w_onebuf_opt.wo_cc /* 'colorcolumn' */
+# define w_p_cc w_onebuf_opt.wo_cc // 'colorcolumn'
char_u *wo_stl;
-#define w_p_stl w_onebuf_opt.wo_stl /* 'statusline' */
+#define w_p_stl w_onebuf_opt.wo_stl // 'statusline'
int wo_scb;
-# define w_p_scb w_onebuf_opt.wo_scb /* 'scrollbind' */
- int wo_diff_saved; /* options were saved for starting diff mode */
+# define w_p_scb w_onebuf_opt.wo_scb // 'scrollbind'
+ int wo_diff_saved; // options were saved for starting diff mode
# define w_p_diff_saved w_onebuf_opt.wo_diff_saved
- int wo_scb_save; /* 'scrollbind' saved for diff mode*/
+ int wo_scb_save; // 'scrollbind' saved for diff mode
# define w_p_scb_save w_onebuf_opt.wo_scb_save
int wo_wrap;
-#define w_p_wrap w_onebuf_opt.wo_wrap /* 'wrap' */
- int wo_wrap_save; /* 'wrap' state saved for diff mode*/
+#define w_p_wrap w_onebuf_opt.wo_wrap // 'wrap'
+ int wo_wrap_save; // 'wrap' state saved for diff mode
# define w_p_wrap_save w_onebuf_opt.wo_wrap_save
- char_u *wo_cocu; /* 'concealcursor' */
+ char_u *wo_cocu; // 'concealcursor'
# define w_p_cocu w_onebuf_opt.wo_cocu
- long wo_cole; /* 'conceallevel' */
+ long wo_cole; // 'conceallevel'
# define w_p_cole w_onebuf_opt.wo_cole
int wo_crb;
-# define w_p_crb w_onebuf_opt.wo_crb /* 'cursorbind' */
- int wo_crb_save; /* 'cursorbind' state saved for diff mode*/
+# define w_p_crb w_onebuf_opt.wo_crb // 'cursorbind'
+ int wo_crb_save; // 'cursorbind' state saved for diff mode
# define w_p_crb_save w_onebuf_opt.wo_crb_save
char_u *wo_scl;
# define w_p_scl w_onebuf_opt.wo_scl // 'signcolumn'
@@ -271,14 +273,14 @@ typedef struct {
* most-recently-used order.
*/
struct wininfo_S {
- wininfo_T *wi_next; /* next entry or NULL for last entry */
- wininfo_T *wi_prev; /* previous entry or NULL for first entry */
- win_T *wi_win; /* pointer to window that did set wi_fpos */
- pos_T wi_fpos; /* last cursor position in the file */
- bool wi_optset; /* true when wi_opt has useful values */
- winopt_T wi_opt; /* local window options */
- bool wi_fold_manual; /* copy of w_fold_manual */
- garray_T wi_folds; /* clone of w_folds */
+ wininfo_T *wi_next; // next entry or NULL for last entry
+ wininfo_T *wi_prev; // previous entry or NULL for first entry
+ win_T *wi_win; // pointer to window that did set wi_fpos
+ pos_T wi_fpos; // last cursor position in the file
+ bool wi_optset; // true when wi_opt has useful values
+ winopt_T wi_opt; // local window options
+ bool wi_fold_manual; // copy of w_fold_manual
+ garray_T wi_folds; // clone of w_folds
};
/*
@@ -288,8 +290,8 @@ struct wininfo_S {
* TODO: move struct arglist to another header
*/
typedef struct arglist {
- garray_T al_ga; /* growarray with the array of file names */
- int al_refcount; /* number of windows using this arglist */
+ garray_T al_ga; // growarray with the array of file names
+ int al_refcount; // number of windows using this arglist
int id; ///< id of this arglist
} alist_T;
@@ -301,8 +303,8 @@ typedef struct arglist {
* TODO: move aentry_T to another header
*/
typedef struct argentry {
- char_u *ae_fname; /* file name as specified */
- int ae_fnum; /* buffer number with expanded file name */
+ char_u *ae_fname; // file name as specified
+ int ae_fnum; // buffer number with expanded file name
} aentry_T;
# define ALIST(win) (win)->w_alist
@@ -318,21 +320,21 @@ typedef struct argentry {
* Used for the typeahead buffer: typebuf.
*/
typedef struct {
- char_u *tb_buf; /* buffer for typed characters */
- char_u *tb_noremap; /* mapping flags for characters in tb_buf[] */
- int tb_buflen; /* size of tb_buf[] */
- int tb_off; /* current position in tb_buf[] */
- int tb_len; /* number of valid bytes in tb_buf[] */
- int tb_maplen; /* nr of mapped bytes in tb_buf[] */
- int tb_silent; /* nr of silently mapped bytes in tb_buf[] */
- int tb_no_abbr_cnt; /* nr of bytes without abbrev. in tb_buf[] */
- int tb_change_cnt; /* nr of time tb_buf was changed; never zero */
+ char_u *tb_buf; // buffer for typed characters
+ char_u *tb_noremap; // mapping flags for characters in tb_buf[]
+ int tb_buflen; // size of tb_buf[]
+ int tb_off; // current position in tb_buf[]
+ int tb_len; // number of valid bytes in tb_buf[]
+ int tb_maplen; // nr of mapped bytes in tb_buf[]
+ int tb_silent; // nr of silently mapped bytes in tb_buf[]
+ int tb_no_abbr_cnt; // nr of bytes without abbrev. in tb_buf[]
+ int tb_change_cnt; // nr of time tb_buf was changed; never zero
} typebuf_T;
-/* Struct to hold the saved typeahead for save_typeahead(). */
+// Struct to hold the saved typeahead for save_typeahead().
typedef struct {
typebuf_T save_typebuf;
- int typebuf_valid; /* TRUE when save_typebuf valid */
+ bool typebuf_valid; // true when save_typebuf valid
int old_char;
int old_mod_mask;
buffheader_T save_readbuf1;
@@ -363,15 +365,15 @@ struct mapblock {
*/
struct stl_hlrec {
char_u *start;
- int userhl; /* 0: no HL, 1-9: User HL, < 0 for syn ID */
+ int userhl; // 0: no HL, 1-9: User HL, < 0 for syn ID
};
-/* values for b_syn_spell: what to do with toplevel text */
-#define SYNSPL_DEFAULT 0 /* spell check if @Spell not defined */
-#define SYNSPL_TOP 1 /* spell check toplevel text */
-#define SYNSPL_NOTOP 2 /* don't spell check toplevel text */
+// values for b_syn_spell: what to do with toplevel text
+#define SYNSPL_DEFAULT 0 // spell check if @Spell not defined
+#define SYNSPL_TOP 1 // spell check toplevel text
+#define SYNSPL_NOTOP 2 // don't spell check toplevel text
-/* avoid #ifdefs for when b_spell is not available */
+// avoid #ifdefs for when b_spell is not available
# define B_SPELL(buf) ((buf)->b_spell)
typedef struct qf_info_S qf_info_T;
@@ -380,10 +382,10 @@ typedef struct qf_info_S qf_info_T;
* Used for :syntime: timing of executing a syntax pattern.
*/
typedef struct {
- proftime_T total; /* total time used */
- proftime_T slowest; /* time of slowest call */
- long count; /* nr of times used */
- long match; /* nr of times matched */
+ proftime_T total; // total time used
+ proftime_T slowest; // time of slowest call
+ long count; // nr of times used
+ long match; // nr of times matched
} syn_time_T;
/*
@@ -411,25 +413,23 @@ typedef struct {
char_u *b_syn_linecont_pat; // line continuation pattern
regprog_T *b_syn_linecont_prog; // line continuation program
syn_time_T b_syn_linecont_time;
- int b_syn_linecont_ic; /* ignore-case flag for above */
- int b_syn_topgrp; /* for ":syntax include" */
- int b_syn_conceal; /* auto-conceal for :syn cmds */
- int b_syn_folditems; /* number of patterns with the HL_FOLD
- flag set */
- /*
- * b_sst_array[] contains the state stack for a number of lines, for the
- * start of that line (col == 0). This avoids having to recompute the
- * syntax state too often.
- * b_sst_array[] is allocated to hold the state for all displayed lines,
- * and states for 1 out of about 20 other lines.
- * b_sst_array pointer to an array of synstate_T
- * b_sst_len number of entries in b_sst_array[]
- * b_sst_first pointer to first used entry in b_sst_array[] or NULL
- * b_sst_firstfree pointer to first free entry in b_sst_array[] or NULL
- * b_sst_freecount number of free entries in b_sst_array[]
- * b_sst_check_lnum entries after this lnum need to be checked for
- * validity (MAXLNUM means no check needed)
- */
+ int b_syn_linecont_ic; // ignore-case flag for above
+ int b_syn_topgrp; // for ":syntax include"
+ int b_syn_conceal; // auto-conceal for :syn cmds
+ int b_syn_folditems; // number of patterns with the HL_FOLD
+ // flag set
+ // b_sst_array[] contains the state stack for a number of lines, for the
+ // start of that line (col == 0). This avoids having to recompute the
+ // syntax state too often.
+ // b_sst_array[] is allocated to hold the state for all displayed lines,
+ // and states for 1 out of about 20 other lines.
+ // b_sst_array pointer to an array of synstate_T
+ // b_sst_len number of entries in b_sst_array[]
+ // b_sst_first pointer to first used entry in b_sst_array[] or NULL
+ // b_sst_firstfree pointer to first free entry in b_sst_array[] or NULL
+ // b_sst_freecount number of free entries in b_sst_array[]
+ // b_sst_check_lnum entries after this lnum need to be checked for
+ // validity (MAXLNUM means no check needed)
synstate_T *b_sst_array;
int b_sst_len;
synstate_T *b_sst_first;
@@ -488,10 +488,10 @@ struct file_buffer {
memline_T b_ml; // associated memline (also contains line count
- buf_T *b_next; /* links in list of buffers */
+ buf_T *b_next; // links in list of buffers
buf_T *b_prev;
- int b_nwindows; /* nr of windows open on this buffer */
+ int b_nwindows; // nr of windows open on this buffer
int b_flags; // various BF_ flags
int b_locked; // Buffer is being closed or referenced, don't
@@ -532,24 +532,25 @@ struct file_buffer {
*/
bool b_mod_set; /* true when there are changes since the last
time the display was updated */
- linenr_T b_mod_top; /* topmost lnum that was changed */
- linenr_T b_mod_bot; /* lnum below last changed line, AFTER the
- change */
- long b_mod_xlines; /* number of extra buffer lines inserted;
- negative when lines were deleted */
-
- wininfo_T *b_wininfo; /* list of last used info for each window */
-
- long b_mtime; /* last change time of original file */
- long b_mtime_read; /* last change time when reading */
- uint64_t b_orig_size; /* size of original file in bytes */
- int b_orig_mode; /* mode of original file */
-
- fmark_T b_namedm[NMARKS]; /* current named marks (mark.c) */
-
- /* These variables are set when VIsual_active becomes FALSE */
+ linenr_T b_mod_top; // topmost lnum that was changed
+ linenr_T b_mod_bot; // lnum below last changed line, AFTER the
+ // change
+ long b_mod_xlines; // number of extra buffer lines inserted;
+ // negative when lines were deleted
+ wininfo_T *b_wininfo; // list of last used info for each window
+
+ long b_mtime; // last change time of original file
+ long b_mtime_read; // last change time when reading
+ uint64_t b_orig_size; // size of original file in bytes
+ int b_orig_mode; // mode of original file
+ time_t b_last_used; // time when the buffer was last used; used
+ // for viminfo
+
+ fmark_T b_namedm[NMARKS]; // current named marks (mark.c)
+
+ // These variables are set when VIsual_active becomes FALSE
visualinfo_T b_visual;
- int b_visual_mode_eval; /* b_visual.vi_mode for visualmode() */
+ int b_visual_mode_eval; // b_visual.vi_mode for visualmode()
fmark_T b_last_cursor; // cursor position when last unloading this
// buffer
@@ -560,8 +561,8 @@ struct file_buffer {
* the changelist contains old change positions
*/
fmark_T b_changelist[JUMPLISTSIZE];
- int b_changelistlen; /* number of active entries */
- bool b_new_change; /* set by u_savecommon() */
+ int b_changelistlen; // number of active entries
+ bool b_new_change; // set by u_savecommon()
/*
* Character table, only used in charset.c for 'iskeyword'
@@ -572,9 +573,9 @@ struct file_buffer {
// Table used for mappings local to a buffer.
mapblock_T *(b_maphash[MAX_MAPHASH]);
- /* First abbreviation local to a buffer. */
+ // First abbreviation local to a buffer.
mapblock_T *b_first_abbr;
- /* User commands local to the buffer. */
+ // User commands local to the buffer.
garray_T b_ucmds;
/*
* start and end of an operator, also used for '[ and ']
@@ -583,31 +584,31 @@ struct file_buffer {
pos_T b_op_start_orig; // used for Insstart_orig
pos_T b_op_end;
- bool b_marks_read; /* Have we read ShaDa marks yet? */
+ bool b_marks_read; // Have we read ShaDa marks yet?
/*
* The following only used in undo.c.
*/
- u_header_T *b_u_oldhead; /* pointer to oldest header */
- u_header_T *b_u_newhead; /* pointer to newest header; may not be valid
- if b_u_curhead is not NULL */
- u_header_T *b_u_curhead; /* pointer to current header */
- int b_u_numhead; /* current number of headers */
- bool b_u_synced; /* entry lists are synced */
- long b_u_seq_last; /* last used undo sequence number */
- long b_u_save_nr_last; /* counter for last file write */
- long b_u_seq_cur; /* hu_seq of header below which we are now */
- time_t b_u_time_cur; /* uh_time of header below which we are now */
- long b_u_save_nr_cur; /* file write nr after which we are now */
+ u_header_T *b_u_oldhead; // pointer to oldest header
+ u_header_T *b_u_newhead; // pointer to newest header; may not be valid
+ // if b_u_curhead is not NULL
+ u_header_T *b_u_curhead; // pointer to current header
+ int b_u_numhead; // current number of headers
+ bool b_u_synced; // entry lists are synced
+ long b_u_seq_last; // last used undo sequence number
+ long b_u_save_nr_last; // counter for last file write
+ long b_u_seq_cur; // hu_seq of header below which we are now
+ time_t b_u_time_cur; // uh_time of header below which we are now
+ long b_u_save_nr_cur; // file write nr after which we are now
/*
* variables for "U" command in undo.c
*/
- char_u *b_u_line_ptr; /* saved line for "U" command */
- linenr_T b_u_line_lnum; /* line number of line in u_line */
- colnr_T b_u_line_colnr; /* optional column number */
+ char_u *b_u_line_ptr; // saved line for "U" command
+ linenr_T b_u_line_lnum; // line number of line in u_line
+ colnr_T b_u_line_colnr; // optional column number
- bool b_scanned; /* ^N/^P have scanned this buffer */
+ bool b_scanned; // ^N/^P have scanned this buffer
// flags for use of ":lmap" and IM control
long b_p_iminsert; // input mode for insert
@@ -617,10 +618,10 @@ struct file_buffer {
#define B_IMODE_LMAP 1 // Input via langmap
# define B_IMODE_LAST 1
- short b_kmap_state; /* using "lmap" mappings */
-# define KEYMAP_INIT 1 /* 'keymap' was set, call keymap_init() */
-# define KEYMAP_LOADED 2 /* 'keymap' mappings have been loaded */
- garray_T b_kmap_ga; /* the keymap table */
+ int16_t b_kmap_state; // using "lmap" mappings
+# define KEYMAP_INIT 1 // 'keymap' was set, call keymap_init()
+# define KEYMAP_LOADED 2 // 'keymap' mappings have been loaded
+ garray_T b_kmap_ga; // the keymap table
/*
* Options local to a buffer.
@@ -720,9 +721,9 @@ struct file_buffer {
int b_p_udf; ///< 'undofile'
char_u *b_p_lw; ///< 'lispwords' local value
- /* end of buffer options */
+ // end of buffer options
- /* values set from b_p_cino */
+ // values set from b_p_cino
int b_ind_level;
int b_ind_open_imag;
int b_ind_no_brace;
@@ -763,11 +764,11 @@ struct file_buffer {
linenr_T b_no_eol_lnum; /* non-zero lnum when last line of next binary
* write should not have an end-of-line */
- int b_start_eol; /* last line had eol when it was read */
- int b_start_ffc; /* first char of 'ff' when edit started */
- char_u *b_start_fenc; /* 'fileencoding' when edit started or NULL */
- int b_bad_char; /* "++bad=" argument when edit started or 0 */
- int b_start_bomb; /* 'bomb' when it was read */
+ int b_start_eol; // last line had eol when it was read
+ int b_start_ffc; // first char of 'ff' when edit started
+ char_u *b_start_fenc; // 'fileencoding' when edit started or NULL
+ int b_bad_char; // "++bad=" argument when edit started or 0
+ int b_start_bomb; // 'bomb' when it was read
ScopeDictDictItem b_bufvar; ///< Variable for "b:" Dictionary.
dict_T *b_vars; ///< b: scope dictionary.
@@ -791,6 +792,12 @@ struct file_buffer {
// are not used! Use the B_SPELL macro to
// access b_spell without #ifdef.
+ char_u *b_prompt_text; // set by prompt_setprompt()
+ Callback b_prompt_callback; // set by prompt_setcallback()
+ Callback b_prompt_interrupt; // set by prompt_setinterrupt()
+ int b_prompt_insert; // value for restart_edit when entering
+ // a prompt buffer window.
+
synblock_T b_s; // Info related to syntax highlighting. w_s
// normally points to this, but some windows
// may use a different synblock_T.
@@ -860,8 +867,8 @@ struct file_buffer {
typedef struct diffblock_S diff_T;
struct diffblock_S {
diff_T *df_next;
- linenr_T df_lnum[DB_COUNT]; /* line number in buffer */
- linenr_T df_count[DB_COUNT]; /* nr of inserted/changed lines */
+ linenr_T df_lnum[DB_COUNT]; // line number in buffer
+ linenr_T df_count[DB_COUNT]; // nr of inserted/changed lines
};
#define SNAP_HELP_IDX 0
@@ -909,11 +916,11 @@ struct tabpage_S {
* wl_lnum and wl_lastlnum are invalid too.
*/
typedef struct w_line {
- linenr_T wl_lnum; /* buffer line number for logical line */
- uint16_t wl_size; /* height in screen lines */
- char wl_valid; /* TRUE values are valid for text in buffer */
- char wl_folded; /* TRUE when this is a range of folded lines */
- linenr_T wl_lastlnum; /* last buffer line number for logical line */
+ linenr_T wl_lnum; // buffer line number for logical line
+ uint16_t wl_size; // height in screen lines
+ char wl_valid; // TRUE values are valid for text in buffer
+ char wl_folded; // TRUE when this is a range of folded lines
+ linenr_T wl_lastlnum; // last buffer line number for logical line
} wline_T;
/*
@@ -936,9 +943,9 @@ struct frame_S {
win_T *fr_win; // window that fills this frame
};
-#define FR_LEAF 0 /* frame is a leaf */
-#define FR_ROW 1 /* frame with a row of windows */
-#define FR_COL 2 /* frame with a column of windows */
+#define FR_LEAF 0 // frame is a leaf
+#define FR_ROW 1 // frame with a row of windows
+#define FR_COL 2 // frame with a column of windows
/*
* Struct used for highlighting 'hlsearch' matches, matches defined by
@@ -1055,6 +1062,48 @@ typedef struct
pos_T w_cursor_corr; // corrected cursor position
} pos_save_T;
+/// Indices into vimmenu_T->strings[] and vimmenu_T->noremap[] for each mode
+/// \addtogroup MENU_INDEX
+/// @{
+enum {
+ MENU_INDEX_INVALID = -1,
+ MENU_INDEX_NORMAL = 0,
+ MENU_INDEX_VISUAL = 1,
+ MENU_INDEX_SELECT = 2,
+ MENU_INDEX_OP_PENDING = 3,
+ MENU_INDEX_INSERT = 4,
+ MENU_INDEX_CMDLINE = 5,
+ MENU_INDEX_TIP = 6,
+ MENU_MODES = 7,
+};
+
+typedef struct VimMenu vimmenu_T;
+
+struct VimMenu {
+ int modes; ///< Which modes is this menu visible for
+ int enabled; ///< for which modes the menu is enabled
+ char_u *name; ///< Name of menu, possibly translated
+ char_u *dname; ///< Displayed Name ("name" without '&')
+ char_u *en_name; ///< "name" untranslated, NULL when
+ ///< was not translated
+ char_u *en_dname; ///< NULL when "dname" untranslated
+ int mnemonic; ///< mnemonic key (after '&')
+ char_u *actext; ///< accelerator text (after TAB)
+ long priority; ///< Menu order priority
+ char_u *strings[MENU_MODES]; ///< Mapped string for each mode
+ int noremap[MENU_MODES]; ///< A \ref REMAP_VALUES flag for each mode
+ bool silent[MENU_MODES]; ///< A silent flag for each mode
+ vimmenu_T *children; ///< Children of sub-menu
+ vimmenu_T *parent; ///< Parent of menu
+ vimmenu_T *next; ///< Next item in menu
+};
+
+typedef struct {
+ int wb_startcol;
+ int wb_endcol;
+ vimmenu_T *wb_menu;
+} winbar_item_T;
+
/// Structure which contains all information that belongs to a window.
///
/// All row numbers are relative to the start of the window, except w_winrow.
@@ -1139,16 +1188,16 @@ struct window_S {
top of the window */
char w_topline_was_set; /* flag set to TRUE when topline is set,
e.g. by winrestview() */
- int w_topfill; /* number of filler lines above w_topline */
- int w_old_topfill; /* w_topfill at last redraw */
- bool w_botfill; /* true when filler lines are actually
- below w_topline (at end of file) */
- bool w_old_botfill; /* w_botfill at last redraw */
- colnr_T w_leftcol; /* window column number of the left most
- character in the window; used when
- 'wrap' is off */
- colnr_T w_skipcol; /* starting column when a single line
- doesn't fit in the window */
+ int w_topfill; // number of filler lines above w_topline
+ int w_old_topfill; // w_topfill at last redraw
+ bool w_botfill; // true when filler lines are actually
+ // below w_topline (at end of file)
+ bool w_old_botfill; // w_botfill at last redraw
+ colnr_T w_leftcol; // window column number of the left most
+ // character in the window; used when
+ // 'wrap' is off
+ colnr_T w_skipcol; // starting column when a single line
+ // doesn't fit in the window
//
// Layout of the window in the screen.
@@ -1156,7 +1205,7 @@ struct window_S {
//
int w_winrow; // first row of window in screen
int w_height; // number of rows in window, excluding
- // status/command line(s)
+ // status/command/winbar line(s)
int w_status_height; // number of status lines (0 or 1)
int w_wincol; // Leftmost column of window in screen.
int w_width; // Width of window, excluding separation.
@@ -1182,16 +1231,18 @@ struct window_S {
int w_valid;
pos_T w_valid_cursor; /* last known position of w_cursor, used
to adjust w_valid */
- colnr_T w_valid_leftcol; /* last known w_leftcol */
+ colnr_T w_valid_leftcol; // last known w_leftcol
+
+ bool w_viewport_invalid;
/*
* w_cline_height is the number of physical lines taken by the buffer line
* that the cursor is on. We use this to avoid extra calls to plines().
*/
- int w_cline_height; /* current size of cursor line */
- bool w_cline_folded; /* cursor line is folded */
+ int w_cline_height; // current size of cursor line
+ bool w_cline_folded; // cursor line is folded
- int w_cline_row; /* starting row of the cursor line */
+ int w_cline_row; // starting row of the cursor line
colnr_T w_virtcol; // column number of the cursor in the
// buffer line, as opposed to the column
@@ -1205,7 +1256,7 @@ struct window_S {
* This is related to positions in the window, not in the display or
* buffer, thus w_wrow is relative to w_winrow.
*/
- int w_wrow, w_wcol; /* cursor position in window */
+ int w_wrow, w_wcol; // cursor position in window
linenr_T w_botline; // number of the line below the bottom of
// the window
@@ -1223,65 +1274,70 @@ struct window_S {
* what is currently displayed. wl_valid is reset to indicated this.
* This is used for efficient redrawing.
*/
- int w_lines_valid; /* number of valid entries */
+ int w_lines_valid; // number of valid entries
wline_T *w_lines;
- garray_T w_folds; /* array of nested folds */
- bool w_fold_manual; /* when true: some folds are opened/closed
- manually */
- bool w_foldinvalid; /* when true: folding needs to be
- recomputed */
- int w_nrwidth; /* width of 'number' and 'relativenumber'
- column being used */
+ garray_T w_folds; // array of nested folds
+ bool w_fold_manual; // when true: some folds are opened/closed
+ // manually
+ bool w_foldinvalid; // when true: folding needs to be
+ // recomputed
+ int w_nrwidth; // width of 'number' and 'relativenumber'
+ // column being used
/*
* === end of cached values ===
*/
- int w_redr_type; /* type of redraw to be performed on win */
- int w_upd_rows; /* number of window lines to update when
- w_redr_type is REDRAW_TOP */
- linenr_T w_redraw_top; /* when != 0: first line needing redraw */
- linenr_T w_redraw_bot; /* when != 0: last line needing redraw */
- int w_redr_status; /* if TRUE status line must be redrawn */
+ int w_redr_type; // type of redraw to be performed on win
+ int w_upd_rows; // number of window lines to update when
+ // w_redr_type is REDRAW_TOP
+ linenr_T w_redraw_top; // when != 0: first line needing redraw
+ linenr_T w_redraw_bot; // when != 0: last line needing redraw
+ int w_redr_status; // if TRUE status line must be redrawn
- /* remember what is shown in the ruler for this window (if 'ruler' set) */
- pos_T w_ru_cursor; /* cursor position shown in ruler */
- colnr_T w_ru_virtcol; /* virtcol shown in ruler */
- linenr_T w_ru_topline; /* topline shown in ruler */
- linenr_T w_ru_line_count; /* line count used for ruler */
- int w_ru_topfill; /* topfill shown in ruler */
- char w_ru_empty; /* TRUE if ruler shows 0-1 (empty line) */
+ // remember what is shown in the ruler for this window (if 'ruler' set)
+ pos_T w_ru_cursor; // cursor position shown in ruler
+ colnr_T w_ru_virtcol; // virtcol shown in ruler
+ linenr_T w_ru_topline; // topline shown in ruler
+ linenr_T w_ru_line_count; // line count used for ruler
+ int w_ru_topfill; // topfill shown in ruler
+ char w_ru_empty; // TRUE if ruler shows 0-1 (empty line)
- int w_alt_fnum; /* alternate file (for # and CTRL-^) */
+ int w_alt_fnum; // alternate file (for # and CTRL-^)
- alist_T *w_alist; /* pointer to arglist for this window */
- int w_arg_idx; /* current index in argument list (can be
- out of range!) */
- int w_arg_idx_invalid; /* editing another file than w_arg_idx */
+ alist_T *w_alist; // pointer to arglist for this window
+ int w_arg_idx; // current index in argument list (can be
+ // out of range!)
+ int w_arg_idx_invalid; // editing another file than w_arg_idx
char_u *w_localdir; /* absolute path of local directory or
NULL */
- /*
- * Options local to a window.
- * They are local because they influence the layout of the window or
- * depend on the window layout.
- * There are two values: w_onebuf_opt is local to the buffer currently in
- * this window, w_allbuf_opt is for all buffers in this window.
- */
+ vimmenu_T *w_winbar; // The root of the WinBar menu hierarchy.
+ winbar_item_T *w_winbar_items; // list of items in the WinBar
+ int w_winbar_height; // 1 if there is a window toolbar
+
+ // Options local to a window.
+ // They are local because they influence the layout of the window or
+ // depend on the window layout.
+ // There are two values: w_onebuf_opt is local to the buffer currently in
+ // this window, w_allbuf_opt is for all buffers in this window.
winopt_T w_onebuf_opt;
winopt_T w_allbuf_opt;
- /* A few options have local flags for P_INSECURE. */
- uint32_t w_p_stl_flags; /* flags for 'statusline' */
- uint32_t w_p_fde_flags; /* flags for 'foldexpr' */
- uint32_t w_p_fdt_flags; /* flags for 'foldtext' */
- int *w_p_cc_cols; /* array of columns to highlight or NULL */
- int w_p_brimin; /* minimum width for breakindent */
- int w_p_brishift; /* additional shift for breakindent */
- bool w_p_brisbr; /* sbr in 'briopt' */
+ // A few options have local flags for P_INSECURE.
+ uint32_t w_p_stl_flags; // flags for 'statusline'
+ uint32_t w_p_fde_flags; // flags for 'foldexpr'
+ uint32_t w_p_fdt_flags; // flags for 'foldtext'
+ int *w_p_cc_cols; // array of columns to highlight or NULL
+ long w_p_siso; // 'sidescrolloff' local value
+ long w_p_so; // 'scrolloff' local value
+
+ int w_briopt_min; // minimum width for breakindent
+ int w_briopt_shift; // additional shift for breakindent
+ bool w_briopt_sbr; // sbr in 'briopt'
- /* transform a pointer to a "onebuf" option into a "allbuf" option */
+ // transform a pointer to a "onebuf" option into a "allbuf" option
#define GLOBAL_WO(p) ((char *)p + sizeof(winopt_T))
long w_scbind_pos;
@@ -1294,20 +1350,20 @@ struct window_S {
* a new line after setting the w_pcmark. If not, then we revert to
* using the previous w_pcmark.
*/
- pos_T w_pcmark; /* previous context mark */
- pos_T w_prev_pcmark; /* previous w_pcmark */
+ pos_T w_pcmark; // previous context mark
+ pos_T w_prev_pcmark; // previous w_pcmark
/*
* the jumplist contains old cursor positions
*/
xfmark_T w_jumplist[JUMPLISTSIZE];
- int w_jumplistlen; /* number of active entries */
- int w_jumplistidx; /* current position */
+ int w_jumplistlen; // number of active entries
+ int w_jumplistidx; // current position
- int w_changelistidx; /* current position in b_changelist */
+ int w_changelistidx; // current position in b_changelist
- matchitem_T *w_match_head; /* head of match list */
- int w_next_match_id; /* next match ID */
+ matchitem_T *w_match_head; // head of match list
+ int w_next_match_id; // next match ID
/*
* the tagstack grows from 0 upwards:
@@ -1315,9 +1371,9 @@ struct window_S {
* entry 1: newer
* entry 2: newest
*/
- taggy_T w_tagstack[TAGSTACKSIZE]; /* the tag stack */
- int w_tagstackidx; /* idx just below active entry */
- int w_tagstacklen; /* number of tags on stack */
+ taggy_T w_tagstack[TAGSTACKSIZE]; // the tag stack
+ int w_tagstackidx; // idx just below active entry
+ int w_tagstacklen; // number of tags on stack
ScreenGrid w_grid; // the grid specific to the window
bool w_pos_changed; // true if window position changed
@@ -1335,13 +1391,11 @@ struct window_S {
linenr_T w_nrwidth_line_count; /* line count when ml_nrwidth_width
* was computed. */
- int w_nrwidth_width; /* nr of chars to print line count. */
+ int w_nrwidth_width; // nr of chars to print line count.
- qf_info_T *w_llist; /* Location list for this window */
- /*
- * Location list reference used in the location list window.
- * In a non-location list window, w_llist_ref is NULL.
- */
+ qf_info_T *w_llist; // Location list for this window
+ // Location list reference used in the location list window.
+ // In a non-location list window, w_llist_ref is NULL.
qf_info_T *w_llist_ref;
};
diff --git a/src/nvim/buffer_updates.c b/src/nvim/buffer_updates.c
index 80780a3aa3..e6393bf02c 100644
--- a/src/nvim/buffer_updates.c
+++ b/src/nvim/buffer_updates.c
@@ -296,10 +296,7 @@ void buf_updates_send_splice(buf_T *buf,
BufUpdateCallbacks cb = kv_A(buf->update_callbacks, i);
bool keep = true;
if (cb.on_bytes != LUA_NOREF) {
- Array args = ARRAY_DICT_INIT;
- Object items[8];
- args.size = 8;
- args.items = items;
+ FIXED_TEMP_ARRAY(args, 8);
// the first argument is always the buffer handle
args.items[0] = BUFFER_OBJ(buf->handle);
diff --git a/src/nvim/change.c b/src/nvim/change.c
index a341b8fce1..51afb40b40 100644
--- a/src/nvim/change.c
+++ b/src/nvim/change.c
@@ -361,8 +361,8 @@ void changed_bytes(linenr_T lnum, colnr_T col)
/// insert/delete bytes at column
///
-/// Like changed_bytes() but also adjust extmark for "added" bytes.
-/// When "added" is negative text was deleted.
+/// Like changed_bytes() but also adjust extmark for "new" bytes.
+/// When "new" is negative text was deleted.
static void inserted_bytes(linenr_T lnum, colnr_T col, int old, int new)
{
if (curbuf_splice_pending == 0) {
diff --git a/src/nvim/channel.c b/src/nvim/channel.c
index c66a0682e3..5eb29a7290 100644
--- a/src/nvim/channel.c
+++ b/src/nvim/channel.c
@@ -19,7 +19,6 @@
#include "nvim/ascii.h"
static bool did_stdio = false;
-PMap(uint64_t) *channels = NULL;
/// next free id for a job or rpc channel
/// 1 is reserved for stdio channel
diff --git a/src/nvim/channel.h b/src/nvim/channel.h
index c733e276be..9d26852ce5 100644
--- a/src/nvim/channel.h
+++ b/src/nvim/channel.h
@@ -85,7 +85,7 @@ struct Channel {
bool callback_scheduled;
};
-EXTERN PMap(uint64_t) *channels;
+EXTERN PMap(uint64_t) *channels INIT(= NULL);
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "channel.h.generated.h"
diff --git a/src/nvim/charset.c b/src/nvim/charset.c
index e9140f8ec5..f9d5adbc12 100644
--- a/src/nvim/charset.c
+++ b/src/nvim/charset.c
@@ -8,7 +8,6 @@
#include <assert.h>
#include <string.h>
#include <wctype.h>
-#include <wchar.h> // for towupper() and towlower()
#include <inttypes.h>
#include "nvim/vim.h"
@@ -1571,6 +1570,7 @@ char_u* skiptohex(char_u *q)
///
/// @return Pointer to the next whitespace or NUL character.
char_u *skiptowhite(const char_u *p)
+ FUNC_ATTR_NONNULL_ALL
{
while (*p != ' ' && *p != '\t' && *p != NUL) {
p++;
diff --git a/src/nvim/cursor_shape.h b/src/nvim/cursor_shape.h
index 2c466603f0..a23fa6836d 100644
--- a/src/nvim/cursor_shape.h
+++ b/src/nvim/cursor_shape.h
@@ -33,11 +33,11 @@ SHAPE_HOR = 1, ///< horizontal bar cursor
SHAPE_VER = 2 ///< vertical bar cursor
} CursorShape;
-#define MSHAPE_NUMBERED 1000 /* offset for shapes identified by number */
-#define MSHAPE_HIDE 1 /* hide mouse pointer */
+#define MSHAPE_NUMBERED 1000 // offset for shapes identified by number
+#define MSHAPE_HIDE 1 // hide mouse pointer
-#define SHAPE_MOUSE 1 /* used for mouse pointer shape */
-#define SHAPE_CURSOR 2 /* used for text cursor shape */
+#define SHAPE_MOUSE 1 // used for mouse pointer shape
+#define SHAPE_CURSOR 2 // used for text cursor shape
typedef struct cursor_entry {
char *full_name; ///< mode description
diff --git a/src/nvim/diff.c b/src/nvim/diff.c
index c31adc01fd..3de5fc49bd 100644
--- a/src/nvim/diff.c
+++ b/src/nvim/diff.c
@@ -648,8 +648,8 @@ void diff_redraw(bool dofold)
foldUpdateAll(wp);
}
- /* A change may have made filler lines invalid, need to take care
- * of that for other windows. */
+ // A change may have made filler lines invalid, need to take care
+ // of that for other windows.
int n = diff_check(wp, wp->w_topline);
if (((wp != curwin) && (wp->w_topfill > 0)) || (n > 0)) {
@@ -1385,11 +1385,18 @@ void diff_win_options(win_T *wp, int addbuf)
curbuf = curwin->w_buffer;
if (!wp->w_p_diff) {
- wp->w_p_fdc_save = wp->w_p_fdc;
wp->w_p_fen_save = wp->w_p_fen;
wp->w_p_fdl_save = wp->w_p_fdl;
+
+ if (wp->w_p_diff_saved) {
+ free_string_option(wp->w_p_fdc_save);
+ }
+ wp->w_p_fdc_save = vim_strsave(wp->w_p_fdc);
}
- wp->w_p_fdc = diff_foldcolumn;
+ xfree(wp->w_p_fdc);
+ wp->w_p_fdc = (char_u *)xstrdup("2");
+ assert(diff_foldcolumn >= 0 && diff_foldcolumn <= 9);
+ snprintf((char *)wp->w_p_fdc, STRLEN(wp->w_p_fdc) + 1, "%d", diff_foldcolumn);
wp->w_p_fen = true;
wp->w_p_fdl = 0;
foldUpdateAll(wp);
@@ -1443,9 +1450,9 @@ void ex_diffoff(exarg_T *eap)
wp->w_p_fdm = vim_strsave(*wp->w_p_fdm_save
? wp->w_p_fdm_save
: (char_u *)"manual");
- if (wp->w_p_fdc == diff_foldcolumn) {
- wp->w_p_fdc = wp->w_p_fdc_save;
- }
+ free_string_option(wp->w_p_fdc);
+ wp->w_p_fdc = vim_strsave(wp->w_p_fdc_save);
+
if (wp->w_p_fdl == 0) {
wp->w_p_fdl = wp->w_p_fdl_save;
}
@@ -2432,6 +2439,10 @@ void nv_diffgetput(bool put, size_t count)
exarg_T ea;
char buf[30];
+ if (bt_prompt(curbuf)) {
+ vim_beep(BO_OPER);
+ return;
+ }
if (count == 0) {
ea.arg = (char_u *)"";
} else {
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index 68fa99484c..ea38221dc7 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -143,6 +143,7 @@ struct compl_S {
compl_T *cp_prev;
char_u *cp_str; // matched text
char_u *(cp_text[CPT_COUNT]); // text for the menu
+ typval_T cp_user_data;
char_u *cp_fname; // file containing the match, allocated when
// cp_flags has CP_FREE_FNAME
int cp_flags; // CP_ values
@@ -184,7 +185,7 @@ static bool compl_used_match; // Selected one of the matches.
static int compl_was_interrupted = FALSE; /* didn't finish finding
completions. */
-static int compl_restarting = FALSE; /* don't insert match */
+static bool compl_restarting = false; // don't insert match
// When the first completion is done "compl_started" is set. When it's
// false the word to be completed must be located.
@@ -197,7 +198,7 @@ static int compl_matches = 0;
static char_u *compl_pattern = NULL;
static int compl_direction = FORWARD;
static int compl_shows_dir = FORWARD;
-static int compl_pending = 0; /* > 1 for postponed CTRL-N */
+static int compl_pending = 0; // > 1 for postponed CTRL-N
static pos_T compl_startpos;
static colnr_T compl_col = 0; /* column where the text starts
* that is being completed */
@@ -249,30 +250,30 @@ typedef struct insert_state {
#define BACKSPACE_WORD_NOT_SPACE 3
#define BACKSPACE_LINE 4
-static size_t spell_bad_len = 0; /* length of located bad word */
+static size_t spell_bad_len = 0; // length of located bad word
-static colnr_T Insstart_textlen; /* length of line when insert started */
-static colnr_T Insstart_blank_vcol; /* vcol for first inserted blank */
-static bool update_Insstart_orig = true; /* set Insstart_orig to Insstart */
+static colnr_T Insstart_textlen; // length of line when insert started
+static colnr_T Insstart_blank_vcol; // vcol for first inserted blank
+static bool update_Insstart_orig = true; // set Insstart_orig to Insstart
-static char_u *last_insert = NULL; /* the text of the previous insert,
- K_SPECIAL and CSI are escaped */
-static int last_insert_skip; /* nr of chars in front of previous insert */
-static int new_insert_skip; /* nr of chars in front of current insert */
-static int did_restart_edit; /* "restart_edit" when calling edit() */
+static char_u *last_insert = NULL; // the text of the previous insert,
+ // K_SPECIAL and CSI are escaped
+static int last_insert_skip; // nr of chars in front of previous insert
+static int new_insert_skip; // nr of chars in front of current insert
+static int did_restart_edit; // "restart_edit" when calling edit()
static bool can_cindent; // may do cindenting on this line
-static int old_indent = 0; /* for ^^D command in insert mode */
+static int old_indent = 0; // for ^^D command in insert mode
-static int revins_on; /* reverse insert mode on */
-static int revins_chars; /* how much to skip after edit */
-static int revins_legal; /* was the last char 'legal'? */
-static int revins_scol; /* start column of revins session */
+static int revins_on; // reverse insert mode on
+static int revins_chars; // how much to skip after edit
+static int revins_legal; // was the last char 'legal'?
+static int revins_scol; // start column of revins session
-static int ins_need_undo; /* call u_save() before inserting a
- char. Set when edit() is called.
- after that arrow_used is used. */
+static bool ins_need_undo; // call u_save() before inserting a
+ // char. Set when edit() is called.
+ // after that arrow_used is used.
static bool did_add_space = false; // auto_format() added an extra space
// under the cursor
@@ -464,8 +465,8 @@ static void insert_enter(InsertState *s)
change_warning(s->i == 0 ? 0 : s->i + 1);
}
- ui_cursor_shape(); /* may show different cursor shape */
- do_digraph(-1); /* clear digraphs */
+ ui_cursor_shape(); // may show different cursor shape
+ do_digraph(-1); // clear digraphs
// Get the current length of the redo buffer, those characters have to be
// skipped if we want to get to the inserted characters.
@@ -574,6 +575,10 @@ static int insert_check(VimState *state)
foldCheckClose();
}
+ if (bt_prompt(curbuf)) {
+ init_prompt(s->cmdchar);
+ }
+
// If we inserted a character at the last position of the last line in the
// window, scroll the window one line up. This avoids an extra redraw. This
// is detected when the cursor column is smaller after inserting something.
@@ -589,7 +594,7 @@ static int insert_check(VimState *state)
if (curwin->w_wcol < s->mincol - curbuf->b_p_ts
&& curwin->w_wrow == curwin->w_winrow
- + curwin->w_height_inner - 1 - p_so
+ + curwin->w_height_inner - 1 - get_scrolloff_value()
&& (curwin->w_cursor.lnum != curwin->w_topline
|| curwin->w_topfill > 0)) {
if (curwin->w_topfill > 0) {
@@ -817,6 +822,16 @@ static int insert_handle_key(InsertState *s)
s->nomove = true;
return 0; // exit insert mode
}
+ if (s->c == Ctrl_C && bt_prompt(curbuf)) {
+ if (invoke_prompt_interrupt()) {
+ if (!bt_prompt(curbuf)) {
+ // buffer changed to a non-prompt buffer, get out of
+ // Insert mode
+ return 0;
+ }
+ break;
+ }
+ }
// when 'insertmode' set, and not halfway through a mapping, don't leave
// Insert mode
@@ -1143,6 +1158,15 @@ check_pum:
cmdwin_result = CAR;
return 0;
}
+ if (bt_prompt(curbuf)) {
+ invoke_prompt_callback();
+ if (!bt_prompt(curbuf)) {
+ // buffer changed to a non-prompt buffer, get out of
+ // Insert mode
+ return 0;
+ }
+ break;
+ }
if (!ins_eol(s->c) && !p_im) {
return 0; // out of memory
}
@@ -1397,9 +1421,8 @@ bool edit(int cmdchar, bool startln, long count)
* Only redraw when there are no characters available. This speeds up
* inserting sequences of characters (e.g., for CTRL-R).
*/
-static void
-ins_redraw (
- int ready /* not busy with something */
+static void ins_redraw(
+ bool ready // not busy with something
)
{
bool conceal_cursor_moved = false;
@@ -1480,7 +1503,7 @@ ins_redraw (
}
showruler(false);
setcursor();
- emsg_on_display = FALSE; /* may remove error message now */
+ emsg_on_display = false; // may remove error message now
}
/*
@@ -1489,24 +1512,25 @@ ins_redraw (
static void ins_ctrl_v(void)
{
int c;
- int did_putchar = FALSE;
+ bool did_putchar = false;
- /* may need to redraw when no more chars available now */
- ins_redraw(FALSE);
+ // may need to redraw when no more chars available now
+ ins_redraw(false);
if (redrawing() && !char_avail()) {
- edit_putchar('^', TRUE);
- did_putchar = TRUE;
+ edit_putchar('^', true);
+ did_putchar = true;
}
AppendToRedobuff(CTRL_V_STR);
add_to_showcmd_c(Ctrl_V);
c = get_literal();
- if (did_putchar)
- /* when the line fits in 'columns' the '^' is at the start of the next
- * line and will not removed by the redraw */
+ if (did_putchar) {
+ // when the line fits in 'columns' the '^' is at the start of the next
+ // line and will not removed by the redraw
edit_unputchar();
+ }
clear_showcmd();
insert_special(c, true, true);
revins_chars++;
@@ -1518,16 +1542,16 @@ static void ins_ctrl_v(void)
* Used while handling CTRL-K, CTRL-V, etc. in Insert mode.
*/
static int pc_status;
-#define PC_STATUS_UNSET 0 /* pc_bytes was not set */
-#define PC_STATUS_RIGHT 1 /* right halve of double-wide char */
-#define PC_STATUS_LEFT 2 /* left halve of double-wide char */
-#define PC_STATUS_SET 3 /* pc_bytes was filled */
-static char_u pc_bytes[MB_MAXBYTES + 1]; /* saved bytes */
+#define PC_STATUS_UNSET 0 // pc_bytes was not set
+#define PC_STATUS_RIGHT 1 // right halve of double-wide char
+#define PC_STATUS_LEFT 2 // left halve of double-wide char
+#define PC_STATUS_SET 3 // pc_bytes was filled
+static char_u pc_bytes[MB_MAXBYTES + 1]; // saved bytes
static int pc_attr;
static int pc_row;
static int pc_col;
-void edit_putchar(int c, int highlight)
+void edit_putchar(int c, bool highlight)
{
int attr;
@@ -1560,7 +1584,7 @@ void edit_putchar(int c, int highlight)
}
}
- /* save the character to be able to put it back */
+ // save the character to be able to put it back
if (pc_status == PC_STATUS_UNSET) {
grid_getbytes(&curwin->w_grid, pc_row, pc_col, pc_bytes, &pc_attr);
pc_status = PC_STATUS_SET;
@@ -1569,6 +1593,52 @@ void edit_putchar(int c, int highlight)
}
}
+// Return the effective prompt for the current buffer.
+char_u *prompt_text(void)
+{
+ if (curbuf->b_prompt_text == NULL) {
+ return (char_u *)"% ";
+ }
+ return curbuf->b_prompt_text;
+}
+
+// Prepare for prompt mode: Make sure the last line has the prompt text.
+// Move the cursor to this line.
+static void init_prompt(int cmdchar_todo)
+{
+ char_u *prompt = prompt_text();
+ char_u *text;
+
+ curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
+ text = get_cursor_line_ptr();
+ if (STRNCMP(text, prompt, STRLEN(prompt)) != 0) {
+ // prompt is missing, insert it or append a line with it
+ if (*text == NUL) {
+ ml_replace(curbuf->b_ml.ml_line_count, prompt, true);
+ } else {
+ ml_append(curbuf->b_ml.ml_line_count, prompt, 0, false);
+ }
+ curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
+ coladvance((colnr_T)MAXCOL);
+ changed_bytes(curbuf->b_ml.ml_line_count, 0);
+ }
+ if (cmdchar_todo == 'A') {
+ coladvance((colnr_T)MAXCOL);
+ }
+ if (cmdchar_todo == 'I' || curwin->w_cursor.col <= (int)STRLEN(prompt)) {
+ curwin->w_cursor.col = STRLEN(prompt);
+ }
+ // Make sure the cursor is in a valid position.
+ check_cursor();
+}
+
+// Return TRUE if the cursor is in the editable position of the prompt line.
+int prompt_curpos_editable(void)
+{
+ return curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count
+ && curwin->w_cursor.col >= (int)STRLEN(prompt_text());
+}
+
/*
* Undo the previous edit_putchar().
*/
@@ -1637,29 +1707,29 @@ change_indent (
int type,
int amount,
int round,
- int replaced, /* replaced character, put on replace stack */
- int call_changed_bytes /* call changed_bytes() */
+ int replaced, // replaced character, put on replace stack
+ int call_changed_bytes // call changed_bytes()
)
{
int vcol;
int last_vcol;
- int insstart_less; /* reduction for Insstart.col */
+ int insstart_less; // reduction for Insstart.col
int new_cursor_col;
int i;
char_u *ptr;
int save_p_list;
int start_col;
colnr_T vc;
- colnr_T orig_col = 0; /* init for GCC */
- char_u *new_line, *orig_line = NULL; /* init for GCC */
+ colnr_T orig_col = 0; // init for GCC
+ char_u *new_line, *orig_line = NULL; // init for GCC
- /* VREPLACE mode needs to know what the line was like before changing */
+ // VREPLACE mode needs to know what the line was like before changing
if (State & VREPLACE_FLAG) {
- orig_line = vim_strsave(get_cursor_line_ptr()); /* Deal with NULL below */
+ orig_line = vim_strsave(get_cursor_line_ptr()); // Deal with NULL below
orig_col = curwin->w_cursor.col;
}
- /* for the following tricks we don't want list mode */
+ // for the following tricks we don't want list mode
save_p_list = curwin->w_p_list;
curwin->w_p_list = FALSE;
vc = getvcol_nolist(&curwin->w_cursor);
@@ -1672,7 +1742,7 @@ change_indent (
*/
start_col = curwin->w_cursor.col;
- /* determine offset from first non-blank */
+ // determine offset from first non-blank
new_cursor_col = curwin->w_cursor.col;
beginline(BL_WHITE);
new_cursor_col -= curwin->w_cursor.col;
@@ -1686,8 +1756,9 @@ change_indent (
if (new_cursor_col < 0)
vcol = get_indent() - vcol;
- if (new_cursor_col > 0) /* can't fix replace stack */
+ if (new_cursor_col > 0) { // can't fix replace stack
start_col = -1;
+ }
/*
* Set the new indent. The cursor will be put on the first non-blank.
@@ -1697,9 +1768,10 @@ change_indent (
else {
int save_State = State;
- /* Avoid being called recursively. */
- if (State & VREPLACE_FLAG)
+ // Avoid being called recursively.
+ if (State & VREPLACE_FLAG) {
State = INSERT;
+ }
shift_line(type == INDENT_DEC, round, 1, call_changed_bytes);
State = save_State;
}
@@ -1802,8 +1874,8 @@ change_indent (
*/
if (REPLACE_NORMAL(State) && start_col >= 0) {
while (start_col > (int)curwin->w_cursor.col) {
- replace_join(0); /* remove a NUL from the replace stack */
- --start_col;
+ replace_join(0); // remove a NUL from the replace stack
+ start_col--;
}
while (start_col < (int)curwin->w_cursor.col || replaced) {
replace_push(NUL);
@@ -1821,10 +1893,10 @@ change_indent (
* put it back again the way we wanted it.
*/
if (State & VREPLACE_FLAG) {
- /* Save new line */
+ // Save new line
new_line = vim_strsave(get_cursor_line_ptr());
- /* We only put back the new line up to the cursor */
+ // We only put back the new line up to the cursor
new_line[curwin->w_cursor.col] = NUL;
int new_col = curwin->w_cursor.col;
@@ -1834,10 +1906,10 @@ change_indent (
curbuf_splice_pending++;
- /* Backspace from cursor to start of line */
+ // Backspace from cursor to start of line
backspace_until_column(0);
- /* Insert new stuff into line again */
+ // Insert new stuff into line again
ins_bytes(new_line);
xfree(new_line);
@@ -1863,10 +1935,11 @@ void truncate_spaces(char_u *line)
{
int i;
- /* find start of trailing white space */
+ // find start of trailing white space
for (i = (int)STRLEN(line) - 1; i >= 0 && ascii_iswhite(line[i]); i--) {
- if (State & REPLACE_FLAG)
- replace_join(0); /* remove a NUL from the replace stack */
+ if (State & REPLACE_FLAG) {
+ replace_join(0); // remove a NUL from the replace stack
+ }
}
line[i + 1] = NUL;
}
@@ -1935,7 +2008,7 @@ static void ins_ctrl_x(void)
compl_cont_status |= CONT_INTRPT;
else
compl_cont_status = 0;
- /* We're not sure which CTRL-X mode it will be yet */
+ // We're not sure which CTRL-X mode it will be yet
ctrl_x_mode = CTRL_X_NOT_DEFINED_YET;
edit_submode = (char_u *)_(CTRL_X_MSG(ctrl_x_mode));
edit_submode_pre = NULL;
@@ -2084,8 +2157,8 @@ int ins_compl_add_infercase(char_u *str_arg, int len, bool icase, char_u *fname,
{
char_u *str = str_arg;
int i, c;
- int actual_len; /* Take multi-byte characters */
- int actual_compl_length; /* into account. */
+ int actual_len; // Take multi-byte characters
+ int actual_compl_length; // into account.
int min_len;
bool has_lower = false;
bool was_letter = false;
@@ -2105,16 +2178,15 @@ int ins_compl_add_infercase(char_u *str_arg, int len, bool icase, char_u *fname,
} else
actual_len = len;
- /* Find actual length of original text. */
- if (has_mbyte) {
+ // Find actual length of original text.
+ {
const char_u *p = compl_orig_text;
actual_compl_length = 0;
while (*p != NUL) {
MB_PTR_ADV(p);
actual_compl_length++;
}
- } else
- actual_compl_length = compl_length;
+ }
/* "actual_len" may be smaller than "actual_compl_length" when using
* thesaurus, only use the minimum when comparing. */
@@ -2220,7 +2292,7 @@ int ins_compl_add_infercase(char_u *str_arg, int len, bool icase, char_u *fname,
flags |= CP_ICASE;
}
- return ins_compl_add(str, len, fname, NULL, false, dir, flags, false);
+ return ins_compl_add(str, len, fname, NULL, false, NULL, dir, flags, false);
}
/// Add a match to the list of matches
@@ -2244,6 +2316,7 @@ static int ins_compl_add(char_u *const str, int len,
char_u *const fname,
char_u *const *const cptext,
const bool cptext_allocated,
+ typval_T *user_data,
const Direction cdir, int flags_arg, const bool adup)
FUNC_ATTR_NONNULL_ARG(1)
{
@@ -2284,7 +2357,7 @@ static int ins_compl_add(char_u *const str, int len,
} while (match != NULL && match != compl_first_match);
}
- /* Remove any popup menu before changing the list of matches. */
+ // Remove any popup menu before changing the list of matches.
ins_compl_del_pum();
/*
@@ -2332,6 +2405,10 @@ static int ins_compl_add(char_u *const str, int len,
}
}
+ if (user_data != NULL) {
+ match->cp_user_data = *user_data;
+ }
+
/*
* Link the new match structure in the list of matches.
*/
@@ -2340,16 +2417,18 @@ static int ins_compl_add(char_u *const str, int len,
else if (dir == FORWARD) {
match->cp_next = compl_curr_match->cp_next;
match->cp_prev = compl_curr_match;
- } else { /* BACKWARD */
+ } else { // BACKWARD
match->cp_next = compl_curr_match;
match->cp_prev = compl_curr_match->cp_prev;
}
- if (match->cp_next)
+ if (match->cp_next) {
match->cp_next->cp_prev = match;
- if (match->cp_prev)
+ }
+ if (match->cp_prev) {
match->cp_prev->cp_next = match;
- else /* if there's nothing before, it is the first match */
+ } else { // if there's nothing before, it is the first match
compl_first_match = match;
+ }
compl_curr_match = match;
/*
@@ -2390,7 +2469,7 @@ static void ins_compl_longest_match(compl_T *match)
int had_match;
if (compl_leader == NULL) {
- /* First match, use it as a whole. */
+ // First match, use it as a whole.
compl_leader = vim_strsave(match->cp_str);
had_match = (curwin->w_cursor.col > compl_col);
ins_compl_delete();
@@ -2403,7 +2482,7 @@ static void ins_compl_longest_match(compl_T *match)
ins_compl_delete();
compl_used_match = false;
} else {
- /* Reduce the text if this match differs from compl_leader. */
+ // Reduce the text if this match differs from compl_leader.
p = compl_leader;
s = match->cp_str;
while (*p != NUL) {
@@ -2420,7 +2499,7 @@ static void ins_compl_longest_match(compl_T *match)
}
if (*p != NUL) {
- /* Leader was shortened, need to change the inserted text. */
+ // Leader was shortened, need to change the inserted text.
*p = NUL;
had_match = (curwin->w_cursor.col > compl_col);
ins_compl_delete();
@@ -2448,7 +2527,7 @@ static void ins_compl_add_matches(int num_matches, char_u **matches, int icase)
int dir = compl_direction;
for (int i = 0; i < num_matches && add_r != FAIL; i++) {
- if ((add_r = ins_compl_add(matches[i], -1, NULL, NULL, false, dir,
+ if ((add_r = ins_compl_add(matches[i], -1, NULL, NULL, false, NULL, dir,
icase ? CP_ICASE : 0, false)) == OK) {
// If dir was BACKWARD then honor it just once.
dir = FORWARD;
@@ -2470,7 +2549,7 @@ static int ins_compl_make_cyclic(void)
* Find the end of the list.
*/
match = compl_first_match;
- /* there's always an entry for the compl_orig_text, it doesn't count. */
+ // there's always an entry for the compl_orig_text, it doesn't count.
while (match->cp_next != NULL && match->cp_next != compl_first_match) {
match = match->cp_next;
++count;
@@ -2517,13 +2596,13 @@ void set_completion(colnr_T startcol, list_T *list)
startcol = curwin->w_cursor.col;
compl_col = startcol;
compl_length = (int)curwin->w_cursor.col - (int)startcol;
- /* compl_pattern doesn't need to be set */
+ // compl_pattern doesn't need to be set
compl_orig_text = vim_strnsave(get_cursor_line_ptr() + compl_col,
compl_length);
if (p_ic) {
flags |= CP_ICASE;
}
- if (ins_compl_add(compl_orig_text, -1, NULL, NULL, false, 0,
+ if (ins_compl_add(compl_orig_text, -1, NULL, NULL, false, NULL, 0,
flags, false) != OK) {
return;
}
@@ -2650,10 +2729,10 @@ void ins_compl_show_pum(void)
if (!pum_wanted() || !pum_enough_matches())
return;
- /* Dirty hard-coded hack: remove any matchparen highlighting. */
+ // Dirty hard-coded hack: remove any matchparen highlighting.
do_cmdline_cmd("if exists('g:loaded_matchparen')|3match none|endif");
- /* Update the screen before drawing the popup menu over it. */
+ // Update the screen before drawing the popup menu over it.
update_screen(0);
if (compl_match_array == NULL) {
@@ -2744,17 +2823,19 @@ void ins_compl_show_pum(void)
compl = compl->cp_next;
} while (compl != NULL && compl != compl_first_match);
- if (!shown_match_ok) /* no displayed match at all */
+ if (!shown_match_ok) { // no displayed match at all
cur = -1;
+ }
} else {
- /* popup menu already exists, only need to find the current item.*/
- for (i = 0; i < compl_match_arraysize; ++i)
+ // popup menu already exists, only need to find the current item.
+ for (i = 0; i < compl_match_arraysize; i++) {
if (compl_match_array[i].pum_text == compl_shown_match->cp_str
|| compl_match_array[i].pum_text
== compl_shown_match->cp_text[CPT_ABBR]) {
cur = i;
break;
}
+ }
}
// In Replace mode when a $ is displayed at the end of the line only
@@ -2774,8 +2855,8 @@ void ins_compl_show_pum(void)
}
}
-#define DICT_FIRST (1) /* use just first element in "dict" */
-#define DICT_EXACT (2) /* "dict" is the exact name of a file */
+#define DICT_FIRST (1) // use just first element in "dict"
+#define DICT_EXACT (2) // "dict" is the exact name of a file
/*
* Add any identifiers that match the given pattern in the list of dictionary
@@ -2785,8 +2866,8 @@ static void
ins_compl_dictionaries (
char_u *dict_start,
char_u *pat,
- int flags, /* DICT_FIRST and/or DICT_EXACT */
- int thesaurus /* Thesaurus completion */
+ int flags, // DICT_FIRST and/or DICT_EXACT
+ int thesaurus // Thesaurus completion
)
{
char_u *dict = dict_start;
@@ -2808,9 +2889,9 @@ ins_compl_dictionaries (
}
buf = xmalloc(LSIZE);
- regmatch.regprog = NULL; /* so that we can goto theend */
+ regmatch.regprog = NULL; // so that we can goto theend
- /* If 'infercase' is set, don't use 'smartcase' here */
+ // If 'infercase' is set, don't use 'smartcase' here
save_p_scs = p_scs;
if (curbuf->b_p_inf)
p_scs = FALSE;
@@ -2833,10 +2914,10 @@ ins_compl_dictionaries (
goto theend;
}
- /* ignore case depends on 'ignorecase', 'smartcase' and "pat" */
+ // ignore case depends on 'ignorecase', 'smartcase' and "pat"
regmatch.rm_ic = ignorecase(pat);
while (*dict != NUL && !got_int && !compl_interrupted) {
- /* copy one dictionary file name into buf */
+ // copy one dictionary file name into buf
if (flags == DICT_EXACT) {
count = 1;
files = &dict;
@@ -2861,7 +2942,7 @@ ins_compl_dictionaries (
else
ptr = pat;
spell_dump_compl(ptr, regmatch.rm_ic, &dir, 0);
- } else if (count > 0) { /* avoid warning for using "files" uninit */
+ } else if (count > 0) { // avoid warning for using "files" uninit
ins_compl_files(count, files, thesaurus, flags,
&regmatch, buf, &dir);
if (flags != DICT_EXACT)
@@ -2927,20 +3008,18 @@ static void ins_compl_files(int count, char_u **files, int thesaurus, int flags,
break;
wstart = ptr;
- /* Find end of the word. */
- if (has_mbyte)
- /* Japanese words may have characters in
- * different classes, only separate words
- * with single-byte non-word characters. */
- while (*ptr != NUL) {
- int l = (*mb_ptr2len)(ptr);
-
- if (l < 2 && !vim_iswordc(*ptr))
- break;
- ptr += l;
+ // Find end of the word.
+ // Japanese words may have characters in
+ // different classes, only separate words
+ // with single-byte non-word characters.
+ while (*ptr != NUL) {
+ const int l = utfc_ptr2len(ptr);
+
+ if (l < 2 && !vim_iswordc(*ptr)) {
+ break;
}
- else
- ptr = find_word_end(ptr);
+ ptr += l;
+ }
// Add the word. Skip the regexp match.
if (wstart != regmatch->startp[0]) {
@@ -2949,15 +3028,17 @@ static void ins_compl_files(int count, char_u **files, int thesaurus, int flags,
}
}
}
- if (add_r == OK)
- /* if dir was BACKWARD then honor it just once */
+ if (add_r == OK) {
+ // if dir was BACKWARD then honor it just once
*dir = FORWARD;
- else if (add_r == FAIL)
+ } else if (add_r == FAIL) {
break;
- /* avoid expensive call to vim_regexec() when at end
- * of line */
- if (*ptr == '\n' || got_int)
+ }
+ // avoid expensive call to vim_regexec() when at end
+ // of line
+ if (*ptr == '\n' || got_int) {
break;
+ }
}
line_breakcheck();
ins_compl_check_keys(50, false);
@@ -3045,6 +3126,7 @@ static void ins_compl_free(void)
for (int i = 0; i < CPT_COUNT; i++) {
xfree(match->cp_text[i]);
}
+ tv_clear(&match->cp_user_data);
xfree(match);
} while (compl_curr_match != NULL && compl_curr_match != compl_first_match);
compl_first_match = compl_curr_match = NULL;
@@ -3140,8 +3222,11 @@ void get_complete_info(list_T *what_list, dict_T *retdict)
(char *)EMPTY_IF_NULL(match->cp_text[CPT_KIND]));
tv_dict_add_str(di, S_LEN("info"),
(char *)EMPTY_IF_NULL(match->cp_text[CPT_INFO]));
- tv_dict_add_str(di, S_LEN("user_data"),
- (char *)EMPTY_IF_NULL(match->cp_text[CPT_USER_DATA]));
+ if (match->cp_user_data.v_type == VAR_UNKNOWN) {
+ tv_dict_add_str(di, S_LEN("user_data"), "");
+ } else {
+ tv_dict_add_tv(di, S_LEN("user_data"), &match->cp_user_data);
+ }
}
match = match->cp_next;
} while (match != NULL && match != compl_first_match);
@@ -3204,9 +3289,10 @@ static int ins_compl_bs(void)
xfree(compl_leader);
compl_leader = vim_strnsave(line + compl_col, (int)(p - line) - compl_col);
ins_compl_new_leader();
- if (compl_shown_match != NULL)
- /* Make sure current match is not a hidden item. */
+ if (compl_shown_match != NULL) {
+ // Make sure current match is not a hidden item.
compl_curr_match = compl_shown_match;
+ }
return NUL;
}
@@ -3250,7 +3336,7 @@ static void ins_compl_new_leader(void)
compl_enter_selects = !compl_used_match;
- /* Show the popup menu with a different set of matches. */
+ // Show the popup menu with a different set of matches.
ins_compl_show_pum();
/* Don't let Enter select the original text when there is no popup menu.
@@ -3293,9 +3379,10 @@ static void ins_compl_addleader(int c)
ins_char(c);
}
- /* If we didn't complete finding matches we must search again. */
- if (ins_compl_need_restart())
+ // If we didn't complete finding matches we must search again.
+ if (ins_compl_need_restart()) {
ins_compl_restart();
+ }
xfree(compl_leader);
compl_leader = vim_strnsave(get_cursor_line_ptr() + compl_col,
@@ -3351,9 +3438,9 @@ static void ins_compl_addfrommatch(void)
compl_T *cp;
assert(compl_shown_match != NULL);
p = compl_shown_match->cp_str;
- if ((int)STRLEN(p) <= len) { /* the match is too short */
- /* When still at the original match use the first entry that matches
- * the leader. */
+ if ((int)STRLEN(p) <= len) { // the match is too short
+ // When still at the original match use the first entry that matches
+ // the leader.
if (compl_shown_match->cp_flags & CP_ORIGINAL_TEXT) {
p = NULL;
for (cp = compl_shown_match->cp_next; cp != NULL
@@ -3393,14 +3480,14 @@ static bool ins_compl_prep(int c)
if (c != Ctrl_R && vim_is_ctrl_x_key(c))
edit_submode_extra = NULL;
- /* Ignore end of Select mode mapping and mouse scroll buttons. */
+ // Ignore end of Select mode mapping and mouse scroll buttons.
if (c == K_SELECT || c == K_MOUSEDOWN || c == K_MOUSEUP
|| c == K_MOUSELEFT || c == K_MOUSERIGHT || c == K_EVENT
|| c == K_COMMAND) {
return retval;
}
- /* Set "compl_get_longest" when finding the first matches. */
+ // Set "compl_get_longest" when finding the first matches.
if (ctrl_x_mode == CTRL_X_NOT_DEFINED_YET
|| (ctrl_x_mode == CTRL_X_NORMAL && !compl_started)) {
compl_get_longest = (strstr((char *)p_cot, "longest") != NULL);
@@ -3433,7 +3520,7 @@ static bool ins_compl_prep(int c)
ctrl_x_mode = CTRL_X_DICTIONARY;
break;
case Ctrl_R:
- /* Simply allow ^R to happen without affecting ^X mode */
+ // Simply allow ^R to happen without affecting ^X mode
break;
case Ctrl_T:
ctrl_x_mode = CTRL_X_THESAURUS;
@@ -3447,9 +3534,9 @@ static bool ins_compl_prep(int c)
case 's':
case Ctrl_S:
ctrl_x_mode = CTRL_X_SPELL;
- ++emsg_off; /* Avoid getting the E756 error twice. */
+ emsg_off++; // Avoid getting the E756 error twice.
spell_back_to_badword();
- --emsg_off;
+ emsg_off--;
break;
case Ctrl_RSB:
ctrl_x_mode = CTRL_X_TAGS;
@@ -3548,10 +3635,10 @@ static bool ins_compl_prep(int c)
// When completing whole lines: fix indent for 'cindent'.
// Otherwise, break line if it's too long.
if (compl_cont_mode == CTRL_X_WHOLE_LINE) {
- /* re-indent the current line */
+ // re-indent the current line
if (want_cindent) {
do_c_expr_indent();
- want_cindent = FALSE; /* don't do it again */
+ want_cindent = false; // don't do it again
}
} else {
int prev_col = curwin->w_cursor.col;
@@ -3594,17 +3681,11 @@ static bool ins_compl_prep(int c)
auto_format(FALSE, TRUE);
- {
- const int new_mode = ctrl_x_mode;
-
- // Trigger the CompleteDone event to give scripts a chance to
- // act upon the completion. Do this before clearing the info,
- // and restore ctrl_x_mode, so that complete_info() can be
- // used.
- ctrl_x_mode = prev_mode;
- ins_apply_autocmds(EVENT_COMPLETEDONE);
- ctrl_x_mode = new_mode;
- }
+ // Trigger the CompleteDonePre event to give scripts a chance to
+ // act upon the completion before clearing the info, and restore
+ // ctrl_x_mode, so that complete_info() can be used.
+ ctrl_x_mode = prev_mode;
+ ins_apply_autocmds(EVENT_COMPLETEDONEPRE);
ins_compl_free();
compl_started = false;
@@ -3630,6 +3711,9 @@ static bool ins_compl_prep(int c)
*/
if (want_cindent && in_cinkeys(KEY_COMPLETE, ' ', inindent(0)))
do_c_expr_indent();
+ // Trigger the CompleteDone event to give scripts a chance to act
+ // upon the end of completion.
+ ins_apply_autocmds(EVENT_COMPLETEDONE);
}
} else if (ctrl_x_mode == CTRL_X_LOCAL_MSG)
/* Trigger the CompleteDone event to give scripts a chance to act
@@ -3658,10 +3742,11 @@ static void ins_compl_fixRedoBufForLeader(char_u *ptr_arg)
char_u *ptr = ptr_arg;
if (ptr == NULL) {
- if (compl_leader != NULL)
+ if (compl_leader != NULL) {
ptr = compl_leader;
- else
- return; /* nothing to do */
+ } else {
+ return; // nothing to do
+ }
}
if (compl_orig_text != NULL) {
p = compl_orig_text;
@@ -3690,9 +3775,10 @@ static buf_T *ins_compl_next_buf(buf_T *buf, int flag)
{
static win_T *wp;
- if (flag == 'w') { /* just windows */
- if (buf == curbuf) /* first call for this flag/expansion */
+ if (flag == 'w') { // just windows
+ if (buf == curbuf) { // first call for this flag/expansion
wp = curwin;
+ }
assert(wp);
while ((wp = (wp->w_next != NULL ? wp->w_next : firstwin)) != curwin
&& wp->w_buffer->b_scanned)
@@ -3770,7 +3856,7 @@ expand_by_function(
EMSG(_(e_complwin));
goto theend;
}
- curwin->w_cursor = pos; /* restore the cursor position */
+ curwin->w_cursor = pos; // restore the cursor position
validate_cursor();
if (!equalpos(curwin->w_cursor, pos)) {
EMSG(_(e_compldel));
@@ -3854,15 +3940,16 @@ int ins_compl_add_tv(typval_T *const tv, const Direction dir)
bool empty = false;
int flags = 0;
char *(cptext[CPT_COUNT]);
+ typval_T user_data;
+ user_data.v_type = VAR_UNKNOWN;
if (tv->v_type == VAR_DICT && tv->vval.v_dict != NULL) {
word = tv_dict_get_string(tv->vval.v_dict, "word", false);
cptext[CPT_ABBR] = tv_dict_get_string(tv->vval.v_dict, "abbr", true);
cptext[CPT_MENU] = tv_dict_get_string(tv->vval.v_dict, "menu", true);
cptext[CPT_KIND] = tv_dict_get_string(tv->vval.v_dict, "kind", true);
cptext[CPT_INFO] = tv_dict_get_string(tv->vval.v_dict, "info", true);
- cptext[CPT_USER_DATA] = tv_dict_get_string(tv->vval.v_dict,
- "user_data", true);
+ tv_dict_get_tv(tv->vval.v_dict, "user_data", &user_data);
if (tv_dict_get_number(tv->vval.v_dict, "icase")) {
flags |= CP_ICASE;
@@ -3884,7 +3971,7 @@ int ins_compl_add_tv(typval_T *const tv, const Direction dir)
return FAIL;
}
return ins_compl_add((char_u *)word, -1, NULL,
- (char_u **)cptext, true, dir, flags, dup);
+ (char_u **)cptext, true, &user_data, dir, flags, dup);
}
// Get the next expansion(s), using "compl_pattern".
@@ -4017,7 +4104,7 @@ static int ins_compl_get_exp(pos_T *ini)
type = CTRL_X_PATH_DEFINES;
else if (*e_cpt == ']' || *e_cpt == 't') {
type = CTRL_X_TAGS;
- vim_snprintf((char *)IObuff, IOSIZE, _("Scanning tags."));
+ vim_snprintf((char *)IObuff, IOSIZE, "%s", _("Scanning tags."));
(void)msg_trunc_attr(IObuff, true, HL_ATTR(HLF_R));
} else {
type = -1;
@@ -4374,9 +4461,11 @@ static dict_T *ins_compl_dict_alloc(compl_T *match)
tv_dict_add_str(
dict, S_LEN("info"),
(const char *)EMPTY_IF_NULL(match->cp_text[CPT_INFO]));
- tv_dict_add_str(
- dict, S_LEN("user_data"),
- (const char *)EMPTY_IF_NULL(match->cp_text[CPT_USER_DATA]));
+ if (match->cp_user_data.v_type == VAR_UNKNOWN) {
+ tv_dict_add_str(dict, S_LEN("user_data"), "");
+ } else {
+ tv_dict_add_tv(dict, S_LEN("user_data"), &match->cp_user_data);
+ }
return dict;
}
@@ -4408,8 +4497,7 @@ ins_compl_next (
int num_matches = -1;
int todo = count;
compl_T *found_compl = NULL;
- int found_end = FALSE;
- int advance;
+ bool found_end = false;
const bool started = compl_started;
/* When user complete function return -1 for findstart which is next
@@ -4445,17 +4533,17 @@ ins_compl_next (
if (allow_get_expansion && insert_match
&& (!(compl_get_longest || compl_restarting) || compl_used_match))
- /* Delete old text to be replaced */
+ // Delete old text to be replaced
ins_compl_delete();
- /* When finding the longest common text we stick at the original text,
- * don't let CTRL-N or CTRL-P move to the first match. */
- advance = count != 1 || !allow_get_expansion || !compl_get_longest;
+ // When finding the longest common text we stick at the original text,
+ // don't let CTRL-N or CTRL-P move to the first match.
+ bool advance = count != 1 || !allow_get_expansion || !compl_get_longest;
- /* When restarting the search don't insert the first match either. */
+ // When restarting the search don't insert the first match either.
if (compl_restarting) {
- advance = FALSE;
- compl_restarting = FALSE;
+ advance = false;
+ compl_restarting = false;
}
/* Repeat this for when <PageUp> or <PageDown> is typed. But don't wrap
@@ -4489,10 +4577,10 @@ ins_compl_next (
++compl_pending;
}
- /* Find matches. */
+ // Find matches.
num_matches = ins_compl_get_exp(&compl_startpos);
- /* handle any pending completions */
+ // handle any pending completions
while (compl_pending != 0 && compl_direction == compl_shows_dir
&& advance) {
if (compl_pending > 0 && compl_shown_match->cp_next != NULL) {
@@ -4505,7 +4593,7 @@ ins_compl_next (
} else
break;
}
- found_end = FALSE;
+ found_end = false;
}
if ((compl_shown_match->cp_flags & CP_ORIGINAL_TEXT) == 0
&& compl_leader != NULL
@@ -4517,17 +4605,17 @@ ins_compl_next (
found_compl = compl_shown_match;
}
- /* Stop at the end of the list when we found a usable match. */
+ // Stop at the end of the list when we found a usable match.
if (found_end) {
if (found_compl != NULL) {
compl_shown_match = found_compl;
break;
}
- todo = 1; /* use first usable match after wrapping around */
+ todo = 1; // use first usable match after wrapping around
}
}
- /* Insert the text of the new completion, or the compl_leader. */
+ // Insert the text of the new completion, or the compl_leader.
if (compl_no_insert && !started) {
ins_bytes(compl_orig_text + ins_compl_len());
compl_used_match = false;
@@ -4621,9 +4709,10 @@ void ins_compl_check_keys(int frequency, int in_compl_func)
return;
}
- /* Only do this at regular intervals */
- if (++count < frequency)
+ // Only do this at regular intervals
+ if (++count < frequency) {
return;
+ }
count = 0;
/* Check for a typed key. Do use mappings, otherwise vim_is_ctrl_x_key()
@@ -4631,7 +4720,7 @@ void ins_compl_check_keys(int frequency, int in_compl_func)
int c = vpeekc_any();
if (c != NUL) {
if (vim_is_ctrl_x_key(c) && c != Ctrl_X && c != Ctrl_R) {
- c = safe_vgetc(); /* Eat the character */
+ c = safe_vgetc(); // Eat the character
compl_shows_dir = ins_compl_key2dir(c);
(void)ins_compl_next(false, ins_compl_key2count(c),
c != K_UP && c != K_DOWN, in_compl_func);
@@ -4700,8 +4789,9 @@ static int ins_compl_key2count(int c)
if (ins_compl_pum_key(c) && c != K_UP && c != K_DOWN) {
h = pum_get_height();
- if (h > 3)
- h -= 2; /* keep some context */
+ if (h > 3) {
+ h -= 2; // keep some context
+ }
return h;
}
return 1;
@@ -4739,8 +4829,8 @@ static bool ins_compl_use_match(int c)
static int ins_complete(int c, bool enable_pum)
{
char_u *line;
- int startcol = 0; /* column where searched text starts */
- colnr_T curs_col; /* cursor column */
+ int startcol = 0; // column where searched text starts
+ colnr_T curs_col; // cursor column
int n;
int save_w_wrow;
int save_w_leftcol;
@@ -4752,7 +4842,7 @@ static int ins_complete(int c, bool enable_pum)
insert_match = ins_compl_use_match(c);
if (!compl_started) {
- /* First time we hit ^N or ^P (in a row, I mean) */
+ // First time we hit ^N or ^P (in a row, I mean)
did_ai = false;
did_si = false;
@@ -4790,7 +4880,7 @@ static int ins_complete(int c, bool enable_pum)
compl_col = (colnr_T)getwhitecols(line);
compl_startpos.col = compl_col;
compl_startpos.lnum = curwin->w_cursor.lnum;
- compl_cont_status &= ~CONT_SOL; /* clear SOL if present */
+ compl_cont_status &= ~CONT_SOL; // clear SOL if present
} else {
/* S_IPOS was set when we inserted a word that was at the
* beginning of the line, which means that we'll go to SOL
@@ -4822,7 +4912,7 @@ static int ins_complete(int c, bool enable_pum)
} else
compl_cont_status &= CONT_LOCAL;
- if (!(compl_cont_status & CONT_ADDING)) { /* normal expansion */
+ if (!(compl_cont_status & CONT_ADDING)) { // normal expansion
compl_cont_mode = ctrl_x_mode;
if (ctrl_x_mode != CTRL_X_NORMAL) {
// Remove LOCAL if ctrl_x_mode != CTRL_X_NORMAL
@@ -4851,7 +4941,7 @@ static int ins_complete(int c, bool enable_pum)
} else if (compl_cont_status & CONT_ADDING) {
char_u *prefix = (char_u *)"\\<";
- /* we need up to 2 extra chars for the prefix */
+ // we need up to 2 extra chars for the prefix
compl_pattern = xmalloc(quote_meta(NULL, line + compl_col,
compl_length) + 2);
if (!vim_iswordp(line + compl_col)
@@ -4903,14 +4993,16 @@ static int ins_complete(int c, bool enable_pum)
} else if (CTRL_X_MODE_LINE_OR_EVAL(ctrl_x_mode)) {
compl_col = (colnr_T)getwhitecols(line);
compl_length = (int)curs_col - (int)compl_col;
- if (compl_length < 0) /* cursor in indent: empty pattern */
+ if (compl_length < 0) { // cursor in indent: empty pattern
compl_length = 0;
- if (p_ic)
+ }
+ if (p_ic) {
compl_pattern = str_foldcase(line + compl_col, compl_length, NULL, 0);
- else
+ } else {
compl_pattern = vim_strnsave(line + compl_col, compl_length);
+ }
} else if (ctrl_x_mode == CTRL_X_FILES) {
- /* Go back to just before the first filename character. */
+ // Go back to just before the first filename character.
if (startcol > 0) {
char_u *p = line + startcol;
@@ -4982,7 +5074,7 @@ static int ins_complete(int c, bool enable_pum)
EMSG(_(e_complwin));
return FAIL;
}
- curwin->w_cursor = pos; /* restore the cursor position */
+ curwin->w_cursor = pos; // restore the cursor position
validate_cursor();
if (!equalpos(curwin->w_cursor, pos)) {
EMSG(_(e_compldel));
@@ -5034,7 +5126,7 @@ static int ins_complete(int c, bool enable_pum)
spell_expand_check_cap(compl_col);
compl_length = (int)curs_col - compl_col;
}
- /* Need to obtain "line" again, it may have become invalid. */
+ // Need to obtain "line" again, it may have become invalid.
line = ml_get(curwin->w_cursor.lnum);
compl_pattern = vim_strnsave(line + compl_col, compl_length);
} else {
@@ -5045,7 +5137,7 @@ static int ins_complete(int c, bool enable_pum)
if (compl_cont_status & CONT_ADDING) {
edit_submode_pre = (char_u *)_(" Adding");
if (CTRL_X_MODE_LINE_OR_EVAL(ctrl_x_mode)) {
- /* Insert a new line, keep indentation but ignore 'comments' */
+ // Insert a new line, keep indentation but ignore 'comments'
char_u *old = curbuf->b_p_com;
curbuf->b_p_com = (char_u *)"";
@@ -5070,13 +5162,13 @@ static int ins_complete(int c, bool enable_pum)
* the redo buffer. */
ins_compl_fixRedoBufForLeader(NULL);
- /* Always add completion for the original text. */
+ // Always add completion for the original text.
xfree(compl_orig_text);
compl_orig_text = vim_strnsave(line + compl_col, compl_length);
if (p_ic) {
flags |= CP_ICASE;
}
- if (ins_compl_add(compl_orig_text, -1, NULL, NULL, false, 0,
+ if (ins_compl_add(compl_orig_text, -1, NULL, NULL, false, NULL, 0,
flags, false) != OK) {
XFREE_CLEAR(compl_pattern);
XFREE_CLEAR(compl_orig_text);
@@ -5107,8 +5199,9 @@ static int ins_complete(int c, bool enable_pum)
n = ins_compl_next(true, ins_compl_key2count(c), insert_match, false);
- if (n > 1) /* all matches have been found */
+ if (n > 1) { // all matches have been found
compl_matches = n;
+ }
compl_curr_match = compl_shown_match;
compl_direction = compl_shows_dir;
@@ -5119,7 +5212,7 @@ static int ins_complete(int c, bool enable_pum)
got_int = FALSE;
}
- /* we found no match if the list has only the "compl_orig_text"-entry */
+ // we found no match if the list has only the "compl_orig_text"-entry
if (compl_first_match == compl_first_match->cp_next) {
edit_submode_extra = (compl_cont_status & CONT_ADDING)
&& compl_length > 1
@@ -5155,7 +5248,7 @@ static int ins_complete(int c, bool enable_pum)
edit_submode_extra = (char_u *)_("The only match");
edit_submode_highl = HLF_COUNT;
} else {
- /* Update completion sequence number when needed. */
+ // Update completion sequence number when needed.
if (compl_curr_match->cp_number == -1) {
int number = 0;
compl_T *match;
@@ -5178,24 +5271,27 @@ static int ins_complete(int c, bool enable_pum)
match != NULL && match->cp_number == -1;
match = match->cp_next)
match->cp_number = ++number;
- } else { /* BACKWARD */
- /* search forwards (upwards) for the first valid (!= -1)
- * number. This should normally succeed already at the
- * first loop cycle, so it's fast! */
- for (match = compl_curr_match->cp_next; match != NULL
- && match != compl_first_match;
- match = match->cp_next)
+ } else { // BACKWARD
+ // search forwards (upwards) for the first valid (!= -1)
+ // number. This should normally succeed already at the
+ // first loop cycle, so it's fast!
+ for (match = compl_curr_match->cp_next;
+ match != NULL && match != compl_first_match;
+ match = match->cp_next) {
if (match->cp_number != -1) {
number = match->cp_number;
break;
}
- if (match != NULL)
- /* go down and assign all numbers which are not
- * assigned yet */
- for (match = match->cp_prev; match
- && match->cp_number == -1;
- match = match->cp_prev)
+ }
+ if (match != NULL) {
+ // go down and assign all numbers which are not
+ // assigned yet
+ for (match = match->cp_prev;
+ match && match->cp_number == -1;
+ match = match->cp_prev) {
match->cp_number = ++number;
+ }
+ }
}
}
@@ -5254,7 +5350,7 @@ static int ins_complete(int c, bool enable_pum)
*/
static unsigned quote_meta(char_u *dest, char_u *src, int len)
{
- unsigned m = (unsigned)len + 1; /* one extra for the NUL */
+ unsigned m = (unsigned)len + 1; // one extra for the NUL
for (; --len >= 0; src++) {
switch (*src) {
@@ -5266,8 +5362,9 @@ static unsigned quote_meta(char_u *dest, char_u *src, int len)
break;
FALLTHROUGH;
case '~':
- if (!p_magic) /* quote these only if magic is set */
+ if (!p_magic) { // quote these only if magic is set
break;
+ }
FALLTHROUGH;
case '\\':
if (ctrl_x_mode == CTRL_X_DICTIONARY
@@ -5277,24 +5374,24 @@ static unsigned quote_meta(char_u *dest, char_u *src, int len)
case '^': // currently it's not needed.
case '$':
m++;
- if (dest != NULL)
+ if (dest != NULL) {
*dest++ = '\\';
+ }
break;
}
- if (dest != NULL)
+ if (dest != NULL) {
*dest++ = *src;
- /* Copy remaining bytes of a multibyte character. */
- if (has_mbyte) {
- int i, mb_len;
-
- mb_len = (*mb_ptr2len)(src) - 1;
- if (mb_len > 0 && len >= mb_len)
- for (i = 0; i < mb_len; ++i) {
- --len;
- ++src;
- if (dest != NULL)
- *dest++ = *src;
+ }
+ // Copy remaining bytes of a multibyte character.
+ const int mb_len = utfc_ptr2len(src) - 1;
+ if (mb_len > 0 && len >= mb_len) {
+ for (int i = 0; i < mb_len; i++) {
+ len--;
+ src++;
+ if (dest != NULL) {
+ *dest++ = *src;
}
+ }
}
}
if (dest != NULL)
@@ -5321,7 +5418,7 @@ int get_literal(void)
if (got_int)
return Ctrl_C;
- ++no_mapping; /* don't map the next key hits */
+ no_mapping++; // don't map the next key hits
cc = 0;
i = 0;
for (;; ) {
@@ -5359,20 +5456,23 @@ int get_literal(void)
if (cc > 255
&& unicode == 0
)
- cc = 255; /* limit range to 0-255 */
+ cc = 255; // limit range to 0-255
nc = 0;
- if (hex) { /* hex: up to two chars */
- if (i >= 2)
+ if (hex) { // hex: up to two chars
+ if (i >= 2) {
break;
- } else if (unicode) { /* Unicode: up to four or eight chars */
- if ((unicode == 'u' && i >= 4) || (unicode == 'U' && i >= 8))
+ }
+ } else if (unicode) { // Unicode: up to four or eight chars
+ if ((unicode == 'u' && i >= 4) || (unicode == 'U' && i >= 8)) {
break;
- } else if (i >= 3) /* decimal or octal: up to three chars */
+ }
+ } else if (i >= 3) { // decimal or octal: up to three chars
break;
+ }
}
- if (i == 0) { /* no number entered */
- if (nc == K_ZERO) { /* NUL is stored as NL */
+ if (i == 0) { // no number entered
+ if (nc == K_ZERO) { // NUL is stored as NL
cc = '\n';
nc = 0;
} else {
@@ -5388,7 +5488,7 @@ int get_literal(void)
--no_mapping;
if (nc)
vungetc(nc);
- got_int = FALSE; /* CTRL-C typed after CTRL-V is not an interrupt */
+ got_int = false; // CTRL-C typed after CTRL-V is not an interrupt
return cc;
}
@@ -5451,11 +5551,10 @@ static void insert_special(int c, int allow_modmask, int ctrlv)
* INSCHAR_DO_COM - format comments
* INSCHAR_COM_LIST - format comments with num list or 2nd line indent
*/
-void
-insertchar (
- int c, /* character to insert or NUL */
- int flags, /* INSCHAR_FORMAT, etc. */
- int second_indent /* indent for second line if >= 0 */
+void insertchar(
+ int c, // character to insert or NUL
+ int flags, // INSCHAR_FORMAT, etc.
+ int second_indent // indent for second line if >= 0
)
{
int textwidth;
@@ -5491,27 +5590,27 @@ insertchar (
|| ((!has_format_option(FO_INS_LONG)
|| Insstart_textlen <= (colnr_T)textwidth)
&& (!fo_ins_blank
- || Insstart_blank_vcol <= (colnr_T)textwidth
- )))))) {
- /* Format with 'formatexpr' when it's set. Use internal formatting
- * when 'formatexpr' isn't set or it returns non-zero. */
- int do_internal = TRUE;
+ || Insstart_blank_vcol <= (colnr_T)textwidth)))))) {
+ // Format with 'formatexpr' when it's set. Use internal formatting
+ // when 'formatexpr' isn't set or it returns non-zero.
+ bool do_internal = true;
colnr_T virtcol = get_nolist_virtcol()
+ char2cells(c != NUL ? c : gchar_cursor());
if (*curbuf->b_p_fex != NUL && (flags & INSCHAR_NO_FEX) == 0
&& (force_format || virtcol > (colnr_T)textwidth)) {
do_internal = (fex_format(curwin->w_cursor.lnum, 1L, c) != 0);
- /* It may be required to save for undo again, e.g. when setline()
- * was called. */
- ins_need_undo = TRUE;
+ // It may be required to save for undo again, e.g. when setline()
+ // was called.
+ ins_need_undo = true;
}
if (do_internal)
internal_format(textwidth, second_indent, flags, c == NUL, c);
}
- if (c == NUL) /* only formatting was wanted */
+ if (c == NUL) { // only formatting was wanted
return;
+ }
// Check whether this character should end a comment.
if (did_ai && c == end_comment_pending) {
@@ -5615,8 +5714,8 @@ insertchar (
buf[i++] = c;
}
- do_digraph(-1); /* clear digraphs */
- do_digraph(buf[i-1]); /* may be the start of a digraph */
+ do_digraph(-1); // clear digraphs
+ do_digraph(buf[i-1]); // may be the start of a digraph
buf[i] = NUL;
ins_str(buf);
if (flags & INSCHAR_CTRLV) {
@@ -5659,7 +5758,7 @@ internal_format (
int second_indent,
int flags,
int format_only,
- int c /* character to be inserted (can be NUL) */
+ int c // character to be inserted (can be NUL)
)
{
int cc;
@@ -5670,7 +5769,7 @@ internal_format (
int fo_white_par = has_format_option(FO_WHITE_PAR);
int first_line = TRUE;
colnr_T leader_len;
- int no_leader = FALSE;
+ bool no_leader = false;
int do_comments = (flags & INSCHAR_DO_COM);
int has_lbr = curwin->w_p_lbr;
@@ -5695,10 +5794,10 @@ internal_format (
* Repeat breaking lines, until the current line is not too long.
*/
while (!got_int) {
- int startcol; /* Cursor column at entry */
- int wantcol; /* column at textwidth border */
- int foundcol; /* column for start of spaces */
- int end_foundcol = 0; /* column for start of word */
+ int startcol; // Cursor column at entry
+ int wantcol; // column at textwidth border
+ int foundcol; // column for start of spaces
+ int end_foundcol = 0; // column for start of word
colnr_T len;
colnr_T virtcol;
int orig_col = 0;
@@ -5711,33 +5810,37 @@ internal_format (
if (virtcol <= (colnr_T)textwidth)
break;
- if (no_leader)
- do_comments = FALSE;
- else if (!(flags & INSCHAR_FORMAT)
- && has_format_option(FO_WRAP_COMS))
- do_comments = TRUE;
+ if (no_leader) {
+ do_comments = false;
+ } else if (!(flags & INSCHAR_FORMAT)
+ && has_format_option(FO_WRAP_COMS)) {
+ do_comments = true;
+ }
- /* Don't break until after the comment leader */
- if (do_comments)
- leader_len = get_leader_len(get_cursor_line_ptr(), NULL, FALSE, TRUE);
- else
+ // Don't break until after the comment leader
+ if (do_comments) {
+ leader_len = get_leader_len(get_cursor_line_ptr(), NULL, false, true);
+ } else {
leader_len = 0;
+ }
- /* If the line doesn't start with a comment leader, then don't
- * start one in a following broken line. Avoids that a %word
- * moved to the start of the next line causes all following lines
- * to start with %. */
- if (leader_len == 0)
- no_leader = TRUE;
+ // If the line doesn't start with a comment leader, then don't
+ // start one in a following broken line. Avoids that a %word
+ // moved to the start of the next line causes all following lines
+ // to start with %.
+ if (leader_len == 0) {
+ no_leader = true;
+ }
if (!(flags & INSCHAR_FORMAT)
&& leader_len == 0
- && !has_format_option(FO_WRAP))
-
+ && !has_format_option(FO_WRAP)) {
break;
- if ((startcol = curwin->w_cursor.col) == 0)
+ }
+ if ((startcol = curwin->w_cursor.col) == 0) {
break;
+ }
- /* find column of textwidth border */
+ // find column of textwidth border
coladvance((colnr_T)textwidth);
wantcol = curwin->w_cursor.col;
@@ -5757,7 +5860,7 @@ internal_format (
else
cc = gchar_cursor();
if (WHITECHAR(cc)) {
- /* remember position of blank just before text */
+ // remember position of blank just before text
end_col = curwin->w_cursor.col;
// find start of sequence of blanks
@@ -5788,18 +5891,21 @@ internal_format (
}
if (has_format_option(FO_ONE_LETTER)) {
- /* do not break after one-letter words */
- if (curwin->w_cursor.col == 0)
- break; /* one-letter word at begin */
- /* do not break "#a b" when 'tw' is 2 */
- if (curwin->w_cursor.col <= leader_len)
+ // do not break after one-letter words
+ if (curwin->w_cursor.col == 0) {
+ break; // one-letter word at begin
+ }
+ // do not break "#a b" when 'tw' is 2
+ if (curwin->w_cursor.col <= leader_len) {
break;
+ }
col = curwin->w_cursor.col;
dec_cursor();
cc = gchar_cursor();
- if (WHITECHAR(cc))
- continue; /* one-letter, continue */
+ if (WHITECHAR(cc)) {
+ continue; // one-letter, continue
+ }
curwin->w_cursor.col = col;
}
@@ -5810,14 +5916,15 @@ internal_format (
if (curwin->w_cursor.col <= (colnr_T)wantcol)
break;
} else if (cc >= 0x100 && fo_multibyte) {
- /* Break after or before a multi-byte character. */
+ // Break after or before a multi-byte character.
if (curwin->w_cursor.col != startcol) {
- /* Don't break until after the comment leader */
- if (curwin->w_cursor.col < leader_len)
+ // Don't break until after the comment leader
+ if (curwin->w_cursor.col < leader_len) {
break;
+ }
col = curwin->w_cursor.col;
inc_cursor();
- /* Don't change end_foundcol if already set. */
+ // Don't change end_foundcol if already set.
if (foundcol != curwin->w_cursor.col) {
foundcol = curwin->w_cursor.col;
end_foundcol = foundcol;
@@ -5835,11 +5942,13 @@ internal_format (
dec_cursor();
cc = gchar_cursor();
- if (WHITECHAR(cc))
- continue; /* break with space */
- /* Don't break until after the comment leader */
- if (curwin->w_cursor.col < leader_len)
+ if (WHITECHAR(cc)) {
+ continue; // break with space
+ }
+ // Don't break until after the comment leader
+ if (curwin->w_cursor.col < leader_len) {
break;
+ }
curwin->w_cursor.col = col;
@@ -5853,12 +5962,12 @@ internal_format (
dec_cursor();
}
- if (foundcol == 0) { /* no spaces, cannot break line */
+ if (foundcol == 0) { // no spaces, cannot break line
curwin->w_cursor.col = startcol;
break;
}
- /* Going to break the line, remove any "$" now. */
+ // Going to break the line, remove any "$" now.
undisplay_dollar();
/*
@@ -5866,10 +5975,11 @@ internal_format (
* stack functions. VREPLACE does not use this, and backspaces
* over the text instead.
*/
- if (State & VREPLACE_FLAG)
- orig_col = startcol; /* Will start backspacing from here */
- else
+ if (State & VREPLACE_FLAG) {
+ orig_col = startcol; // Will start backspacing from here
+ } else {
replace_offset = startcol - end_foundcol;
+ }
/*
* adjust startcol for spaces that will be deleted and
@@ -5892,13 +6002,15 @@ internal_format (
curwin->w_cursor.col = orig_col;
saved_text[startcol] = NUL;
- /* Backspace over characters that will move to the next line */
- if (!fo_white_par)
+ // Backspace over characters that will move to the next line
+ if (!fo_white_par) {
backspace_until_column(foundcol);
+ }
} else {
- /* put cursor after pos. to break line */
- if (!fo_white_par)
+ // put cursor after pos. to break line
+ if (!fo_white_par) {
curwin->w_cursor.col = foundcol;
+ }
}
/*
@@ -5916,32 +6028,29 @@ internal_format (
replace_offset = 0;
if (first_line) {
if (!(flags & INSCHAR_COM_LIST)) {
- /*
- * This section is for auto-wrap of numeric lists. When not
- * in insert mode (i.e. format_lines()), the INSCHAR_COM_LIST
- * flag will be set and open_line() will handle it (as seen
- * above). The code here (and in get_number_indent()) will
- * recognize comments if needed...
- */
- if (second_indent < 0 && has_format_option(FO_Q_NUMBER))
- second_indent =
- get_number_indent(curwin->w_cursor.lnum - 1);
+ // This section is for auto-wrap of numeric lists. When not
+ // in insert mode (i.e. format_lines()), the INSCHAR_COM_LIST
+ // flag will be set and open_line() will handle it (as seen
+ // above). The code here (and in get_number_indent()) will
+ // recognize comments if needed...
+ if (second_indent < 0 && has_format_option(FO_Q_NUMBER)) {
+ second_indent = get_number_indent(curwin->w_cursor.lnum - 1);
+ }
if (second_indent >= 0) {
- if (State & VREPLACE_FLAG)
- change_indent(INDENT_SET, second_indent,
- FALSE, NUL, TRUE);
- else if (leader_len > 0 && second_indent - leader_len > 0) {
- int i;
+ if (State & VREPLACE_FLAG) {
+ change_indent(INDENT_SET, second_indent, false, NUL, true);
+ } else if (leader_len > 0 && second_indent - leader_len > 0) {
int padding = second_indent - leader_len;
- /* We started at the first_line of a numbered list
- * that has a comment. the open_line() function has
- * inserted the proper comment leader and positioned
- * the cursor at the end of the split line. Now we
- * add the additional whitespace needed after the
- * comment leader for the numbered list. */
- for (i = 0; i < padding; i++)
+ // We started at the first_line of a numbered list
+ // that has a comment. the open_line() function has
+ // inserted the proper comment leader and positioned
+ // the cursor at the end of the split line. Now we
+ // add the additional whitespace needed after the
+ // comment leader for the numbered list.
+ for (int i = 0; i < padding; i++) {
ins_str((char_u *)" ");
+ }
changed_bytes(curwin->w_cursor.lnum, leader_len);
} else {
(void)set_indent(second_indent, SIN_CHANGED);
@@ -5979,8 +6088,9 @@ internal_format (
line_breakcheck();
}
- if (save_char != NUL) /* put back space after cursor */
+ if (save_char != NUL) { // put back space after cursor
pchar_cursor(save_char);
+ }
curwin->w_p_lbr = has_lbr;
@@ -5997,10 +6107,9 @@ internal_format (
* The caller must have saved the cursor line for undo, following ones will be
* saved here.
*/
-void
-auto_format (
- int trailblank, /* when TRUE also format with trailing blank */
- int prev_line /* may start in previous line */
+void auto_format(
+ bool trailblank, // when true also format with trailing blank
+ bool prev_line // may start in previous line
)
{
pos_T pos;
@@ -6019,11 +6128,11 @@ auto_format (
// may remove added space
check_auto_format(false);
- /* Don't format in Insert mode when the cursor is on a trailing blank, the
- * user might insert normal text next. Also skip formatting when "1" is
- * in 'formatoptions' and there is a single character before the cursor.
- * Otherwise the line would be broken and when typing another non-white
- * next they are not joined back together. */
+ // Don't format in Insert mode when the cursor is on a trailing blank, the
+ // user might insert normal text next. Also skip formatting when "1" is
+ // in 'formatoptions' and there is a single character before the cursor.
+ // Otherwise the line would be broken and when typing another non-white
+ // next they are not joined back together.
wasatend = (pos.col == (colnr_T)STRLEN(old));
if (*old != NUL && !trailblank && wasatend) {
dec_cursor();
@@ -6066,16 +6175,16 @@ auto_format (
saved_cursor.lnum = 0;
if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) {
- /* "cannot happen" */
+ // "cannot happen"
curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
coladvance((colnr_T)MAXCOL);
} else
check_cursor_col();
- /* Insert mode: If the cursor is now after the end of the line while it
- * previously wasn't, the line was broken. Because of the rule above we
- * need to add a space when 'w' is in 'formatoptions' to keep a paragraph
- * formatted. */
+ // Insert mode: If the cursor is now after the end of the line while it
+ // previously wasn't, the line was broken. Because of the rule above we
+ // need to add a space when 'w' is in 'formatoptions' to keep a paragraph
+ // formatted.
if (!wasatend && has_format_option(FO_WHITE_PAR)) {
new = get_cursor_line_ptr();
len = (colnr_T)STRLEN(new);
@@ -6134,22 +6243,21 @@ static void check_auto_format(
* if invalid value, use 0.
* Set default to window width (maximum 79) for "gq" operator.
*/
-int
-comp_textwidth (
- int ff /* force formatting (for "gq" command) */
+int comp_textwidth(
+ int ff // force formatting (for "gq" command)
)
{
int textwidth;
textwidth = curbuf->b_p_tw;
if (textwidth == 0 && curbuf->b_p_wm) {
- /* The width is the window width minus 'wrapmargin' minus all the
- * things that add to the margin. */
+ // The width is the window width minus 'wrapmargin' minus all the
+ // things that add to the margin.
textwidth = curwin->w_width_inner - curbuf->b_p_wm;
if (cmdwin_type != 0) {
textwidth -= 1;
}
- textwidth -= curwin->w_p_fdc;
+ textwidth -= win_fdccol_count(curwin);
textwidth -= win_signcol_count(curwin);
if (curwin->w_p_nu || curwin->w_p_rnu)
@@ -6185,7 +6293,9 @@ static void redo_literal(int c)
// start_arrow() is called when an arrow key is used in insert mode.
// For undo/redo it resembles hitting the <ESC> key.
-static void start_arrow(pos_T *end_insert_pos /* can be NULL */)
+static void start_arrow(
+ pos_T *end_insert_pos // can be NULL
+)
{
start_arrow_common(end_insert_pos, true);
}
@@ -6258,8 +6368,8 @@ int stop_arrow(void)
Insstart_textlen = (colnr_T)linetabsize(get_cursor_line_ptr());
if (u_save_cursor() == OK) {
- arrow_used = FALSE;
- ins_need_undo = FALSE;
+ arrow_used = false;
+ ins_need_undo = false;
}
ai_col = 0;
if (State & VREPLACE_FLAG) {
@@ -6270,11 +6380,12 @@ int stop_arrow(void)
AppendToRedobuff("1i"); // Pretend we start an insertion.
new_insert_skip = 2;
} else if (ins_need_undo) {
- if (u_save_cursor() == OK)
- ins_need_undo = FALSE;
+ if (u_save_cursor() == OK) {
+ ins_need_undo = false;
+ }
}
- /* Always open fold at the cursor line when inserting something. */
+ // Always open fold at the cursor line when inserting something.
foldOpenCursor();
return arrow_used || ins_need_undo ? FAIL : OK;
@@ -6288,15 +6399,15 @@ int stop_arrow(void)
static void
stop_insert (
pos_T *end_insert_pos,
- int esc, /* called by ins_esc() */
- int nomove /* <c-\><c-o>, don't move cursor */
+ int esc, // called by ins_esc()
+ int nomove // <c-\><c-o>, don't move cursor
)
{
int cc;
char_u *ptr;
stop_redo_ins();
- replace_flush(); /* abandon replace stack */
+ replace_flush(); // abandon replace stack
/*
* Save the inserted text for later redo with ^@ and CTRL-A.
@@ -6313,16 +6424,16 @@ stop_insert (
xfree(ptr);
if (!arrow_used && end_insert_pos != NULL) {
- /* Auto-format now. It may seem strange to do this when stopping an
- * insertion (or moving the cursor), but it's required when appending
- * a line and having it end in a space. But only do it when something
- * was actually inserted, otherwise undo won't work. */
+ // Auto-format now. It may seem strange to do this when stopping an
+ // insertion (or moving the cursor), but it's required when appending
+ // a line and having it end in a space. But only do it when something
+ // was actually inserted, otherwise undo won't work.
if (!ins_need_undo && has_format_option(FO_AUTO)) {
pos_T tpos = curwin->w_cursor;
- /* When the cursor is at the end of the line after a space the
- * formatting will move it to the following word. Avoid that by
- * moving the cursor onto the space. */
+ // When the cursor is at the end of the line after a space the
+ // formatting will move it to the following word. Avoid that by
+ // moving the cursor onto the space.
cc = 'x';
if (curwin->w_cursor.col > 0 && gchar_cursor() == NUL) {
dec_cursor();
@@ -6348,11 +6459,11 @@ stop_insert (
// If a space was inserted for auto-formatting, remove it now.
check_auto_format(true);
- /* If we just did an auto-indent, remove the white space from the end
- * of the line, and put the cursor back.
- * Do this when ESC was used or moving the cursor up/down.
- * Check for the old position still being valid, just in case the text
- * got changed unexpectedly. */
+ // If we just did an auto-indent, remove the white space from the end
+ // of the line, and put the cursor back.
+ // Do this when ESC was used or moving the cursor up/down.
+ // Check for the old position still being valid, just in case the text
+ // got changed unexpectedly.
if (!nomove && did_ai && (esc || (vim_strchr(p_cpo, CPO_INDENT) == NULL
&& curwin->w_cursor.lnum !=
end_insert_pos->lnum))
@@ -6360,7 +6471,7 @@ stop_insert (
pos_T tpos = curwin->w_cursor;
curwin->w_cursor = *end_insert_pos;
- check_cursor_col(); /* make sure it is not past the line */
+ check_cursor_col(); // make sure it is not past the line
for (;; ) {
if (gchar_cursor() == NUL && curwin->w_cursor.col > 0)
--curwin->w_cursor.col;
@@ -6372,10 +6483,10 @@ stop_insert (
break; // should not happen
}
}
- if (curwin->w_cursor.lnum != tpos.lnum)
+ if (curwin->w_cursor.lnum != tpos.lnum) {
curwin->w_cursor = tpos;
- else {
- /* reset tpos, could have been invalidated in the loop above */
+ } else {
+ // reset tpos, could have been invalidated in the loop above
tpos = curwin->w_cursor;
tpos.col++;
if (cc != NUL && gchar_pos(&tpos) == NUL) {
@@ -6383,8 +6494,8 @@ stop_insert (
}
}
- /* <C-S-Right> may have started Visual mode, adjust the position for
- * deleted characters. */
+ // <C-S-Right> may have started Visual mode, adjust the position for
+ // deleted characters.
if (VIsual_active && VIsual.lnum == curwin->w_cursor.lnum) {
int len = (int)STRLEN(get_cursor_line_ptr());
@@ -6400,8 +6511,8 @@ stop_insert (
can_si = false;
can_si_back = false;
- /* Set '[ and '] to the inserted text. When end_insert_pos is NULL we are
- * now in a different buffer. */
+ // Set '[ and '] to the inserted text. When end_insert_pos is NULL we are
+ // now in a different buffer.
if (end_insert_pos != NULL) {
curbuf->b_op_start = Insstart;
curbuf->b_op_start_orig = Insstart_orig;
@@ -6420,9 +6531,10 @@ void set_last_insert(int c)
xfree(last_insert);
last_insert = xmalloc(MB_MAXBYTES * 3 + 5);
s = last_insert;
- /* Use the CTRL-V only when entering a special char */
- if (c < ' ' || c == DEL)
+ // Use the CTRL-V only when entering a special char
+ if (c < ' ' || c == DEL) {
*s++ = Ctrl_V;
+ }
s = add_char2buf(c, s);
*s++ = ESC;
*s++ = NUL;
@@ -6508,7 +6620,7 @@ int oneright(void)
if (virtual_active()) {
pos_T prevpos = curwin->w_cursor;
- /* Adjust for multi-wide char (excluding TAB) */
+ // Adjust for multi-wide char (excluding TAB)
ptr = get_cursor_pos_ptr();
coladvance(getviscol() + ((*ptr != TAB && vim_isprintc(utf_ptr2char(ptr))) ?
ptr2cells(ptr) : 1));
@@ -6519,20 +6631,18 @@ int oneright(void)
}
ptr = get_cursor_pos_ptr();
- if (*ptr == NUL)
- return FAIL; /* already at the very end */
+ if (*ptr == NUL) {
+ return FAIL; // already at the very end
+ }
- if (has_mbyte)
- l = (*mb_ptr2len)(ptr);
- else
- l = 1;
+ l = utfc_ptr2len(ptr);
- /* move "l" bytes right, but don't end up on the NUL, unless 'virtualedit'
- * contains "onemore". */
+ // move "l" bytes right, but don't end up on the NUL, unless 'virtualedit'
+ // contains "onemore".
if (ptr[l] == NUL
- && (ve_flags & VE_ONEMORE) == 0
- )
+ && (ve_flags & VE_ONEMORE) == 0) {
return FAIL;
+ }
curwin->w_cursor.col += l;
curwin->w_set_curswant = TRUE;
@@ -6548,25 +6658,23 @@ int oneleft(void)
if (v == 0)
return FAIL;
- /* We might get stuck on 'showbreak', skip over it. */
+ // We might get stuck on 'showbreak', skip over it.
width = 1;
for (;; ) {
coladvance(v - width);
- /* getviscol() is slow, skip it when 'showbreak' is empty,
- 'breakindent' is not set and there are no multi-byte
- characters */
- if ((*p_sbr == NUL
- && !curwin->w_p_bri
- && !has_mbyte
- ) || getviscol() < v)
+ // getviscol() is slow, skip it when 'showbreak' is empty,
+ // 'breakindent' is not set and there are no multi-byte
+ // characters
+ if (getviscol() < v) {
break;
- ++width;
+ }
+ width++;
}
if (curwin->w_cursor.coladd == 1) {
char_u *ptr;
- /* Adjust for multi-wide char (not a TAB) */
+ // Adjust for multi-wide char (not a TAB)
ptr = get_cursor_pos_ptr();
if (*ptr != TAB && vim_isprintc(utf_ptr2char(ptr))
&& ptr2cells(ptr) > 1) {
@@ -6584,17 +6692,16 @@ int oneleft(void)
curwin->w_set_curswant = TRUE;
--curwin->w_cursor.col;
- /* if the character on the left of the current cursor is a multi-byte
- * character, move to its first byte */
- if (has_mbyte)
- mb_adjust_cursor();
+ // if the character on the left of the current cursor is a multi-byte
+ // character, move to its first byte
+ mb_adjust_cursor();
return OK;
}
int
cursor_up (
long n,
- int upd_topline /* When TRUE: update topline */
+ int upd_topline // When TRUE: update topline
)
{
linenr_T lnum;
@@ -6612,19 +6719,21 @@ cursor_up (
/*
* Count each sequence of folded lines as one logical line.
*/
- /* go to the start of the current fold */
+ // go to the start of the current fold
(void)hasFolding(lnum, &lnum, NULL);
while (n--) {
- /* move up one line */
- --lnum;
- if (lnum <= 1)
+ // move up one line
+ lnum--;
+ if (lnum <= 1) {
break;
- /* If we entered a fold, move to the beginning, unless in
- * Insert mode or when 'foldopen' contains "all": it will open
- * in a moment. */
- if (n > 0 || !((State & INSERT) || (fdo_flags & FDO_ALL)))
+ }
+ // If we entered a fold, move to the beginning, unless in
+ // Insert mode or when 'foldopen' contains "all": it will open
+ // in a moment.
+ if (n > 0 || !((State & INSERT) || (fdo_flags & FDO_ALL))) {
(void)hasFolding(lnum, &lnum, NULL);
+ }
}
if (lnum < 1)
lnum = 1;
@@ -6633,11 +6742,12 @@ cursor_up (
curwin->w_cursor.lnum = lnum;
}
- /* try to advance to the column we want to be at */
+ // try to advance to the column we want to be at
coladvance(curwin->w_curswant);
- if (upd_topline)
- update_topline(); /* make sure curwin->w_topline is valid */
+ if (upd_topline) {
+ update_topline(); // make sure curwin->w_topline is valid
+ }
return OK;
}
@@ -6648,14 +6758,14 @@ cursor_up (
int
cursor_down (
long n,
- int upd_topline /* When TRUE: update topline */
+ int upd_topline // When TRUE: update topline
)
{
linenr_T lnum;
if (n > 0) {
lnum = curwin->w_cursor.lnum;
- /* Move to last line of fold, will fail if it's the end-of-file. */
+ // Move to last line of fold, will fail if it's the end-of-file.
(void)hasFolding(lnum, NULL, &lnum);
// This fails if the cursor is already in the last line.
@@ -6667,7 +6777,7 @@ cursor_down (
else if (hasAnyFolding(curwin)) {
linenr_T last;
- /* count each sequence of folded lines as one logical line */
+ // count each sequence of folded lines as one logical line
while (n--) {
if (hasFolding(lnum, NULL, &last))
lnum = last + 1;
@@ -6683,11 +6793,12 @@ cursor_down (
curwin->w_cursor.lnum = lnum;
}
- /* try to advance to the column we want to be at */
+ // try to advance to the column we want to be at
coladvance(curwin->w_curswant);
- if (upd_topline)
- update_topline(); /* make sure curwin->w_topline is valid */
+ if (upd_topline) {
+ update_topline(); // make sure curwin->w_topline is valid
+ }
return OK;
}
@@ -6697,11 +6808,10 @@ cursor_down (
* Last_insert actually is a copy of the redo buffer, so we
* first have to remove the command.
*/
-int
-stuff_inserted (
- int c, /* Command character to be inserted */
- long count, /* Repeat this many times */
- int no_esc /* Don't add an ESC at the end */
+int stuff_inserted(
+ int c, // Command character to be inserted
+ long count, // Repeat this many times
+ int no_esc // Don't add an ESC at the end
)
{
char_u *esc_ptr;
@@ -6715,18 +6825,18 @@ stuff_inserted (
return FAIL;
}
- /* may want to stuff the command character, to start Insert mode */
- if (c != NUL)
+ // may want to stuff the command character, to start Insert mode
+ if (c != NUL) {
stuffcharReadbuff(c);
+ }
if ((esc_ptr = STRRCHR(ptr, ESC)) != NULL) {
// remove the ESC.
*esc_ptr = NUL;
}
- /* when the last char is either "0" or "^" it will be quoted if no ESC
- * comes after it OR if it will inserted more than once and "ptr"
- * starts with ^D. -- Acevedo
- */
+ // when the last char is either "0" or "^" it will be quoted if no ESC
+ // comes after it OR if it will inserted more than once and "ptr"
+ // starts with ^D. -- Acevedo
last_ptr = (esc_ptr ? esc_ptr : ptr + STRLEN(ptr)) - 1;
if (last_ptr >= ptr && (*last_ptr == '0' || *last_ptr == '^')
&& (no_esc || (*ptr == Ctrl_D && count > 1))) {
@@ -6747,12 +6857,14 @@ stuff_inserted (
if (last)
*last_ptr = last;
- if (esc_ptr != NULL)
- *esc_ptr = ESC; /* put the ESC back */
+ if (esc_ptr != NULL) {
+ *esc_ptr = ESC; // put the ESC back
+ }
- /* may want to stuff a trailing ESC, to get out of Insert mode */
- if (!no_esc)
+ // may want to stuff a trailing ESC, to get out of Insert mode
+ if (!no_esc) {
stuffcharReadbuff(ESC);
+ }
return OK;
}
@@ -6777,8 +6889,9 @@ char_u *get_last_insert_save(void)
return NULL;
s = vim_strsave(last_insert + last_insert_skip);
len = (int)STRLEN(s);
- if (len > 0 && s[len - 1] == ESC) /* remove trailing ESC */
+ if (len > 0 && s[len - 1] == ESC) { // remove trailing ESC
s[len - 1] = NUL;
+ }
return s;
}
@@ -6820,8 +6933,8 @@ static bool echeck_abbr(int c)
*/
static char_u *replace_stack = NULL;
-static ssize_t replace_stack_nr = 0; /* next entry in replace stack */
-static ssize_t replace_stack_len = 0; /* max. number of entries */
+static ssize_t replace_stack_nr = 0; // next entry in replace stack
+static ssize_t replace_stack_len = 0; // max. number of entries
/// Push character that is replaced onto the the replace stack.
///
@@ -6875,9 +6988,8 @@ static int replace_pop(void)
* Join the top two items on the replace stack. This removes to "off"'th NUL
* encountered.
*/
-static void
-replace_join (
- int off /* offset for which NUL to remove */
+static void replace_join(
+ int off // offset for which NUL to remove
)
{
int i;
@@ -6900,7 +7012,7 @@ static void replace_pop_ins(void)
int cc;
int oldState = State;
- State = NORMAL; /* don't want REPLACE here */
+ State = NORMAL; // don't want REPLACE here
while ((cc = replace_pop()) > 0) {
mb_replace_pop_ins(cc);
dec_cursor();
@@ -6919,7 +7031,7 @@ static void mb_replace_pop_ins(int cc)
int i;
int c;
- if (has_mbyte && (n = MB_BYTE2LEN(cc)) > 1) {
+ if ((n = MB_BYTE2LEN(cc)) > 1) {
buf[0] = cc;
for (i = 1; i < n; ++i)
buf[i] = replace_pop();
@@ -6928,31 +7040,33 @@ static void mb_replace_pop_ins(int cc)
ins_char(cc);
}
- if (enc_utf8)
- /* Handle composing chars. */
- for (;; ) {
- c = replace_pop();
- if (c == -1) /* stack empty */
- break;
- if ((n = MB_BYTE2LEN(c)) == 1) {
- /* Not a multi-byte char, put it back. */
- replace_push(c);
- break;
+ // Handle composing chars.
+ for (;; ) {
+ c = replace_pop();
+ if (c == -1) { // stack empty
+ break;
+ }
+ if ((n = MB_BYTE2LEN(c)) == 1) {
+ // Not a multi-byte char, put it back.
+ replace_push(c);
+ break;
+ } else {
+ buf[0] = c;
+ assert(n > 1);
+ for (i = 1; i < n; i++) {
+ buf[i] = replace_pop();
+ }
+ if (utf_iscomposing(utf_ptr2char(buf))) {
+ ins_bytes_len(buf, n);
} else {
- buf[0] = c;
- assert(n > 1);
- for (i = 1; i < n; ++i)
- buf[i] = replace_pop();
- if (utf_iscomposing(utf_ptr2char(buf)))
- ins_bytes_len(buf, n);
- else {
- /* Not a composing char, put it back. */
- for (i = n - 1; i >= 0; --i)
- replace_push(buf[i]);
- break;
+ // Not a composing char, put it back.
+ for (i = n - 1; i >= 0; i--) {
+ replace_push(buf[i]);
}
+ break;
}
}
+ }
}
/*
@@ -6990,8 +7104,8 @@ static void replace_do_bs(int limit_col)
cc = replace_pop();
if (cc > 0) {
if (l_State & VREPLACE_FLAG) {
- /* Get the number of screen cells used by the character we are
- * going to delete. */
+ // Get the number of screen cells used by the character we are
+ // going to delete.
getvcol(curwin, &curwin->w_cursor, NULL, &start_vcol, NULL);
orig_vcols = chartabsize(get_cursor_pos_ptr(), start_vcol);
}
@@ -7008,7 +7122,7 @@ static void replace_do_bs(int limit_col)
replace_pop_ins();
if (l_State & VREPLACE_FLAG) {
- /* Get the number of screen cells used by the inserted characters */
+ // Get the number of screen cells used by the inserted characters
p = get_cursor_pos_ptr();
ins_len = (int)STRLEN(p) - orig_len;
vcol = start_vcol;
@@ -7018,8 +7132,8 @@ static void replace_do_bs(int limit_col)
}
vcol -= start_vcol;
- /* Delete spaces that were inserted after the cursor to keep the
- * text aligned. */
+ // Delete spaces that were inserted after the cursor to keep the
+ // text aligned.
curwin->w_cursor.col += ins_len;
while (vcol > orig_vcols && gchar_cursor() == ' ') {
del_char(false);
@@ -7028,7 +7142,7 @@ static void replace_do_bs(int limit_col)
curwin->w_cursor.col -= ins_len;
}
- /* mark the buffer as changed and prepare for displaying */
+ // mark the buffer as changed and prepare for displaying
changed_bytes(curwin->w_cursor.lnum, curwin->w_cursor.col);
} else if (cc == 0)
(void)del_char_after_col(limit_col);
@@ -7096,10 +7210,11 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty)
return false;
}
- if (*curbuf->b_p_inde != NUL)
- look = curbuf->b_p_indk; /* 'indentexpr' set: use 'indentkeys' */
- else
- look = curbuf->b_p_cink; /* 'indentexpr' empty: use 'cinkeys' */
+ if (*curbuf->b_p_inde != NUL) {
+ look = curbuf->b_p_indk; // 'indentexpr' set: use 'indentkeys'
+ } else {
+ look = curbuf->b_p_cink; // 'indentexpr' empty: use 'cinkeys'
+ }
while (*look) {
/*
* Find out if we want to try a match with this key, depending on
@@ -7298,10 +7413,12 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty)
*/
int hkmap(int c)
{
- if (p_hkmapp) { /* phonetic mapping, by Ilya Dogolazky */
- enum {hALEF=0, BET, GIMEL, DALET, HEI, VAV, ZAIN, HET, TET, IUD,
- KAFsofit, hKAF, LAMED, MEMsofit, MEM, NUNsofit, NUN, SAMEH, AIN,
- PEIsofit, PEI, ZADIsofit, ZADI, KOF, RESH, hSHIN, TAV};
+ if (p_hkmapp) { // phonetic mapping, by Ilya Dogolazky
+ enum {
+ hALEF = 0, BET, GIMEL, DALET, HEI, VAV, ZAIN, HET, TET, IUD,
+ KAFsofit, hKAF, LAMED, MEMsofit, MEM, NUNsofit, NUN, SAMEH, AIN,
+ PEIsofit, PEI, ZADIsofit, ZADI, KOF, RESH, hSHIN, TAV
+ };
static char_u map[26] =
{(char_u)hALEF /*a*/, (char_u)BET /*b*/, (char_u)hKAF /*c*/,
(char_u)DALET /*d*/, (char_u)-1 /*e*/, (char_u)PEIsofit /*f*/,
@@ -7313,28 +7430,27 @@ int hkmap(int c)
(char_u)VAV /*v*/, (char_u)hSHIN /*w*/, (char_u)-1 /*x*/,
(char_u)AIN /*y*/, (char_u)ZADI /*z*/};
- if (c == 'N' || c == 'M' || c == 'P' || c == 'C' || c == 'Z')
+ if (c == 'N' || c == 'M' || c == 'P' || c == 'C' || c == 'Z') {
return (int)(map[CharOrd(c)] - 1 + p_aleph);
- /* '-1'='sofit' */
- else if (c == 'x')
+ } else if (c == 'x') { // '-1'='sofit'
return 'X';
- else if (c == 'q')
- return '\''; /* {geresh}={'} */
- else if (c == 246)
- return ' '; /* \"o --> ' ' for a german keyboard */
- else if (c == 228)
- return ' '; /* \"a --> ' ' -- / -- */
- else if (c == 252)
- return ' '; /* \"u --> ' ' -- / -- */
- /* NOTE: islower() does not do the right thing for us on Linux so we
- * do this the same was as 5.7 and previous, so it works correctly on
- * all systems. Specifically, the e.g. Delete and Arrow keys are
- * munged and won't work if e.g. searching for Hebrew text.
- */
- else if (c >= 'a' && c <= 'z')
+ } else if (c == 'q') {
+ return '\''; // {geresh}={'}
+ } else if (c == 246) {
+ return ' '; // \"o --> ' ' for a german keyboard
+ } else if (c == 228) {
+ return ' '; // \"a --> ' ' -- / --
+ } else if (c == 252) {
+ return ' '; // \"u --> ' ' -- / --
+ } else if (c >= 'a' && c <= 'z') {
+ // NOTE: islower() does not do the right thing for us on Linux so we
+ // do this the same was as 5.7 and previous, so it works correctly on
+ // all systems. Specifically, the e.g. Delete and Arrow keys are
+ // munged and won't work if e.g. searching for Hebrew text.
return (int)(map[CharOrdLow(c)] + p_aleph);
- else
+ } else {
return c;
+ }
} else {
switch (c) {
case '`': return ';';
@@ -7343,7 +7459,7 @@ int hkmap(int c)
case 'q': return '/';
case 'w': return '\'';
- /* Hebrew letters - set offset from 'a' */
+ // Hebrew letters - set offset from 'a'
case ',': c = '{'; break;
case '.': c = 'v'; break;
case ';': c = 't'; break;
@@ -7373,10 +7489,10 @@ static void ins_reg(void)
*/
pc_status = PC_STATUS_UNSET;
if (redrawing() && !char_avail()) {
- /* may need to redraw when no more chars available now */
- ins_redraw(FALSE);
+ // may need to redraw when no more chars available now
+ ins_redraw(false);
- edit_putchar('"', TRUE);
+ edit_putchar('"', true);
add_to_showcmd_c(Ctrl_R);
}
@@ -7389,7 +7505,7 @@ static void ins_reg(void)
regname = plain_vgetc();
LANGMAP_ADJUST(regname, TRUE);
if (regname == Ctrl_R || regname == Ctrl_O || regname == Ctrl_P) {
- /* Get a third key for literal register insertion */
+ // Get a third key for literal register insertion
literally = regname;
add_to_showcmd_c(literally);
regname = plain_vgetc();
@@ -7397,9 +7513,9 @@ static void ins_reg(void)
}
--no_mapping;
- /* Don't call u_sync() while typing the expression or giving an error
- * message for it. Only call it explicitly. */
- ++no_u_sync;
+ // Don't call u_sync() while typing the expression or giving an error
+ // message for it. Only call it explicitly.
+ no_u_sync++;
if (regname == '=') {
pos_T curpos = curwin->w_cursor;
@@ -7418,7 +7534,7 @@ static void ins_reg(void)
need_redraw = true; // remove the '"'
} else {
if (literally == Ctrl_O || literally == Ctrl_P) {
- /* Append the command to the redo buffer. */
+ // Append the command to the redo buffer.
AppendCharToRedobuff(Ctrl_R);
AppendCharToRedobuff(literally);
AppendCharToRedobuff(regname);
@@ -7435,19 +7551,22 @@ static void ins_reg(void)
need_redraw = true;
}
}
- --no_u_sync;
- if (u_sync_once == 1)
- ins_need_undo = TRUE;
+ no_u_sync--;
+ if (u_sync_once == 1) {
+ ins_need_undo = true;
+ }
u_sync_once = 0;
clear_showcmd();
- /* If the inserted register is empty, we need to remove the '"' */
- if (need_redraw || stuff_empty())
+ // If the inserted register is empty, we need to remove the '"'
+ if (need_redraw || stuff_empty()) {
edit_unputchar();
+ }
- /* Disallow starting Visual mode here, would get a weird mode. */
- if (!vis_active && VIsual_active)
+ // Disallow starting Visual mode here, would get a weird mode.
+ if (!vis_active && VIsual_active) {
end_visual_mode();
+ }
}
/*
@@ -7457,7 +7576,7 @@ static void ins_ctrl_g(void)
{
int c;
- /* Right after CTRL-X the cursor will be after the ruler. */
+ // Right after CTRL-X the cursor will be after the ruler.
setcursor();
/*
@@ -7468,24 +7587,25 @@ static void ins_ctrl_g(void)
c = plain_vgetc();
--no_mapping;
switch (c) {
- /* CTRL-G k and CTRL-G <Up>: cursor up to Insstart.col */
+ // CTRL-G k and CTRL-G <Up>: cursor up to Insstart.col
case K_UP:
case Ctrl_K:
case 'k': ins_up(TRUE);
break;
- /* CTRL-G j and CTRL-G <Down>: cursor down to Insstart.col */
+ // CTRL-G j and CTRL-G <Down>: cursor down to Insstart.col
case K_DOWN:
case Ctrl_J:
case 'j': ins_down(TRUE);
break;
- /* CTRL-G u: start new undoable edit */
- case 'u': u_sync(TRUE);
- ins_need_undo = TRUE;
+ // CTRL-G u: start new undoable edit
+ case 'u':
+ u_sync(true);
+ ins_need_undo = true;
- /* Need to reset Insstart, esp. because a BS that joins
- * a line to the previous one must save for undo. */
+ // Need to reset Insstart, esp. because a BS that joins
+ // a line to the previous one must save for undo.
update_Insstart_orig = false;
Insstart = curwin->w_cursor;
break;
@@ -7497,7 +7617,7 @@ static void ins_ctrl_g(void)
dont_sync_undo = kNone;
break;
- /* Unknown CTRL-G command, reserved for future expansion. */
+ // Unknown CTRL-G command, reserved for future expansion.
default: vim_beep(BO_CTRLG);
}
}
@@ -7519,7 +7639,7 @@ static void ins_ctrl_hat(void)
}
set_iminsert_global();
showmode();
- /* Show/unshow value of 'keymap' in status lines. */
+ // Show/unshow value of 'keymap' in status lines.
status_redraw_curbuf();
}
@@ -7560,10 +7680,11 @@ static bool ins_esc(long *count, int cmdchar, bool nomove)
*count = 0;
}
- if (--*count > 0) { /* repeat what was typed */
- /* Vi repeats the insert without replacing characters. */
- if (vim_strchr(p_cpo, CPO_REPLCNT) != NULL)
+ if (--*count > 0) { // repeat what was typed
+ // Vi repeats the insert without replacing characters.
+ if (vim_strchr(p_cpo, CPO_REPLCNT) != NULL) {
State &= ~REPLACE_FLAG;
+ }
(void)start_redo_ins();
if (cmdchar == 'r' || cmdchar == 'v') {
@@ -7578,12 +7699,13 @@ static bool ins_esc(long *count, int cmdchar, bool nomove)
undisplay_dollar();
}
- /* When an autoindent was removed, curswant stays after the
- * indent */
- if (restart_edit == NUL && (colnr_T)temp == curwin->w_cursor.col)
- curwin->w_set_curswant = TRUE;
+ // When an autoindent was removed, curswant stays after the
+ // indent
+ if (restart_edit == NUL && (colnr_T)temp == curwin->w_cursor.col) {
+ curwin->w_set_curswant = true;
+ }
- /* Remember the last Insert position in the '^ mark. */
+ // Remember the last Insert position in the '^ mark.
if (!cmdmod.keepjumps) {
RESET_FMARK(&curbuf->b_last_insert, curwin->w_cursor, curbuf->b_fnum);
}
@@ -7604,23 +7726,23 @@ static bool ins_esc(long *count, int cmdchar, bool nomove)
) {
if (curwin->w_cursor.coladd > 0 || ve_flags == VE_ALL) {
oneleft();
- if (restart_edit != NUL)
- ++curwin->w_cursor.coladd;
+ if (restart_edit != NUL) {
+ curwin->w_cursor.coladd++;
+ }
} else {
- --curwin->w_cursor.col;
- /* Correct cursor for multi-byte character. */
- if (has_mbyte)
- mb_adjust_cursor();
+ curwin->w_cursor.col--;
+ // Correct cursor for multi-byte character.
+ mb_adjust_cursor();
}
}
State = NORMAL;
- /* need to position cursor again (e.g. when on a TAB ) */
+ // need to position cursor again (e.g. when on a TAB )
changed_cline_bef_curs();
setmouse();
- ui_cursor_shape(); /* may show different cursor shape */
+ ui_cursor_shape(); // may show different cursor shape
// When recording or for CTRL-O, need to display the new mode.
// Otherwise remove the mode message.
@@ -7716,7 +7838,7 @@ static void ins_insert(int replaceState)
}
AppendCharToRedobuff(K_INS);
showmode();
- ui_cursor_shape(); /* may show different cursor shape */
+ ui_cursor_shape(); // may show different cursor shape
}
/*
@@ -7730,10 +7852,11 @@ static void ins_ctrl_o(void)
restart_edit = 'R';
else
restart_edit = 'I';
- if (virtual_active())
- ins_at_eol = FALSE; /* cursor always keeps its column */
- else
+ if (virtual_active()) {
+ ins_at_eol = false; // cursor always keeps its column
+ } else {
ins_at_eol = (gchar_cursor() == NUL);
+ }
}
/*
@@ -7815,11 +7938,12 @@ static void ins_bs_one(colnr_T *vcolp)
dec_cursor();
getvcol(curwin, &curwin->w_cursor, vcolp, NULL, NULL);
if (State & REPLACE_FLAG) {
- /* Don't delete characters before the insert point when in
- * Replace mode */
+ // Don't delete characters before the insert point when in
+ // Replace mode
if (curwin->w_cursor.lnum != Insstart.lnum
- || curwin->w_cursor.col >= Insstart.col)
+ || curwin->w_cursor.col >= Insstart.col) {
replace_do_bs(-1);
+ }
} else {
(void)del_char(false);
}
@@ -7838,13 +7962,13 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
{
linenr_T lnum;
int cc;
- int temp = 0; /* init for GCC */
+ int temp = 0; // init for GCC
colnr_T save_col;
colnr_T mincol;
bool did_backspace = false;
int in_indent;
int oldState;
- int cpc[MAX_MCO]; /* composing characters */
+ int cpc[MAX_MCO]; // composing characters
// can't delete anything in an empty file
// can't backup past first character in buffer
@@ -7908,23 +8032,22 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
* cc >= 0: NL was replaced, put original characters back
*/
cc = -1;
- if (State & REPLACE_FLAG)
- cc = replace_pop(); /* returns -1 if NL was inserted */
- /*
- * In replace mode, in the line we started replacing, we only move the
- * cursor.
- */
+ if (State & REPLACE_FLAG) {
+ cc = replace_pop(); // returns -1 if NL was inserted
+ }
+ // In replace mode, in the line we started replacing, we only move the
+ // cursor.
if ((State & REPLACE_FLAG) && curwin->w_cursor.lnum <= lnum) {
dec_cursor();
} else {
if (!(State & VREPLACE_FLAG)
|| curwin->w_cursor.lnum > orig_line_count) {
- temp = gchar_cursor(); /* remember current char */
- --curwin->w_cursor.lnum;
+ temp = gchar_cursor(); // remember current char
+ curwin->w_cursor.lnum--;
- /* When "aw" is in 'formatoptions' we must delete the space at
- * the end of the line, otherwise the line will be broken
- * again when auto-formatting. */
+ // When "aw" is in 'formatoptions' we must delete the space at
+ // the end of the line, otherwise the line will be broken
+ // again when auto-formatting.
if (has_format_option(FO_AUTO)
&& has_format_option(FO_WHITE_PAR)) {
char_u *ptr = ml_get_buf(curbuf, curwin->w_cursor.lnum,
@@ -7965,20 +8088,19 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
curwin->w_cursor.col = save_col;
cc = replace_pop();
}
- /* restore the characters that NL replaced */
+ // restore the characters that NL replaced
replace_pop_ins();
State = oldState;
}
}
did_ai = false;
} else {
- /*
- * Delete character(s) before the cursor.
- */
- if (revins_on) /* put cursor on last inserted char */
+ // Delete character(s) before the cursor.
+ if (revins_on) { // put cursor on last inserted char
dec_cursor();
+ }
mincol = 0;
- /* keep indent */
+ // keep indent
if (mode == BACKSPACE_LINE
&& (curbuf->b_p_ai
|| cindent_on()
@@ -8013,9 +8135,9 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
ts = get_sw_value(curbuf);
else
ts = get_sts_value();
- /* Compute the virtual column where we want to be. Since
- * 'showbreak' may get in the way, need to get the last column of
- * the previous character. */
+ // Compute the virtual column where we want to be. Since
+ // 'showbreak' may get in the way, need to get the last column of
+ // the previous character.
getvcol(curwin, &curwin->w_cursor, &vcol, NULL, NULL);
start_vcol = vcol;
dec_cursor();
@@ -8023,14 +8145,15 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
inc_cursor();
want_vcol = (want_vcol / ts) * ts;
- /* delete characters until we are at or before want_vcol */
+ // delete characters until we are at or before want_vcol
while (vcol > want_vcol
- && (cc = *(get_cursor_pos_ptr() - 1), ascii_iswhite(cc)))
+ && (cc = *(get_cursor_pos_ptr() - 1), ascii_iswhite(cc))) {
ins_bs_one(&vcol);
+ }
- /* insert extra spaces until we are at want_vcol */
+ // insert extra spaces until we are at want_vcol
while (vcol < want_vcol) {
- /* Remember the first char we inserted */
+ // Remember the first char we inserted
if (curwin->w_cursor.lnum == Insstart_orig.lnum
&& curwin->w_cursor.col < Insstart_orig.col) {
Insstart_orig.col = curwin->w_cursor.col;
@@ -8046,18 +8169,16 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
getvcol(curwin, &curwin->w_cursor, &vcol, NULL, NULL);
}
- /* If we are now back where we started delete one character. Can
- * happen when using 'sts' and 'linebreak'. */
- if (vcol >= start_vcol)
+ // If we are now back where we started delete one character. Can
+ // happen when using 'sts' and 'linebreak'.
+ if (vcol >= start_vcol) {
ins_bs_one(&vcol);
-
- // Delete upto starting point, start of line or previous word.
+ }
} else {
- int cclass = 0, prev_cclass = 0;
+ // Delete upto starting point, start of line or previous word.
+ int prev_cclass = 0;
- if (has_mbyte) {
- cclass = mb_get_class(get_cursor_pos_ptr());
- }
+ int cclass = mb_get_class(get_cursor_pos_ptr());
do {
if (!revins_on) { // put cursor on char to be deleted
dec_cursor();
@@ -8125,21 +8246,22 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
// with.
AppendCharToRedobuff(c);
- /* If deleted before the insertion point, adjust it */
+ // If deleted before the insertion point, adjust it
if (curwin->w_cursor.lnum == Insstart_orig.lnum
&& curwin->w_cursor.col < Insstart_orig.col) {
Insstart_orig.col = curwin->w_cursor.col;
}
- /* vi behaviour: the cursor moves backward but the character that
- * was there remains visible
- * Vim behaviour: the cursor moves backward and the character that
- * was there is erased from the screen.
- * We can emulate the vi behaviour by pretending there is a dollar
- * displayed even when there isn't.
- * --pkv Sun Jan 19 01:56:40 EST 2003 */
- if (vim_strchr(p_cpo, CPO_BACKSPACE) != NULL && dollar_vcol == -1)
+ // vi behaviour: the cursor moves backward but the character that
+ // was there remains visible
+ // Vim behaviour: the cursor moves backward and the character that
+ // was there is erased from the screen.
+ // We can emulate the vi behaviour by pretending there is a dollar
+ // displayed even when there isn't.
+ // --pkv Sun Jan 19 01:56:40 EST 2003
+ if (vim_strchr(p_cpo, CPO_BACKSPACE) != NULL && dollar_vcol == -1) {
dollar_vcol = curwin->w_virtcol;
+ }
// When deleting a char the cursor line must never be in a closed fold.
// E.g., when 'foldmethod' is indent and deleting the first non-white
@@ -8164,10 +8286,14 @@ static void ins_mouse(int c)
win_T *new_curwin = curwin;
if (curwin != old_curwin && win_valid(old_curwin)) {
- /* Mouse took us to another window. We need to go back to the
- * previous one to stop insert there properly. */
+ // Mouse took us to another window. We need to go back to the
+ // previous one to stop insert there properly.
curwin = old_curwin;
curbuf = curwin->w_buffer;
+ if (bt_prompt(curbuf)) {
+ // Restart Insert mode when re-entering the prompt buffer.
+ curbuf->b_prompt_insert = 'A';
+ }
}
start_arrow(curwin == old_curwin ? &tpos : NULL);
if (curwin != new_curwin && win_valid(new_curwin)) {
@@ -8177,7 +8303,7 @@ static void ins_mouse(int c)
can_cindent = true;
}
- /* redraw status lines (in case another window became active) */
+ // redraw status lines (in case another window became active)
redraw_statuslines();
}
@@ -8200,7 +8326,7 @@ static void ins_mousescroll(int dir)
if (curwin == old_curwin)
undisplay_dollar();
- /* Don't scroll the window in which completion is being done. */
+ // Don't scroll the window in which completion is being done.
if (!pum_visible()
|| curwin != old_curwin
) {
@@ -8242,9 +8368,10 @@ static void ins_left(void)
if (!end_change) {
AppendCharToRedobuff(K_LEFT);
}
- /* If exit reversed string, position is fixed */
- if (revins_scol != -1 && (int)curwin->w_cursor.col >= revins_scol)
+ // If exit reversed string, position is fixed
+ if (revins_scol != -1 && (int)curwin->w_cursor.col >= revins_scol) {
revins_legal++;
+ }
revins_chars++;
} else if (vim_strchr(p_ww, '[') != NULL && curwin->w_cursor.lnum > 1) {
// if 'whichwrap' set for cursor in insert mode may go to previous line.
@@ -8337,14 +8464,13 @@ static void ins_right(void)
revins_legal++;
if (revins_chars)
revins_chars--;
- }
- /* if 'whichwrap' set for cursor in insert mode, may move the
- * cursor to the next line */
- else if (vim_strchr(p_ww, ']') != NULL
- && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) {
+ } else if (vim_strchr(p_ww, ']') != NULL
+ && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) {
+ // if 'whichwrap' set for cursor in insert mode, may move the
+ // cursor to the next line
start_arrow(&curwin->w_cursor);
- curwin->w_set_curswant = TRUE;
- ++curwin->w_cursor.lnum;
+ curwin->w_set_curswant = true;
+ curwin->w_cursor.lnum++;
curwin->w_cursor.col = 0;
} else {
vim_beep(BO_CRSR);
@@ -8373,9 +8499,8 @@ static void ins_s_right(void)
dont_sync_undo = kFalse;
}
-static void
-ins_up (
- int startcol /* when TRUE move to Insstart.col */
+static void ins_up(
+ bool startcol // when true move to Insstart.col
)
{
pos_T tpos;
@@ -8405,7 +8530,7 @@ static void ins_pageup(void)
undisplay_dollar();
if (mod_mask & MOD_MASK_CTRL) {
- /* <C-PageUp>: tab page back */
+ // <C-PageUp>: tab page back
if (first_tabpage->tp_next != NULL) {
start_arrow(&curwin->w_cursor);
goto_tabpage(-1);
@@ -8422,9 +8547,8 @@ static void ins_pageup(void)
}
}
-static void
-ins_down (
- int startcol /* when TRUE move to Insstart.col */
+static void ins_down(
+ bool startcol // when true move to Insstart.col
)
{
pos_T tpos;
@@ -8454,7 +8578,7 @@ static void ins_pagedown(void)
undisplay_dollar();
if (mod_mask & MOD_MASK_CTRL) {
- /* <C-PageDown>: tab page forward */
+ // <C-PageDown>: tab page forward
if (first_tabpage->tp_next != NULL) {
start_arrow(&curwin->w_cursor);
goto_tabpage(0);
@@ -8541,7 +8665,7 @@ static bool ins_tab(void)
*/
if (!curbuf->b_p_et && (get_sts_value() || (p_sta && ind))) {
char_u *ptr;
- char_u *saved_line = NULL; /* init for GCC */
+ char_u *saved_line = NULL; // init for GCC
pos_T pos;
pos_T fpos;
pos_T *cursor;
@@ -8563,18 +8687,19 @@ static bool ins_tab(void)
cursor = &curwin->w_cursor;
}
- /* When 'L' is not in 'cpoptions' a tab always takes up 'ts' spaces. */
- if (vim_strchr(p_cpo, CPO_LISTWM) == NULL)
- curwin->w_p_list = FALSE;
+ // When 'L' is not in 'cpoptions' a tab always takes up 'ts' spaces.
+ if (vim_strchr(p_cpo, CPO_LISTWM) == NULL) {
+ curwin->w_p_list = false;
+ }
- /* Find first white before the cursor */
+ // Find first white before the cursor
fpos = curwin->w_cursor;
while (fpos.col > 0 && ascii_iswhite(ptr[-1])) {
--fpos.col;
--ptr;
}
- /* In Replace mode, don't change characters before the insert point. */
+ // In Replace mode, don't change characters before the insert point.
if ((State & REPLACE_FLAG)
&& fpos.lnum == Insstart.lnum
&& fpos.col < Insstart.col) {
@@ -8582,12 +8707,12 @@ static bool ins_tab(void)
fpos.col = Insstart.col;
}
- /* compute virtual column numbers of first white and cursor */
+ // compute virtual column numbers of first white and cursor
getvcol(curwin, &fpos, &vcol, NULL, NULL);
getvcol(curwin, cursor, &want_vcol, NULL, NULL);
- /* Use as many TABs as possible. Beware of 'breakindent', 'showbreak'
- and 'linebreak' adding extra virtual columns. */
+ // Use as many TABs as possible. Beware of 'breakindent', 'showbreak'
+ // and 'linebreak' adding extra virtual columns.
while (ascii_iswhite(*ptr)) {
i = lbr_chartabsize(NULL, (char_u *)"\t", vcol);
if (vcol + i > want_vcol)
@@ -8595,10 +8720,11 @@ static bool ins_tab(void)
if (*ptr != TAB) {
*ptr = TAB;
if (change_col < 0) {
- change_col = fpos.col; /* Column of first change */
- /* May have to adjust Insstart */
- if (fpos.lnum == Insstart.lnum && fpos.col < Insstart.col)
+ change_col = fpos.col; // Column of first change
+ // May have to adjust Insstart
+ if (fpos.lnum == Insstart.lnum && fpos.col < Insstart.col) {
Insstart.col = fpos.col;
+ }
}
}
++fpos.col;
@@ -8610,29 +8736,30 @@ static bool ins_tab(void)
int repl_off = 0;
char_u *line = ptr;
- /* Skip over the spaces we need. */
+ // Skip over the spaces we need.
while (vcol < want_vcol && *ptr == ' ') {
vcol += lbr_chartabsize(line, ptr, vcol);
++ptr;
++repl_off;
}
if (vcol > want_vcol) {
- /* Must have a char with 'showbreak' just before it. */
- --ptr;
- --repl_off;
+ // Must have a char with 'showbreak' just before it.
+ ptr--;
+ repl_off--;
}
fpos.col += repl_off;
- /* Delete following spaces. */
+ // Delete following spaces.
i = cursor->col - fpos.col;
if (i > 0) {
STRMOVE(ptr, ptr + i);
- /* correct replace stack. */
+ // correct replace stack.
if ((State & REPLACE_FLAG)
- && !(State & VREPLACE_FLAG)
- )
- for (temp = i; --temp >= 0; )
+ && !(State & VREPLACE_FLAG)) {
+ for (temp = i; --temp >= 0; ) {
replace_join(repl_off);
+ }
+ }
}
cursor->col -= i;
@@ -8642,11 +8769,11 @@ static bool ins_tab(void)
* spacing.
*/
if (State & VREPLACE_FLAG) {
- /* Backspace from real cursor to change_col */
+ // Backspace from real cursor to change_col
backspace_until_column(change_col);
- /* Insert each char in saved_line from changed_col to
- * ptr-cursor */
+ // Insert each char in saved_line from changed_col to
+ // ptr-cursor
ins_bytes_len(saved_line + change_col,
cursor->col - change_col);
}
@@ -8690,10 +8817,11 @@ static bool ins_eol(int c)
* in open_line().
*/
- /* Put cursor on NUL if on the last char and coladd is 1 (happens after
- * CTRL-O). */
- if (virtual_active() && curwin->w_cursor.coladd > 0)
+ // Put cursor on NUL if on the last char and coladd is 1 (happens after
+ // CTRL-O).
+ if (virtual_active() && curwin->w_cursor.coladd > 0) {
coladvance(getviscol());
+ }
// NL in reverse insert will always start in the end of current line.
if (revins_on) {
@@ -8721,15 +8849,15 @@ static int ins_digraph(void)
{
int c;
int cc;
- int did_putchar = FALSE;
+ bool did_putchar = false;
pc_status = PC_STATUS_UNSET;
if (redrawing() && !char_avail()) {
- /* may need to redraw when no more chars available now */
- ins_redraw(FALSE);
+ // may need to redraw when no more chars available now
+ ins_redraw(false);
- edit_putchar('?', TRUE);
- did_putchar = TRUE;
+ edit_putchar('?', true);
+ did_putchar = true;
add_to_showcmd_c(Ctrl_K);
}
@@ -8745,21 +8873,21 @@ static int ins_digraph(void)
edit_unputchar();
}
- if (IS_SPECIAL(c) || mod_mask) { /* special key */
+ if (IS_SPECIAL(c) || mod_mask) { // special key
clear_showcmd();
insert_special(c, TRUE, FALSE);
return NUL;
}
if (c != ESC) {
- did_putchar = FALSE;
+ did_putchar = false;
if (redrawing() && !char_avail()) {
- /* may need to redraw when no more chars available now */
- ins_redraw(FALSE);
+ // may need to redraw when no more chars available now
+ ins_redraw(false);
if (char2cells(c) == 1) {
- ins_redraw(FALSE);
- edit_putchar(c, TRUE);
- did_putchar = TRUE;
+ ins_redraw(false);
+ edit_putchar(c, true);
+ did_putchar = true;
}
add_to_showcmd_c(c);
}
@@ -8798,7 +8926,7 @@ int ins_copychar(linenr_T lnum)
return NUL;
}
- /* try to advance to the cursor column */
+ // try to advance to the cursor column
temp = 0;
line = ptr = ml_get(lnum);
prev_ptr = ptr;
@@ -8848,8 +8976,8 @@ static int ins_ctrl_ey(int tc)
curbuf->b_p_tw = tw_save;
revins_chars++;
revins_legal++;
- c = Ctrl_V; /* pretend CTRL-V is last character */
- auto_format(FALSE, TRUE);
+ c = Ctrl_V; // pretend CTRL-V is last character
+ auto_format(false, true);
}
}
return c;
@@ -8884,9 +9012,10 @@ static void ins_try_si(int c)
*/
ptr = ml_get(pos->lnum);
i = pos->col;
- if (i > 0) /* skip blanks before '{' */
- while (--i > 0 && ascii_iswhite(ptr[i]))
- ;
+ if (i > 0) { // skip blanks before '{'
+ while (--i > 0 && ascii_iswhite(ptr[i])) {
+ }
+ }
curwin->w_cursor.lnum = pos->lnum;
curwin->w_cursor.col = i;
if (ptr[i] == ')' && (pos = findmatch(NULL, '(')) != NULL)
@@ -8909,9 +9038,10 @@ static void ins_try_si(int c)
while (curwin->w_cursor.lnum > 1) {
ptr = skipwhite(ml_get(--(curwin->w_cursor.lnum)));
- /* ignore empty lines and lines starting with '#'. */
- if (*ptr != '#' && *ptr != NUL)
+ // ignore empty lines and lines starting with '#'.
+ if (*ptr != '#' && *ptr != NUL) {
break;
+ }
}
if (get_indent() >= i)
temp = FALSE;
@@ -8926,14 +9056,15 @@ static void ins_try_si(int c)
* set indent of '#' always to 0
*/
if (curwin->w_cursor.col > 0 && can_si && c == '#') {
- /* remember current indent for next line */
+ // remember current indent for next line
old_indent = get_indent();
(void)set_indent(0, SIN_CHANGED);
}
- /* Adjust ai_col, the char at this position can be deleted. */
- if (ai_col > curwin->w_cursor.col)
+ // Adjust ai_col, the char at this position can be deleted.
+ if (ai_col > curwin->w_cursor.col) {
ai_col = curwin->w_cursor.col;
+ }
}
/*
@@ -8998,7 +9129,8 @@ static int ins_apply_autocmds(event_T event)
// If u_savesub() was called then we are not prepared to start
// a new line. Call u_save() with no contents to fix that.
- if (tick != buf_get_changedtick(curbuf)) {
+ // Except when leaving Insert mode.
+ if (event != EVENT_INSERTLEAVE && tick != buf_get_changedtick(curbuf)) {
u_save(curwin->w_cursor.lnum, (linenr_T)(curwin->w_cursor.lnum + 1));
}
diff --git a/src/nvim/edit.h b/src/nvim/edit.h
index 92dab37a70..09f401ee82 100644
--- a/src/nvim/edit.h
+++ b/src/nvim/edit.h
@@ -10,8 +10,7 @@
#define CPT_MENU 1 // "menu"
#define CPT_KIND 2 // "kind"
#define CPT_INFO 3 // "info"
-#define CPT_USER_DATA 4 // "user data"
-#define CPT_COUNT 5 // Number of entries
+#define CPT_COUNT 4 // Number of entries
// values for cp_flags
typedef enum {
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 55b18d5f4f..4a0876a952 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -5,183 +5,64 @@
* eval.c: Expression evaluation.
*/
-#include <assert.h>
-#include <float.h>
-#include <inttypes.h>
-#include <stdarg.h>
-#include <string.h>
-#include <stdlib.h>
-#include <stdbool.h>
#include <math.h>
-#include <limits.h>
-#include <msgpack.h>
-#include "nvim/assert.h"
-#include "nvim/vim.h"
-#include "nvim/ascii.h"
#ifdef HAVE_LOCALE_H
# include <locale.h>
#endif
-#include "nvim/eval.h"
+
+#include "nvim/ascii.h"
#include "nvim/buffer.h"
#include "nvim/change.h"
#include "nvim/channel.h"
#include "nvim/charset.h"
-#include "nvim/context.h"
#include "nvim/cursor.h"
-#include "nvim/diff.h"
#include "nvim/edit.h"
-#include "nvim/ex_cmds.h"
+#include "nvim/eval/userfunc.h"
+#include "nvim/eval.h"
+#include "nvim/eval/encode.h"
+#include "nvim/eval/executor.h"
+#include "nvim/eval/gc.h"
+#include "nvim/eval/typval.h"
#include "nvim/ex_cmds2.h"
#include "nvim/ex_docmd.h"
-#include "nvim/ex_eval.h"
#include "nvim/ex_getln.h"
+#include "nvim/ex_session.h"
#include "nvim/fileio.h"
-#include "nvim/os/fileio.h"
-#include "nvim/func_attr.h"
-#include "nvim/fold.h"
#include "nvim/getchar.h"
-#include "nvim/hashtab.h"
-#include "nvim/iconv.h"
-#include "nvim/if_cscope.h"
-#include "nvim/indent_c.h"
-#include "nvim/indent.h"
+#include "nvim/lua/executor.h"
#include "nvim/mark.h"
-#include "nvim/math.h"
-#include "nvim/mbyte.h"
#include "nvim/memline.h"
-#include "nvim/memory.h"
-#include "nvim/menu.h"
-#include "nvim/message.h"
#include "nvim/misc1.h"
-#include "nvim/keymap.h"
-#include "nvim/map.h"
-#include "nvim/file_search.h"
-#include "nvim/garray.h"
#include "nvim/move.h"
-#include "nvim/normal.h"
#include "nvim/ops.h"
#include "nvim/option.h"
-#include "nvim/os_unix.h"
+#include "nvim/os/input.h"
+#include "nvim/os/os.h"
+#include "nvim/os/shell.h"
#include "nvim/path.h"
-#include "nvim/popupmnu.h"
-#include "nvim/profile.h"
#include "nvim/quickfix.h"
#include "nvim/regexp.h"
#include "nvim/screen.h"
#include "nvim/search.h"
-#include "nvim/sha256.h"
#include "nvim/sign.h"
-#include "nvim/spell.h"
-#include "nvim/state.h"
-#include "nvim/strings.h"
#include "nvim/syntax.h"
-#include "nvim/tag.h"
#include "nvim/ui.h"
-#include "nvim/main.h"
-#include "nvim/mouse.h"
-#include "nvim/terminal.h"
#include "nvim/undo.h"
#include "nvim/version.h"
#include "nvim/window.h"
-#include "nvim/eval/encode.h"
-#include "nvim/eval/decode.h"
-#include "nvim/os/os.h"
-#include "nvim/event/libuv_process.h"
-#include "nvim/os/pty_process.h"
-#include "nvim/event/rstream.h"
-#include "nvim/event/wstream.h"
-#include "nvim/event/time.h"
-#include "nvim/os/time.h"
-#include "nvim/msgpack_rpc/channel.h"
-#include "nvim/msgpack_rpc/server.h"
-#include "nvim/msgpack_rpc/helpers.h"
-#include "nvim/api/private/helpers.h"
-#include "nvim/api/vim.h"
-#include "nvim/os/dl.h"
-#include "nvim/os/input.h"
-#include "nvim/event/loop.h"
-#include "nvim/lib/kvec.h"
-#include "nvim/lib/khash.h"
-#include "nvim/lib/queue.h"
-#include "nvim/lua/executor.h"
-#include "nvim/eval/typval.h"
-#include "nvim/eval/executor.h"
-#include "nvim/eval/gc.h"
-#include "nvim/macros.h"
-
-// TODO(ZyX-I): Remove DICT_MAXNEST, make users be non-recursive instead
-#define DICT_MAXNEST 100 /* maximum nesting of lists and dicts */
-// Character used as separator in autoload function/variable names.
-#define AUTOLOAD_CHAR '#'
+// TODO(ZyX-I): Remove DICT_MAXNEST, make users be non-recursive instead
-/*
- * Structure returned by get_lval() and used by set_var_lval().
- * For a plain name:
- * "name" points to the variable name.
- * "exp_name" is NULL.
- * "tv" is NULL
- * For a magic braces name:
- * "name" points to the expanded variable name.
- * "exp_name" is non-NULL, to be freed later.
- * "tv" is NULL
- * For an index in a list:
- * "name" points to the (expanded) variable name.
- * "exp_name" NULL or non-NULL, to be freed later.
- * "tv" points to the (first) list item value
- * "li" points to the (first) list item
- * "range", "n1", "n2" and "empty2" indicate what items are used.
- * For an existing Dict item:
- * "name" points to the (expanded) variable name.
- * "exp_name" NULL or non-NULL, to be freed later.
- * "tv" points to the dict item value
- * "newkey" is NULL
- * For a non-existing Dict item:
- * "name" points to the (expanded) variable name.
- * "exp_name" NULL or non-NULL, to be freed later.
- * "tv" points to the Dictionary typval_T
- * "newkey" is the key for the new item.
- */
-typedef struct lval_S {
- const char *ll_name; ///< Start of variable name (can be NULL).
- size_t ll_name_len; ///< Length of the .ll_name.
- char *ll_exp_name; ///< NULL or expanded name in allocated memory.
- typval_T *ll_tv; ///< Typeval of item being used. If "newkey"
- ///< isn't NULL it's the Dict to which to add the item.
- listitem_T *ll_li; ///< The list item or NULL.
- list_T *ll_list; ///< The list or NULL.
- int ll_range; ///< TRUE when a [i:j] range was used.
- long ll_n1; ///< First index for list.
- long ll_n2; ///< Second index for list range.
- int ll_empty2; ///< Second index is empty: [i:].
- dict_T *ll_dict; ///< The Dictionary or NULL.
- dictitem_T *ll_di; ///< The dictitem or NULL.
- char_u *ll_newkey; ///< New key for Dict in allocated memory or NULL.
-} lval_T;
+#define DICT_MAXNEST 100 // maximum nesting of lists and dicts
static char *e_letunexp = N_("E18: Unexpected characters in :let");
static char *e_missbrac = N_("E111: Missing ']'");
-static char *e_listarg = N_("E686: Argument of %s must be a List");
-static char *e_listdictarg = N_(
- "E712: Argument of %s must be a List or Dictionary");
-static char *e_listreq = N_("E714: List required");
-static char *e_dictreq = N_("E715: Dictionary required");
-static char *e_stringreq = N_("E928: String required");
-static char *e_toomanyarg = N_("E118: Too many arguments for function: %s");
-static char *e_dictkey = N_("E716: Key not present in Dictionary: %s");
-static char *e_funcexts = N_(
- "E122: Function %s already exists, add ! to replace it");
-static char *e_funcdict = N_("E717: Dictionary entry already exists");
-static char *e_funcref = N_("E718: Funcref required");
static char *e_dictrange = N_("E719: Cannot use [:] with a Dictionary");
-static char *e_nofunc = N_("E130: Unknown function: %s");
static char *e_illvar = N_("E461: Illegal variable name: %s");
static char *e_cannot_mod = N_("E995: Cannot modify existing variable");
-static const char *e_readonlyvar = N_(
- "E46: Cannot change read-only variable \"%.*s\"");
// TODO(ZyX-I): move to eval/executor
static char *e_letwrong = N_("E734: Wrong variable type for %s=");
@@ -200,10 +81,8 @@ static ScopeDictDictItem globvars_var;
*/
static hashtab_T compat_hashtab;
-hashtab_T func_hashtab;
-
-// Used for checking if local variables or arguments used in a lambda.
-static int *eval_lavars_used = NULL;
+/// Used for checking if local variables or arguments used in a lambda.
+bool *eval_lavars_used = NULL;
/*
* Array to hold the hashtab with variables local to each sourced script.
@@ -218,98 +97,25 @@ static garray_T ga_scripts = {0, 0, sizeof(scriptvar_T *), 4, NULL};
#define SCRIPT_SV(id) (((scriptvar_T **)ga_scripts.ga_data)[(id) - 1])
#define SCRIPT_VARS(id) (SCRIPT_SV(id)->sv_dict.dv_hashtab)
-static int echo_attr = 0; /* attributes used for ":echo" */
-
-/// Describe data to return from find_some_match()
-typedef enum {
- kSomeMatch, ///< Data for match().
- kSomeMatchEnd, ///< Data for matchend().
- kSomeMatchList, ///< Data for matchlist().
- kSomeMatchStr, ///< Data for matchstr().
- kSomeMatchStrPos, ///< Data for matchstrpos().
-} SomeMatchType;
-
-/// trans_function_name() flags
-typedef enum {
- TFN_INT = 1, ///< May use internal function name
- TFN_QUIET = 2, ///< Do not emit error messages.
- TFN_NO_AUTOLOAD = 4, ///< Do not use script autoloading.
- TFN_NO_DEREF = 8, ///< Do not dereference a Funcref.
- TFN_READ_ONLY = 16, ///< Will not change the variable.
-} TransFunctionNameFlags;
-
-/// get_lval() flags
-typedef enum {
- GLV_QUIET = TFN_QUIET, ///< Do not emit error messages.
- GLV_NO_AUTOLOAD = TFN_NO_AUTOLOAD, ///< Do not use script autoloading.
- GLV_READ_ONLY = TFN_READ_ONLY, ///< Indicates that caller will not change
- ///< the value (prevents error message).
-} GetLvalFlags;
-
-// flags used in uf_flags
-#define FC_ABORT 0x01 // abort function on error
-#define FC_RANGE 0x02 // function accepts range
-#define FC_DICT 0x04 // Dict function, uses "self"
-#define FC_CLOSURE 0x08 // closure, uses outer scope variables
-#define FC_DELETED 0x10 // :delfunction used while uf_refcount > 0
-#define FC_REMOVED 0x20 // function redefined while uf_refcount > 0
-#define FC_SANDBOX 0x40 // function defined in the sandbox
+static int echo_attr = 0; // attributes used for ":echo"
// The names of packages that once were loaded are remembered.
static garray_T ga_loaded = { 0, 0, sizeof(char_u *), 4, NULL };
-#define FUNCARG(fp, j) ((char_u **)(fp->uf_args.ga_data))[j]
-#define FUNCLINE(fp, j) ((char_u **)(fp->uf_lines.ga_data))[j]
-
-/// Short variable name length
-#define VAR_SHORT_LEN 20
-/// Number of fixed variables used for arguments
-#define FIXVAR_CNT 12
-
-struct funccall_S {
- ufunc_T *func; ///< Function being called.
- int linenr; ///< Next line to be executed.
- int returned; ///< ":return" used.
- /// Fixed variables for arguments.
- TV_DICTITEM_STRUCT(VAR_SHORT_LEN + 1) fixvar[FIXVAR_CNT];
- dict_T l_vars; ///< l: local function variables.
- ScopeDictDictItem l_vars_var; ///< Variable for l: scope.
- dict_T l_avars; ///< a: argument variables.
- ScopeDictDictItem l_avars_var; ///< Variable for a: scope.
- list_T l_varlist; ///< List for a:000.
- listitem_T l_listitems[MAX_FUNC_ARGS]; ///< List items for a:000.
- typval_T *rettv; ///< Return value.
- linenr_T breakpoint; ///< Next line with breakpoint or zero.
- int dbg_tick; ///< Debug_tick when breakpoint was set.
- int level; ///< Top nesting level of executed function.
- proftime_T prof_child; ///< Time spent in a child.
- funccall_T *caller; ///< Calling function or NULL.
- int fc_refcount; ///< Number of user functions that reference this funccall.
- int fc_copyID; ///< CopyID used for garbage collection.
- garray_T fc_funcs; ///< List of ufunc_T* which keep a reference to "func".
-};
-
-///< Structure used by trans_function_name()
-typedef struct {
- dict_T *fd_dict; ///< Dictionary used.
- char_u *fd_newkey; ///< New key in "dict" in allocated memory.
- dictitem_T *fd_di; ///< Dictionary item used.
-} funcdict_T;
-
/*
* Info used by a ":for" loop.
*/
typedef struct {
- int fi_semicolon; /* TRUE if ending in '; var]' */
- int fi_varcount; /* nr of variables in the list */
- listwatch_T fi_lw; /* keep an eye on the item used. */
- list_T *fi_list; /* list being used */
+ int fi_semicolon; // TRUE if ending in '; var]'
+ int fi_varcount; // nr of variables in the list
+ listwatch_T fi_lw; // keep an eye on the item used.
+ list_T *fi_list; // list being used
} forinfo_T;
-/* values for vv_flags: */
-#define VV_COMPAT 1 /* compatible, also used without "v:" */
-#define VV_RO 2 /* read-only */
-#define VV_RO_SBX 4 /* read-only in the sandbox */
+// values for vv_flags:
+#define VV_COMPAT 1 // compatible, also used without "v:"
+#define VV_RO 2 // read-only
+#define VV_RO_SBX 4 // read-only in the sandbox
#define VV(idx, name, type, flags) \
[idx] = { \
@@ -426,7 +232,7 @@ static struct vimvar {
};
#undef VV
-/* shorthand */
+// shorthand
#define vv_type vv_di.di_tv.v_type
#define vv_nr vv_di.di_tv.vval.v_number
#define vv_special vv_di.di_tv.vval.v_special
@@ -445,71 +251,13 @@ static partial_T *vvlua_partial;
/// v: hashtab
#define vimvarht vimvardict.dv_hashtab
-typedef struct {
- TimeWatcher tw;
- int timer_id;
- int repeat_count;
- int refcount;
- int emsg_count; ///< Errors in a repeating timer.
- long timeout;
- bool stopped;
- bool paused;
- Callback callback;
-} timer_T;
-
-typedef void (*FunPtr)(void);
-
-/// Prototype of C function that implements VimL function
-typedef void (*VimLFunc)(typval_T *args, typval_T *rvar, FunPtr data);
-
-/// Structure holding VimL function definition
-typedef struct fst {
- char *name; ///< Name of the function.
- uint8_t min_argc; ///< Minimal number of arguments.
- uint8_t max_argc; ///< Maximal number of arguments.
- VimLFunc func; ///< Function implementation.
- FunPtr data; ///< Userdata for function implementation.
-} VimLFuncDef;
-
-KHASH_MAP_INIT_STR(functions, VimLFuncDef)
-
-/// Type of assert_* check being performed
-typedef enum
-{
- ASSERT_EQUAL,
- ASSERT_NOTEQUAL,
- ASSERT_MATCH,
- ASSERT_NOTMATCH,
- ASSERT_INRANGE,
- ASSERT_OTHER,
-} assert_type_T;
-
-/// Type for dict_list function
-typedef enum {
- kDictListKeys, ///< List dictionary keys.
- kDictListValues, ///< List dictionary values.
- kDictListItems, ///< List dictionary contents: [keys, values].
-} DictListType;
-
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval.c.generated.h"
#endif
-#define FNE_INCL_BR 1 /* find_name_end(): include [] in name */
-#define FNE_CHECK_START 2 /* find_name_end(): check name starts with
- valid character */
-
static uint64_t last_timer_id = 1;
static PMap(uint64_t) *timers = NULL;
-/// Dummy va_list for passing to vim_snprintf
-///
-/// Used because:
-/// - passing a NULL pointer doesn't work when va_list isn't a pointer
-/// - locally in the function results in a "used before set" warning
-/// - using va_start() to initialize it gives "function with fixed args" error
-static va_list dummy_ap;
-
static const char *const msgpack_type_names[] = {
[kMPNil] = "nil",
[kMPBoolean] = "boolean",
@@ -576,7 +324,7 @@ void eval_init(void)
init_var_dict(&vimvardict, &vimvars_var, VAR_SCOPE);
vimvardict.dv_lock = VAR_FIXED;
hash_init(&compat_hashtab);
- hash_init(&func_hashtab);
+ func_init();
for (size_t i = 0; i < ARRAY_SIZE(vimvars); i++) {
p = &vimvars[i];
@@ -589,12 +337,14 @@ void eval_init(void)
else
p->vv_di.di_flags = DI_FLAGS_FIX;
- /* add to v: scope dict, unless the value is not always available */
- if (p->vv_type != VAR_UNKNOWN)
+ // add to v: scope dict, unless the value is not always available
+ if (p->vv_type != VAR_UNKNOWN) {
hash_add(&vimvarht, p->vv_di.di_key);
- if (p->vv_flags & VV_COMPAT)
- /* add to compat scope dict */
+ }
+ if (p->vv_flags & VV_COMPAT) {
+ // add to compat scope dict
hash_add(&compat_hashtab, p->vv_di.di_key);
+ }
}
vimvars[VV_VERSION].vv_nr = VIM_VERSION_100;
@@ -668,16 +418,16 @@ void eval_clear(void)
}
}
hash_clear(&vimvarht);
- hash_init(&vimvarht); /* garbage_collect() will access it */
+ hash_init(&vimvarht); // garbage_collect() will access it
hash_clear(&compat_hashtab);
free_scriptnames();
free_locales();
- /* global variables */
+ // global variables
vars_clear(&globvarht);
- /* autoloaded script names */
+ // autoloaded script names
ga_clear_strings(&ga_loaded);
/* Script-local variables. First clear all the variables and in a second
@@ -699,53 +449,6 @@ void eval_clear(void)
#endif
/*
- * 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.
- */
-linenr_T *func_breakpoint(void *cookie)
-{
- return &((funccall_T *)cookie)->breakpoint;
-}
-
-/*
- * 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.
- */
-int func_level(void *cookie)
-{
- return ((funccall_T *)cookie)->level;
-}
-
-/* pointer to funccal for currently active function */
-funccall_T *current_funccal = NULL;
-
-// Pointer to list of previously used funccal, still around because some
-// item in it is still being used.
-funccall_T *previous_funccal = NULL;
-
-/*
- * Return TRUE when a function was ended by a ":return" command.
- */
-int current_func_returned(void)
-{
- return current_funccal->returned;
-}
-
-/*
* Set an internal variable to a string value. Creates the variable if it does
* not already exist.
*/
@@ -771,25 +474,25 @@ static char_u *redir_varname = NULL;
int
var_redir_start(
char_u *name,
- int append /* append to an existing variable */
+ int append // append to an existing variable
)
{
int save_emsg;
int err;
typval_T tv;
- /* Catch a bad name early. */
+ // Catch a bad name early.
if (!eval_isnamec1(*name)) {
EMSG(_(e_invarg));
return FAIL;
}
- /* Make a copy of the name, it is used in redir_lval until redir ends. */
+ // Make a copy of the name, it is used in redir_lval until redir ends.
redir_varname = vim_strsave(name);
redir_lval = xcalloc(1, sizeof(lval_T));
- /* The output is stored in growarray "redir_ga" until redirection ends. */
+ // The output is stored in growarray "redir_ga" until redirection ends.
ga_init(&redir_ga, (int)sizeof(char), 500);
// Parse the variable name (can be a dict or list entry).
@@ -798,12 +501,13 @@ var_redir_start(
if (redir_endp == NULL || redir_lval->ll_name == NULL
|| *redir_endp != NUL) {
clear_lval(redir_lval);
- if (redir_endp != NULL && *redir_endp != NUL)
- /* Trailing characters are present after the variable name */
+ if (redir_endp != NULL && *redir_endp != NUL) {
+ // Trailing characters are present after the variable name
EMSG(_(e_trailing));
- else
+ } else {
EMSG(_(e_invarg));
- redir_endp = NULL; /* don't store a value, only cleanup */
+ }
+ redir_endp = NULL; // don't store a value, only cleanup
var_redir_stop();
return FAIL;
}
@@ -823,7 +527,7 @@ var_redir_start(
err = did_emsg;
did_emsg |= save_emsg;
if (err) {
- redir_endp = NULL; /* don't store a value, only cleanup */
+ redir_endp = NULL; // don't store a value, only cleanup
var_redir_stop();
return FAIL;
}
@@ -847,10 +551,11 @@ void var_redir_str(char_u *value, int value_len)
if (redir_lval == NULL)
return;
- if (value_len == -1)
- len = (int)STRLEN(value); /* Append the entire string */
- else
- len = value_len; /* Append only "value_len" characters */
+ if (value_len == -1) {
+ len = (int)STRLEN(value); // Append the entire string
+ } else {
+ len = value_len; // Append only "value_len" characters
+ }
ga_grow(&redir_ga, len);
memmove((char *)redir_ga.ga_data + redir_ga.ga_len, value, len);
@@ -866,9 +571,9 @@ void var_redir_stop(void)
typval_T tv;
if (redir_lval != NULL) {
- /* If there was no error: assign the text to the variable. */
+ // If there was no error: assign the text to the variable.
if (redir_endp != NULL) {
- ga_append(&redir_ga, NUL); /* Append the trailing NUL. */
+ ga_append(&redir_ga, NUL); // Append the trailing NUL.
tv.v_type = VAR_STRING;
tv.vval.v_string = redir_ga.ga_data;
// Call get_lval() again, if it's inside a Dict or List it may
@@ -969,7 +674,7 @@ eval_to_bool(
char_u *arg,
bool *error,
char_u **nextcmd,
- int skip /* only parse, don't execute */
+ int skip // only parse, don't execute
)
{
typval_T tv;
@@ -1017,8 +722,8 @@ static int eval1_emsg(char_u **arg, typval_T *rettv, bool evaluate)
return ret;
}
-static int eval_expr_typval(const typval_T *expr, typval_T *argv,
- int argc, typval_T *rettv)
+int eval_expr_typval(const typval_T *expr, typval_T *argv,
+ int argc, typval_T *rettv)
FUNC_ATTR_NONNULL_ARG(1, 2, 4)
{
int dummy;
@@ -1063,7 +768,7 @@ static int eval_expr_typval(const typval_T *expr, typval_T *argv,
/// Like eval_to_bool() but using a typval_T instead of a string.
/// Works for string, funcref and partial.
-static bool eval_expr_to_bool(const typval_T *expr, bool *error)
+bool eval_expr_to_bool(const typval_T *expr, bool *error)
FUNC_ATTR_NONNULL_ARG(1, 2)
{
typval_T argv, rettv;
@@ -1210,7 +915,7 @@ varnumber_T eval_to_number(char_u *expr)
* Save the current typeval in "save_tv".
* When not used yet add the variable to the v: hashtable.
*/
-static void prepare_vimvar(int idx, typval_T *save_tv)
+void prepare_vimvar(int idx, typval_T *save_tv)
{
*save_tv = vimvars[idx].vv_tv;
if (vimvars[idx].vv_type == VAR_UNKNOWN)
@@ -1221,7 +926,7 @@ static void prepare_vimvar(int idx, typval_T *save_tv)
* Restore v: variable "idx" to typeval "save_tv".
* When no longer defined, remove the variable from the v: hashtable.
*/
-static void restore_vimvar(int idx, typval_T *save_tv)
+void restore_vimvar(int idx, typval_T *save_tv)
{
hashitem_T *hi;
@@ -1237,7 +942,7 @@ static void restore_vimvar(int idx, typval_T *save_tv)
}
/// If there is a window for "curbuf", make it the current window.
-static void find_win_for_curbuf(void)
+void find_win_for_curbuf(void)
{
for (wininfo_T *wip = curbuf->b_wininfo; wip != NULL; wip = wip->wi_next) {
if (wip->wi_win != NULL) {
@@ -1420,33 +1125,15 @@ void *call_func_retlist(const char_u *func, int argc, typval_T *argv)
}
/*
- * Save the current function call pointer, and set it to NULL.
- * Used when executing autocommands and for ":source".
- */
-void *save_funccal(void)
-{
- funccall_T *fc = current_funccal;
-
- current_funccal = NULL;
- return (void *)fc;
-}
-
-void restore_funccal(void *vfc)
-{
- funccall_T *fc = (funccall_T *)vfc;
-
- current_funccal = fc;
-}
-
-/*
* Prepare profiling for entering a child or something else that is not
* counted for the script/function itself.
* Should always be called in pair with prof_child_exit().
*/
-void prof_child_enter(proftime_T *tm /* place to store waittime */
- )
+void prof_child_enter(
+ proftime_T *tm // place to store waittime
+)
{
- funccall_T *fc = current_funccal;
+ funccall_T *fc = get_current_funccal();
if (fc != NULL && fc->func->uf_profiling) {
fc->prof_child = profile_start();
@@ -1459,10 +1146,11 @@ void prof_child_enter(proftime_T *tm /* place to store waittime */
* Take care of time spent in a child.
* Should always be called after prof_child_enter().
*/
-void prof_child_exit(proftime_T *tm /* where waittime was stored */
- )
+void prof_child_exit(
+ proftime_T *tm // where waittime was stored
+)
{
- funccall_T *fc = current_funccal;
+ funccall_T *fc = get_current_funccal();
if (fc != NULL && fc->func->uf_profiling) {
fc->prof_child = profile_end(fc->prof_child);
@@ -1485,7 +1173,6 @@ int eval_foldexpr(char_u *arg, int *cp)
{
typval_T tv;
varnumber_T retval;
- char_u *s;
int use_sandbox = was_set_insecurely((char_u *)"foldexpr",
OPT_LOCAL);
@@ -1494,20 +1181,21 @@ int eval_foldexpr(char_u *arg, int *cp)
++sandbox;
++textlock;
*cp = NUL;
- if (eval0(arg, &tv, NULL, TRUE) == FAIL)
+ if (eval0(arg, &tv, NULL, true) == FAIL) {
retval = 0;
- else {
- /* If the result is a number, just return the number. */
- if (tv.v_type == VAR_NUMBER)
+ } else {
+ // If the result is a number, just return the number.
+ if (tv.v_type == VAR_NUMBER) {
retval = tv.vval.v_number;
- else if (tv.v_type != VAR_STRING || tv.vval.v_string == NULL)
+ } else if (tv.v_type != VAR_STRING || tv.vval.v_string == NULL) {
retval = 0;
- else {
- /* If the result is a string, check if there is a non-digit before
- * the number. */
- s = tv.vval.v_string;
- if (!ascii_isdigit(*s) && *s != '-')
+ } else {
+ // If the result is a string, check if there is a non-digit before
+ // the number.
+ char_u *s = tv.vval.v_string;
+ if (!ascii_isdigit(*s) && *s != '-') {
*cp = *s++;
+ }
retval = atol((char *)s);
}
tv_clear(&tv);
@@ -1847,10 +1535,10 @@ static const char_u *skip_var_list(const char_u *arg, int *var_count,
const char_u *s;
if (*arg == '[') {
- /* "[var, var]": find the matching ']'. */
+ // "[var, var]": find the matching ']'.
p = arg;
for (;; ) {
- p = skipwhite(p + 1); /* skip whites after '[', ';' or ',' */
+ p = skipwhite(p + 1); // skip whites after '[', ';' or ','
s = skip_var_one(p);
if (s == p) {
EMSG2(_(e_invarg2), p);
@@ -1893,8 +1581,8 @@ static const char_u *skip_var_one(const char_u *arg)
* List variables for hashtab "ht" with prefix "prefix".
* If "empty" is TRUE also list NULL strings as empty strings.
*/
-static void list_hashtable_vars(hashtab_T *ht, const char *prefix, int empty,
- int *first)
+void list_hashtable_vars(hashtab_T *ht, const char *prefix, int empty,
+ int *first)
{
hashitem_T *hi;
dictitem_T *di;
@@ -1971,17 +1659,6 @@ static void list_script_vars(int *first)
}
/*
- * List function variables, if there is a function.
- */
-static void list_func_vars(int *first)
-{
- if (current_funccal != NULL) {
- list_hashtable_vars(&current_funccal->l_vars.dv_hashtab, "l:", false,
- first);
- }
-}
-
-/*
* List variables in "arg".
*/
static const char *list_arg_vars(exarg_T *eap, const char *arg, int *first)
@@ -2275,9 +1952,9 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv,
///
/// @return A pointer to just after the name, including indexes. Returns NULL
/// for a parsing error, but it is still needed to free items in lp.
-static char_u *get_lval(char_u *const name, typval_T *const rettv,
- lval_T *const lp, const bool unlet, const bool skip,
- const int flags, const int fne_flags)
+char_u *get_lval(char_u *const name, typval_T *const rettv,
+ lval_T *const lp, const bool unlet, const bool skip,
+ const int flags, const int fne_flags)
FUNC_ATTR_NONNULL_ARG(1, 3)
{
dictitem_T *v;
@@ -2288,7 +1965,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv,
hashtab_T *ht;
int quiet = flags & GLV_QUIET;
- /* Clear everything in "lp". */
+ // Clear everything in "lp".
memset(lp, 0, sizeof(lval_T));
if (skip) {
@@ -2306,7 +1983,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv,
(const char_u **)&expr_end,
fne_flags);
if (expr_start != NULL) {
- /* Don't expand the name when we already know there is an error. */
+ // Don't expand the name when we already know there is an error.
if (unlet && !ascii_iswhite(*p) && !ends_excmd(*p)
&& *p != '[' && *p != '.') {
EMSG(_(e_trailing));
@@ -2386,7 +2063,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv,
}
p = key + len;
} else {
- /* Get the index [expr] or the first index [expr: ]. */
+ // Get the index [expr] or the first index [expr: ].
p = skipwhite(p + 1);
if (*p == ':') {
empty1 = true;
@@ -2402,7 +2079,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv,
}
}
- /* Optionally get the second index [ :expr]. */
+ // Optionally get the second index [ :expr].
if (*p == ':') {
if (lp->ll_tv->v_type == VAR_DICT) {
if (!quiet) {
@@ -2448,8 +2125,8 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv,
return NULL;
}
- /* Skip to past ']'. */
- ++p;
+ // Skip to past ']'.
+ p++;
}
if (lp->ll_tv->v_type == VAR_DICT) {
@@ -2598,7 +2275,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv,
/*
* Clear lval "lp" that was filled by get_lval().
*/
-static void clear_lval(lval_T *lp)
+void clear_lval(lval_T *lp)
{
xfree(lp->ll_exp_name);
xfree(lp->ll_newkey);
@@ -2875,7 +2552,7 @@ void set_context_for_expression(expand_T *xp, char_u *arg, cmdidx_T cmdidx)
if (cmdidx == CMD_let || cmdidx == CMD_const) {
xp->xp_context = EXPAND_USER_VARS;
if (vim_strpbrk(arg, (char_u *)"\"'+-*/%.=!?~|&$([<>,#") == NULL) {
- /* ":let var1 var2 ...": find last space. */
+ // ":let var1 var2 ...": find last space.
for (p = arg + STRLEN(arg); p >= arg; ) {
xp->xp_pattern = p;
MB_PTR_BACK(arg, p);
@@ -2904,7 +2581,7 @@ void set_context_for_expression(expand_T *xp, char_u *arg, cmdidx_T cmdidx)
}
} else if (c == '$') {
- /* environment variable */
+ // environment variable
xp->xp_context = EXPAND_ENV_VARS;
} else if (c == '=') {
got_eq = TRUE;
@@ -2916,18 +2593,20 @@ void set_context_for_expression(expand_T *xp, char_u *arg, cmdidx_T cmdidx)
} else if ((c == '<' || c == '#')
&& xp->xp_context == EXPAND_FUNCTIONS
&& vim_strchr(xp->xp_pattern, '(') == NULL) {
- /* Function name can start with "<SNR>" and contain '#'. */
+ // Function name can start with "<SNR>" and contain '#'.
break;
} else if (cmdidx != CMD_let || got_eq) {
- if (c == '"') { /* string */
- while ((c = *++xp->xp_pattern) != NUL && c != '"')
- if (c == '\\' && xp->xp_pattern[1] != NUL)
- ++xp->xp_pattern;
+ if (c == '"') { // string
+ while ((c = *++xp->xp_pattern) != NUL && c != '"') {
+ if (c == '\\' && xp->xp_pattern[1] != NUL) {
+ xp->xp_pattern++;
+ }
+ }
xp->xp_context = EXPAND_NOTHING;
- } else if (c == '\'') { /* literal string */
- /* Trick: '' is like stopping and starting a literal string. */
- while ((c = *++xp->xp_pattern) != NUL && c != '\'')
- /* skip */;
+ } else if (c == '\'') { // literal string
+ // Trick: '' is like stopping and starting a literal string.
+ while ((c = *++xp->xp_pattern) != NUL && c != '\'') {
+ }
xp->xp_context = EXPAND_NOTHING;
} else if (c == '|') {
if (xp->xp_pattern[1] == '|') {
@@ -2942,136 +2621,14 @@ void set_context_for_expression(expand_T *xp, char_u *arg, cmdidx_T cmdidx)
* anyway. */
xp->xp_context = EXPAND_EXPRESSION;
arg = xp->xp_pattern;
- if (*arg != NUL)
- while ((c = *++arg) != NUL && (c == ' ' || c == '\t'))
- /* skip */;
- }
- xp->xp_pattern = arg;
-}
-
-// TODO(ZyX-I): move to eval/ex_cmds
-
-/*
- * ":1,25call func(arg1, arg2)" function call.
- */
-void ex_call(exarg_T *eap)
-{
- char_u *arg = eap->arg;
- char_u *startarg;
- char_u *name;
- char_u *tofree;
- int len;
- typval_T rettv;
- linenr_T lnum;
- int doesrange;
- bool failed = false;
- funcdict_T fudi;
- partial_T *partial = NULL;
-
- if (eap->skip) {
- // trans_function_name() doesn't work well when skipping, use eval0()
- // instead to skip to any following command, e.g. for:
- // :if 0 | call dict.foo().bar() | endif.
- emsg_skip++;
- if (eval0(eap->arg, &rettv, &eap->nextcmd, false) != FAIL) {
- tv_clear(&rettv);
- }
- emsg_skip--;
- return;
- }
-
- tofree = trans_function_name(&arg, false, TFN_INT, &fudi, &partial);
- if (fudi.fd_newkey != NULL) {
- // Still need to give an error message for missing key.
- EMSG2(_(e_dictkey), fudi.fd_newkey);
- xfree(fudi.fd_newkey);
- }
- if (tofree == NULL) {
- return;
- }
-
- // Increase refcount on dictionary, it could get deleted when evaluating
- // the arguments.
- if (fudi.fd_dict != NULL) {
- fudi.fd_dict->dv_refcount++;
- }
-
- // If it is the name of a variable of type VAR_FUNC or VAR_PARTIAL use its
- // contents. For VAR_PARTIAL get its partial, unless we already have one
- // from trans_function_name().
- len = (int)STRLEN(tofree);
- name = deref_func_name((const char *)tofree, &len,
- partial != NULL ? NULL : &partial, false);
-
- // Skip white space to allow ":call func ()". Not good, but required for
- // backward compatibility.
- startarg = skipwhite(arg);
- rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this.
-
- if (*startarg != '(') {
- EMSG2(_("E107: Missing parentheses: %s"), eap->arg);
- goto end;
- }
-
- lnum = eap->line1;
- for (; lnum <= eap->line2; lnum++) {
- if (eap->addr_count > 0) { // -V560
- if (lnum > curbuf->b_ml.ml_line_count) {
- // If the function deleted lines or switched to another buffer
- // the line number may become invalid.
- EMSG(_(e_invrange));
- break;
+ if (*arg != NUL) {
+ while ((c = *++arg) != NUL && (c == ' ' || c == '\t')) {
}
- curwin->w_cursor.lnum = lnum;
- curwin->w_cursor.col = 0;
- curwin->w_cursor.coladd = 0;
- }
- arg = startarg;
- if (get_func_tv(name, (int)STRLEN(name), &rettv, &arg,
- eap->line1, eap->line2, &doesrange,
- true, partial, fudi.fd_dict) == FAIL) {
- failed = true;
- break;
- }
-
- // Handle a function returning a Funcref, Dictionary or List.
- if (handle_subscript((const char **)&arg, &rettv, true, true)
- == FAIL) {
- failed = true;
- break;
- }
-
- tv_clear(&rettv);
- if (doesrange) {
- break;
- }
-
- // Stop when immediately aborting on error, or when an interrupt
- // occurred or an exception was thrown but not caught.
- // get_func_tv() returned OK, so that the check for trailing
- // characters below is executed.
- if (aborting()) {
- break;
}
}
-
- if (!failed) {
- // Check for trailing illegal characters and a following command.
- if (!ends_excmd(*arg)) {
- emsg_severe = TRUE;
- EMSG(_(e_trailing));
- } else {
- eap->nextcmd = check_nextcmd(arg);
- }
- }
-
-end:
- tv_dict_unref(fudi.fd_dict);
- xfree(tofree);
+ xp->xp_pattern = arg;
}
-// TODO(ZyX-I): move to eval/ex_cmds
-
/*
* ":unlet[!] var1 ... " command.
*/
@@ -3260,22 +2817,22 @@ int do_unlet(const char *const name, const size_t name_len, const int forceit)
hashtab_T *ht = find_var_ht_dict(name, name_len, &varname, &dict);
if (ht != NULL && *varname != NUL) {
- dict_T *d;
- if (ht == &globvarht) {
- d = &globvardict;
- } else if (current_funccal != NULL
- && ht == &current_funccal->l_vars.dv_hashtab) {
- d = &current_funccal->l_vars;
- } else if (ht == &compat_hashtab) {
- d = &vimvardict;
- } else {
- dictitem_T *const di = find_var_in_ht(ht, *name, "", 0, false);
- d = di->di_tv.vval.v_dict;
- }
+ dict_T *d = get_current_funccal_dict(ht);
if (d == NULL) {
- internal_error("do_unlet()");
- return FAIL;
+ if (ht == &globvarht) {
+ d = &globvardict;
+ } else if (ht == &compat_hashtab) {
+ d = &vimvardict;
+ } else {
+ dictitem_T *const di = find_var_in_ht(ht, *name, "", 0, false);
+ d = di->di_tv.vval.v_dict;
+ }
+ if (d == NULL) {
+ internal_error("do_unlet()");
+ return FAIL;
+ }
}
+
hashitem_T *hi = hash_find(ht, (const char_u *)varname);
if (HASHITEM_EMPTY(hi)) {
hi = find_hi_in_scoped_ht((const char *)name, &ht);
@@ -3354,7 +2911,7 @@ static int do_lock_var(lval_T *lp, char_u *const name_end, const int deep,
} else if (lp->ll_range) {
listitem_T *li = lp->ll_li;
- /* (un)lock a range of List items. */
+ // (un)lock a range of List items.
while (li != NULL && (lp->ll_empty2 || lp->ll_n2 >= lp->ll_n1)) {
tv_item_lock(TV_LIST_ITEM_TV(li), deep, lock);
li = TV_LIST_ITEM_NEXT(lp->ll_list, li);
@@ -3404,7 +2961,7 @@ static char_u *cat_prefix_varname(int prefix, char_u *name)
if (len > varnamebuflen) {
xfree(varnamebuf);
- len += 10; /* some additional space */
+ len += 10; // some additional space
varnamebuf = xmalloc(len);
varnamebuflen = len;
}
@@ -3433,7 +2990,7 @@ char_u *get_user_var_name(expand_T *xp, int idx)
tdone = 0;
}
- /* Global variables */
+ // Global variables
if (gdone < globvarht.ht_used) {
if (gdone++ == 0)
hi = globvarht.ht_array;
@@ -3446,7 +3003,7 @@ char_u *get_user_var_name(expand_T *xp, int idx)
return hi->hi_key;
}
- /* b: variables */
+ // b: variables
ht = &curbuf->b_vars->dv_hashtab;
if (bdone < ht->ht_used) {
if (bdone++ == 0)
@@ -3458,7 +3015,7 @@ char_u *get_user_var_name(expand_T *xp, int idx)
return cat_prefix_varname('b', hi->hi_key);
}
- /* w: variables */
+ // w: variables
ht = &curwin->w_vars->dv_hashtab;
if (wdone < ht->ht_used) {
if (wdone++ == 0)
@@ -3470,7 +3027,7 @@ char_u *get_user_var_name(expand_T *xp, int idx)
return cat_prefix_varname('w', hi->hi_key);
}
- /* t: variables */
+ // t: variables
ht = &curtab->tp_vars->dv_hashtab;
if (tdone < ht->ht_used) {
if (tdone++ == 0)
@@ -3496,7 +3053,7 @@ char_u *get_user_var_name(expand_T *xp, int idx)
/// Return TRUE if "pat" matches "text".
/// Does not use 'cpo' and always uses 'magic'.
-static int pattern_match(char_u *pat, char_u *text, int ic)
+static int pattern_match(char_u *pat, char_u *text, bool ic)
{
int matches = 0;
regmatch_T regmatch;
@@ -3582,7 +3139,7 @@ int eval0(char_u *arg, typval_T *rettv, char_u **nextcmd, int evaluate)
*
* Return OK or FAIL.
*/
-static int eval1(char_u **arg, typval_T *rettv, int evaluate)
+int eval1(char_u **arg, typval_T *rettv, int evaluate)
{
int result;
typval_T var2;
@@ -3611,8 +3168,9 @@ static int eval1(char_u **arg, typval_T *rettv, int evaluate)
* Get the second variable.
*/
*arg = skipwhite(*arg + 1);
- if (eval1(arg, rettv, evaluate && result) == FAIL) /* recursive! */
+ if (eval1(arg, rettv, evaluate && result) == FAIL) { // recursive!
return FAIL;
+ }
/*
* Check for the ":".
@@ -3806,10 +3364,10 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate)
char_u *p;
int i;
exptype_T type = TYPE_UNKNOWN;
- int type_is = FALSE; /* TRUE for "is" and "isnot" */
+ bool type_is = false; // true for "is" and "isnot"
int len = 2;
varnumber_T n1, n2;
- int ic;
+ bool ic;
/*
* Get the first variable.
@@ -3847,7 +3405,7 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate)
}
if (!isalnum(p[len]) && p[len] != '_') {
type = len == 2 ? TYPE_EQUAL : TYPE_NEQUAL;
- type_is = TRUE;
+ type_is = true;
}
}
break;
@@ -3857,23 +3415,18 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate)
* If there is a comparative operator, use it.
*/
if (type != TYPE_UNKNOWN) {
- /* extra question mark appended: ignore case */
+ // extra question mark appended: ignore case
if (p[len] == '?') {
- ic = TRUE;
- ++len;
- }
- /* extra '#' appended: match case */
- else if (p[len] == '#') {
- ic = FALSE;
- ++len;
- }
- /* nothing appended: use 'ignorecase' */
- else
+ ic = true;
+ len++;
+ } else if (p[len] == '#') { // extra '#' appended: match case
+ ic = false;
+ len++;
+ } else { // nothing appended: use 'ignorecase'
ic = p_ic;
+ }
- /*
- * Get the second variable.
- */
+ // Get the second variable.
*arg = skipwhite(p + len);
if (eval5(arg, &var2, evaluate) == FAIL) {
tv_clear(rettv);
@@ -4018,7 +3571,7 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate)
const char *const s1 = tv_get_string_buf(rettv, buf1);
const char *const s2 = tv_get_string_buf(&var2, buf2);
if (type != TYPE_MATCH && type != TYPE_NOMATCH) {
- i = mb_strcmp_ic((bool)ic, s1, s2);
+ i = mb_strcmp_ic(ic, s1, s2);
} else {
i = 0;
}
@@ -4179,7 +3732,7 @@ static int eval5(char_u **arg, typval_T *rettv, int evaluate)
}
tv_clear(rettv);
- /* If there is a float on either side the result is a float. */
+ // If there is a float on either side the result is a float.
if (rettv->v_type == VAR_FLOAT || var2.v_type == VAR_FLOAT) {
if (op == '+')
f1 = f1 + f2;
@@ -4618,7 +4171,7 @@ eval_index(
char_u **arg,
typval_T *rettv,
int evaluate,
- int verbose /* give error messages */
+ int verbose // give error messages
)
{
bool empty1 = false;
@@ -4714,7 +4267,7 @@ eval_index(
}
}
- /* Check for the ']'. */
+ // Check for the ']'.
if (**arg != ']') {
if (verbose) {
EMSG(_(e_missbrac));
@@ -4725,7 +4278,7 @@ eval_index(
}
return FAIL;
}
- *arg = skipwhite(*arg + 1); /* skip the ']' */
+ *arg = skipwhite(*arg + 1); // skip the ']'
}
if (evaluate) {
@@ -4886,8 +4439,8 @@ eval_index(
/// @param[in] evaluate If not true, rettv is not populated.
///
/// @return OK or FAIL.
-static int get_option_tv(const char **const arg, typval_T *const rettv,
- const bool evaluate)
+int get_option_tv(const char **const arg, typval_T *const rettv,
+ const bool evaluate)
FUNC_ATTR_NONNULL_ARG(1)
{
long numval;
@@ -4917,28 +4470,29 @@ static int get_option_tv(const char **const arg, typval_T *const rettv,
opt_type = get_option_value((char_u *)(*arg), &numval,
rettv == NULL ? NULL : &stringval, opt_flags);
- if (opt_type == -3) { /* invalid name */
- if (rettv != NULL)
+ if (opt_type == -3) { // invalid name
+ if (rettv != NULL) {
EMSG2(_("E113: Unknown option: %s"), *arg);
+ }
ret = FAIL;
} else if (rettv != NULL) {
- if (opt_type == -2) { /* hidden string option */
+ if (opt_type == -2) { // hidden string option
rettv->v_type = VAR_STRING;
rettv->vval.v_string = NULL;
- } else if (opt_type == -1) { /* hidden number option */
+ } else if (opt_type == -1) { // hidden number option
rettv->v_type = VAR_NUMBER;
rettv->vval.v_number = 0;
- } else if (opt_type == 1) { /* number option */
+ } else if (opt_type == 1) { // number option
rettv->v_type = VAR_NUMBER;
rettv->vval.v_number = numval;
- } else { /* string option */
+ } else { // string option
rettv->v_type = VAR_STRING;
rettv->vval.v_string = stringval;
}
} else if (working && (opt_type == -2 || opt_type == -1))
ret = FAIL;
- *option_end = c; /* put back for error messages */
+ *option_end = c; // put back for error messages
*arg = option_end;
return ret;
@@ -4972,7 +4526,7 @@ static int get_string_tv(char_u **arg, typval_T *rettv, int evaluate)
return FAIL;
}
- /* If only parsing, set *arg and return here */
+ // If only parsing, set *arg and return here
if (!evaluate) {
*arg = p + 1;
return OK;
@@ -4996,9 +4550,9 @@ static int get_string_tv(char_u **arg, typval_T *rettv, int evaluate)
case 'r': *name++ = CAR; ++p; break;
case 't': *name++ = TAB; ++p; break;
- case 'X': /* hex: "\x1", "\x12" */
+ case 'X': // hex: "\x1", "\x12"
case 'x':
- case 'u': /* Unicode: "\u0023" */
+ case 'u': // Unicode: "\u0023"
case 'U':
if (ascii_isxdigit(p[1])) {
int n, nr;
@@ -5027,7 +4581,7 @@ static int get_string_tv(char_u **arg, typval_T *rettv, int evaluate)
}
break;
- /* octal: "\1", "\12", "\123" */
+ // octal: "\1", "\12", "\123"
case '0':
case '1':
case '2':
@@ -5096,7 +4650,7 @@ static int get_lit_string_tv(char_u **arg, typval_T *rettv, int evaluate)
return FAIL;
}
- /* If only parsing return after setting "*arg" */
+ // If only parsing return after setting "*arg"
if (!evaluate) {
*arg = p + 1;
return OK;
@@ -5279,9 +4833,6 @@ int get_copyID(void)
return current_copyID;
}
-// Used by get_func_tv()
-static garray_T funcargs = GA_EMPTY_INIT_VALUE;
-
/*
* Garbage collection for lists and dictionaries.
*
@@ -5328,11 +4879,7 @@ bool garbage_collect(bool testing)
// Don't free variables in the previous_funccal list unless they are only
// referenced through previous_funccal. This must be first, because if
// the item is referenced elsewhere the funccal must not be freed.
- for (funccall_T *fc = previous_funccal; fc != NULL; fc = fc->caller) {
- fc->fc_copyID = copyID + 1;
- ABORTING(set_ref_in_ht)(&fc->l_vars.dv_hashtab, copyID + 1, NULL);
- ABORTING(set_ref_in_ht)(&fc->l_avars.dv_hashtab, copyID + 1, NULL);
- }
+ ABORTING(set_ref_in_previous_funccal)(copyID);
// script-local variables
for (int i = 1; i <= ga_scripts.ga_len; ++i) {
@@ -5355,6 +4902,10 @@ bool garbage_collect(bool testing)
}
// buffer ShaDa additional data
ABORTING(set_ref_dict)(buf->additional_data, copyID);
+
+ // buffer callback functions
+ set_ref_in_callback(&buf->b_prompt_callback, copyID, NULL, NULL);
+ set_ref_in_callback(&buf->b_prompt_interrupt, copyID, NULL, NULL);
}
FOR_ALL_TAB_WINDOWS(tp, wp) {
@@ -5405,14 +4956,10 @@ bool garbage_collect(bool testing)
ABORTING(set_ref_in_ht)(&globvarht, copyID, NULL);
// function-local variables
- for (funccall_T *fc = current_funccal; fc != NULL; fc = fc->caller) {
- fc->fc_copyID = copyID;
- ABORTING(set_ref_in_ht)(&fc->l_vars.dv_hashtab, copyID, NULL);
- ABORTING(set_ref_in_ht)(&fc->l_avars.dv_hashtab, copyID, NULL);
- }
+ ABORTING(set_ref_in_call_stack)(copyID);
// named functions (matters for closures)
- ABORTING(set_ref_in_functions(copyID));
+ ABORTING(set_ref_in_functions)(copyID);
// Channels
{
@@ -5433,10 +4980,7 @@ bool garbage_collect(bool testing)
}
// function call arguments, if v:testing is set.
- for (int i = 0; i < funcargs.ga_len; i++) {
- ABORTING(set_ref_in_item)(((typval_T **)funcargs.ga_data)[i],
- copyID, NULL, NULL);
- }
+ ABORTING(set_ref_in_func_args)(copyID);
// v: vars
ABORTING(set_ref_in_ht)(&vimvarht, copyID, NULL);
@@ -5479,23 +5023,8 @@ bool garbage_collect(bool testing)
did_free = free_unref_items(copyID);
// 3. Check if any funccal can be freed now.
- bool did_free_funccal = false;
- for (funccall_T **pfc = &previous_funccal; *pfc != NULL;) {
- if (can_free_funccal(*pfc, copyID)) {
- funccall_T *fc = *pfc;
- *pfc = fc->caller;
- free_funccal(fc, true);
- did_free = true;
- did_free_funccal = true;
- } else {
- pfc = &(*pfc)->caller;
- }
- }
- if (did_free_funccal) {
- // When a funccal was freed some more items might be garbage
- // collected, so run again.
- (void)garbage_collect(testing);
- }
+ // This may call us back recursively.
+ did_free = did_free || free_unref_funccal(copyID, testing);
} else if (p_verbose > 0) {
verb_msg(_(
"Not enough memory to set references, garbage collection aborted!"));
@@ -5750,27 +5279,6 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack,
return abort;
}
-/// Set "copyID" in all functions available by name.
-bool set_ref_in_functions(int copyID)
-{
- int todo;
- hashitem_T *hi = NULL;
- bool abort = false;
- ufunc_T *fp;
-
- todo = (int)func_hashtab.ht_used;
- for (hi = func_hashtab.ht_array; todo > 0 && !got_int; hi++) {
- if (!HASHITEM_EMPTY(hi)) {
- todo--;
- fp = HI2UF(hi);
- if (!func_name_refcount(fp->uf_name)) {
- abort = abort || set_ref_in_func(NULL, fp, copyID);
- }
- }
- }
- return abort;
-}
-
/// Mark all lists and dicts referenced in given mark
@@ -5819,19 +5327,6 @@ static inline bool set_ref_dict(dict_T *dict, int copyID)
return false;
}
-static bool set_ref_in_funccal(funccall_T *fc, int copyID)
-{
- bool abort = false;
-
- if (fc->fc_copyID != copyID) {
- fc->fc_copyID = copyID;
- abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL);
- abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL);
- abort = abort || set_ref_in_func(NULL, fc->func, copyID);
- }
- return abort;
-}
-
/*
* Allocate a variable for a Dictionary and fill it from "*arg".
* Return OK or FAIL. Returns NOTDONE for {expr}.
@@ -5854,10 +5349,12 @@ static int dict_get_tv(char_u **arg, typval_T *rettv, int evaluate)
* But {} is an empty Dictionary.
*/
if (*start != '}') {
- if (eval1(&start, &tv, FALSE) == FAIL) /* recursive! */
+ if (eval1(&start, &tv, false) == FAIL) { // recursive!
return FAIL;
- if (*start == '}')
+ }
+ if (*start == '}') {
return NOTDONE;
+ }
}
if (evaluate) {
@@ -5868,8 +5365,9 @@ static int dict_get_tv(char_u **arg, typval_T *rettv, int evaluate)
*arg = skipwhite(*arg + 1);
while (**arg != '}' && **arg != NUL) {
- if (eval1(arg, &tvkey, evaluate) == FAIL) /* recursive! */
+ if (eval1(arg, &tvkey, evaluate) == FAIL) { // recursive!
goto failret;
+ }
if (**arg != ':') {
EMSG2(_("E720: Missing colon in Dictionary: %s"), *arg);
tv_clear(&tvkey);
@@ -5934,224 +5432,6 @@ failret:
return OK;
}
-/// Get function arguments.
-static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs,
- int *varargs, bool skip)
-{
- bool mustend = false;
- char_u *arg = *argp;
- char_u *p = arg;
- int c;
- int i;
-
- if (newargs != NULL) {
- ga_init(newargs, (int)sizeof(char_u *), 3);
- }
-
- if (varargs != NULL) {
- *varargs = false;
- }
-
- // Isolate the arguments: "arg1, arg2, ...)"
- while (*p != endchar) {
- if (p[0] == '.' && p[1] == '.' && p[2] == '.') {
- if (varargs != NULL) {
- *varargs = true;
- }
- p += 3;
- mustend = true;
- } else {
- arg = p;
- while (ASCII_ISALNUM(*p) || *p == '_') {
- p++;
- }
- if (arg == p || isdigit(*arg)
- || (p - arg == 9 && STRNCMP(arg, "firstline", 9) == 0)
- || (p - arg == 8 && STRNCMP(arg, "lastline", 8) == 0)) {
- if (!skip) {
- EMSG2(_("E125: Illegal argument: %s"), arg);
- }
- break;
- }
- if (newargs != NULL) {
- ga_grow(newargs, 1);
- c = *p;
- *p = NUL;
- arg = vim_strsave(arg);
-
- // Check for duplicate argument name.
- for (i = 0; i < newargs->ga_len; i++) {
- if (STRCMP(((char_u **)(newargs->ga_data))[i], arg) == 0) {
- EMSG2(_("E853: Duplicate argument name: %s"), arg);
- xfree(arg);
- goto err_ret;
- }
- }
- ((char_u **)(newargs->ga_data))[newargs->ga_len] = arg;
- newargs->ga_len++;
-
- *p = c;
- }
- if (*p == ',') {
- p++;
- } else {
- mustend = true;
- }
- }
- p = skipwhite(p);
- if (mustend && *p != endchar) {
- if (!skip) {
- EMSG2(_(e_invarg2), *argp);
- }
- break;
- }
- }
- if (*p != endchar) {
- goto err_ret;
- }
- p++; // skip "endchar"
-
- *argp = p;
- return OK;
-
-err_ret:
- if (newargs != NULL) {
- ga_clear_strings(newargs);
- }
- return FAIL;
-}
-
-/// Register function "fp" as using "current_funccal" as its scope.
-static void register_closure(ufunc_T *fp)
-{
- if (fp->uf_scoped == current_funccal) {
- // no change
- return;
- }
- funccal_unref(fp->uf_scoped, fp, false);
- fp->uf_scoped = current_funccal;
- current_funccal->fc_refcount++;
- ga_grow(&current_funccal->fc_funcs, 1);
- ((ufunc_T **)current_funccal->fc_funcs.ga_data)
- [current_funccal->fc_funcs.ga_len++] = fp;
-}
-
-/// Parse a lambda expression and get a Funcref from "*arg".
-///
-/// @return OK or FAIL. Returns NOTDONE for dict or {expr}.
-static int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate)
-{
- garray_T newargs = GA_EMPTY_INIT_VALUE;
- garray_T *pnewargs;
- ufunc_T *fp = NULL;
- int varargs;
- int ret;
- char_u *start = skipwhite(*arg + 1);
- char_u *s, *e;
- static int lambda_no = 0;
- int *old_eval_lavars = eval_lavars_used;
- int eval_lavars = false;
-
- // First, check if this is a lambda expression. "->" must exists.
- ret = get_function_args(&start, '-', NULL, NULL, true);
- if (ret == FAIL || *start != '>') {
- return NOTDONE;
- }
-
- // Parse the arguments again.
- if (evaluate) {
- pnewargs = &newargs;
- } else {
- pnewargs = NULL;
- }
- *arg = skipwhite(*arg + 1);
- ret = get_function_args(arg, '-', pnewargs, &varargs, false);
- if (ret == FAIL || **arg != '>') {
- goto errret;
- }
-
- // Set up a flag for checking local variables and arguments.
- if (evaluate) {
- eval_lavars_used = &eval_lavars;
- }
-
- // Get the start and the end of the expression.
- *arg = skipwhite(*arg + 1);
- s = *arg;
- ret = skip_expr(arg);
- if (ret == FAIL) {
- goto errret;
- }
- e = *arg;
- *arg = skipwhite(*arg);
- if (**arg != '}') {
- goto errret;
- }
- (*arg)++;
-
- if (evaluate) {
- int len, flags = 0;
- char_u *p;
- char_u name[20];
- partial_T *pt;
- garray_T newlines;
-
- lambda_no++;
- snprintf((char *)name, sizeof(name), "<lambda>%d", lambda_no);
-
- fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1);
- pt = xcalloc(1, sizeof(partial_T));
-
- ga_init(&newlines, (int)sizeof(char_u *), 1);
- ga_grow(&newlines, 1);
-
- // Add "return " before the expression.
- len = 7 + e - s + 1;
- p = (char_u *)xmalloc(len);
- ((char_u **)(newlines.ga_data))[newlines.ga_len++] = p;
- STRCPY(p, "return ");
- STRLCPY(p + 7, s, e - s + 1);
-
- fp->uf_refcount = 1;
- STRCPY(fp->uf_name, name);
- hash_add(&func_hashtab, UF2HIKEY(fp));
- fp->uf_args = newargs;
- fp->uf_lines = newlines;
- if (current_funccal != NULL && eval_lavars) {
- flags |= FC_CLOSURE;
- register_closure(fp);
- } else {
- fp->uf_scoped = NULL;
- }
-
- if (prof_def_func()) {
- func_do_profile(fp);
- }
- if (sandbox) {
- flags |= FC_SANDBOX;
- }
- fp->uf_varargs = true;
- fp->uf_flags = flags;
- fp->uf_calls = 0;
- fp->uf_script_ctx = current_sctx;
- fp->uf_script_ctx.sc_lnum += sourcing_lnum - newlines.ga_len;
-
- pt->pt_func = fp;
- pt->pt_refcount = 1;
- rettv->vval.v_partial = pt;
- rettv->v_type = VAR_PARTIAL;
- }
-
- eval_lavars_used = old_eval_lavars;
- return OK;
-
-errret:
- ga_clear_strings(&newargs);
- xfree(fp);
- eval_lavars_used = old_eval_lavars;
- return FAIL;
-}
-
/// Convert the string to a floating point number
///
/// This uses strtod(). setlocale(LC_NUMERIC, "C") has been used earlier to
@@ -6226,728 +5506,9 @@ static int get_env_tv(char_u **arg, typval_T *rettv, int evaluate)
return OK;
}
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-
-#ifdef _MSC_VER
-// This prevents MSVC from replacing the functions with intrinsics,
-// and causing errors when trying to get their addresses in funcs.generated.h
-#pragma function (ceil)
-#pragma function (floor)
-#endif
-
-PRAGMA_DIAG_PUSH_IGNORE_MISSING_PROTOTYPES
-# include "funcs.generated.h"
-PRAGMA_DIAG_POP
-#endif
-
-/*
- * Function given to ExpandGeneric() to obtain the list of internal
- * or user defined function names.
- */
-char_u *get_function_name(expand_T *xp, int idx)
-{
- static int intidx = -1;
- char_u *name;
-
- if (idx == 0)
- intidx = -1;
- if (intidx < 0) {
- name = get_user_func_name(xp, idx);
- if (name != NULL)
- return name;
- }
- while ( (size_t)++intidx < ARRAY_SIZE(functions)
- && functions[intidx].name[0] == '\0') {
- }
-
- if ((size_t)intidx >= ARRAY_SIZE(functions)) {
- return NULL;
- }
-
- const char *const key = functions[intidx].name;
- const size_t key_len = strlen(key);
- memcpy(IObuff, key, key_len);
- IObuff[key_len] = '(';
- if (functions[intidx].max_argc == 0) {
- IObuff[key_len + 1] = ')';
- IObuff[key_len + 2] = NUL;
- } else {
- IObuff[key_len + 1] = NUL;
- }
- return IObuff;
-}
-
-/*
- * Function given to ExpandGeneric() to obtain the list of internal or
- * user defined variable or function names.
- */
-char_u *get_expr_name(expand_T *xp, int idx)
-{
- static int intidx = -1;
- char_u *name;
-
- if (idx == 0)
- intidx = -1;
- if (intidx < 0) {
- name = get_function_name(xp, idx);
- if (name != NULL)
- return name;
- }
- return get_user_var_name(xp, ++intidx);
-}
-
-/// Find internal function in hash functions
-///
-/// @param[in] name Name of the function.
-///
-/// Returns pointer to the function definition or NULL if not found.
-static const VimLFuncDef *find_internal_func(const char *const name)
- FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE FUNC_ATTR_NONNULL_ALL
-{
- size_t len = strlen(name);
- return find_internal_func_gperf(name, len);
-}
-
-/// Return name of the function corresponding to `name`
-///
-/// If `name` points to variable that is either a function or partial then
-/// corresponding function name is returned. Otherwise it returns `name` itself.
-///
-/// @param[in] name Function name to check.
-/// @param[in,out] lenp Location where length of the returned name is stored.
-/// Must be set to the length of the `name` argument.
-/// @param[out] partialp Location where partial will be stored if found
-/// function appears to be a partial. May be NULL if this
-/// is not needed.
-/// @param[in] no_autoload If true, do not source autoload scripts if function
-/// was not found.
-///
-/// @return name of the function.
-static char_u *deref_func_name(const char *name, int *lenp,
- partial_T **const partialp, bool no_autoload)
- FUNC_ATTR_NONNULL_ARG(1, 2)
-{
- if (partialp != NULL) {
- *partialp = NULL;
- }
-
- dictitem_T *const v = find_var(name, (size_t)(*lenp), NULL, no_autoload);
- if (v != NULL && v->di_tv.v_type == VAR_FUNC) {
- if (v->di_tv.vval.v_string == NULL) { // just in case
- *lenp = 0;
- return (char_u *)"";
- }
- *lenp = (int)STRLEN(v->di_tv.vval.v_string);
- return v->di_tv.vval.v_string;
- }
-
- if (v != NULL && v->di_tv.v_type == VAR_PARTIAL) {
- partial_T *const pt = v->di_tv.vval.v_partial;
-
- if (pt == NULL) { // just in case
- *lenp = 0;
- return (char_u *)"";
- }
- if (partialp != NULL) {
- *partialp = pt;
- }
- char_u *s = partial_name(pt);
- *lenp = (int)STRLEN(s);
- return s;
- }
-
- return (char_u *)name;
-}
-
-/*
- * Allocate a variable for the result of a function.
- * Return OK or FAIL.
- */
-static int
-get_func_tv(
- const char_u *name, // name of the function
- int len, // length of "name"
- typval_T *rettv,
- char_u **arg, // argument, pointing to the '('
- linenr_T firstline, // first line of range
- linenr_T lastline, // last line of range
- int *doesrange, // return: function handled range
- int evaluate,
- partial_T *partial, // for extra arguments
- dict_T *selfdict // Dictionary for "self"
-)
-{
- char_u *argp;
- int ret = OK;
- typval_T argvars[MAX_FUNC_ARGS + 1]; /* vars for arguments */
- int argcount = 0; /* number of arguments found */
-
- /*
- * Get the arguments.
- */
- argp = *arg;
- while (argcount < MAX_FUNC_ARGS - (partial == NULL ? 0 : partial->pt_argc)) {
- argp = skipwhite(argp + 1); // skip the '(' or ','
- if (*argp == ')' || *argp == ',' || *argp == NUL) {
- break;
- }
- if (eval1(&argp, &argvars[argcount], evaluate) == FAIL) {
- ret = FAIL;
- break;
- }
- ++argcount;
- if (*argp != ',')
- break;
- }
- if (*argp == ')')
- ++argp;
- else
- ret = FAIL;
-
- if (ret == OK) {
- int i = 0;
-
- if (get_vim_var_nr(VV_TESTING)) {
- // Prepare for calling garbagecollect_for_testing(), need to know
- // what variables are used on the call stack.
- if (funcargs.ga_itemsize == 0) {
- ga_init(&funcargs, (int)sizeof(typval_T *), 50);
- }
- for (i = 0; i < argcount; i++) {
- ga_grow(&funcargs, 1);
- ((typval_T **)funcargs.ga_data)[funcargs.ga_len++] = &argvars[i];
- }
- }
- ret = call_func(name, len, rettv, argcount, argvars, NULL,
- firstline, lastline, doesrange, evaluate,
- partial, selfdict);
-
- funcargs.ga_len -= i;
- } else if (!aborting()) {
- if (argcount == MAX_FUNC_ARGS) {
- emsg_funcname(N_("E740: Too many arguments for function %s"), name);
- } else {
- emsg_funcname(N_("E116: Invalid arguments for function %s"), name);
- }
- }
-
- while (--argcount >= 0) {
- tv_clear(&argvars[argcount]);
- }
-
- *arg = skipwhite(argp);
- return ret;
-}
-
-typedef enum {
- ERROR_UNKNOWN = 0,
- ERROR_TOOMANY,
- ERROR_TOOFEW,
- ERROR_SCRIPT,
- ERROR_DICT,
- ERROR_NONE,
- ERROR_OTHER,
- ERROR_BOTH,
- ERROR_DELETED,
-} FnameTransError;
-
-#define FLEN_FIXED 40
-
-/// In a script transform script-local names into actually used names
-///
-/// Transforms "<SID>" and "s:" prefixes to `K_SNR {N}` (e.g. K_SNR "123") and
-/// "<SNR>" prefix to `K_SNR`. Uses `fname_buf` buffer that is supposed to have
-/// #FLEN_FIXED + 1 length when it fits, otherwise it allocates memory.
-///
-/// @param[in] name Name to transform.
-/// @param fname_buf Buffer to save resulting function name to, if it fits.
-/// Must have at least #FLEN_FIXED + 1 length.
-/// @param[out] tofree Location where pointer to an allocated memory is saved
-/// in case result does not fit into fname_buf.
-/// @param[out] error Location where error type is saved, @see
-/// FnameTransError.
-///
-/// @return transformed name: either `fname_buf` or a pointer to an allocated
-/// memory.
-static char_u *fname_trans_sid(const char_u *const name,
- char_u *const fname_buf,
- char_u **const tofree, int *const error)
- FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
-{
- char_u *fname;
- const int llen = eval_fname_script((const char *)name);
- if (llen > 0) {
- fname_buf[0] = K_SPECIAL;
- fname_buf[1] = KS_EXTRA;
- fname_buf[2] = (int)KE_SNR;
- int i = 3;
- if (eval_fname_sid((const char *)name)) { // "<SID>" or "s:"
- if (current_sctx.sc_sid <= 0) {
- *error = ERROR_SCRIPT;
- } else {
- snprintf((char *)fname_buf + 3, FLEN_FIXED + 1, "%" PRId64 "_",
- (int64_t)current_sctx.sc_sid);
- i = (int)STRLEN(fname_buf);
- }
- }
- if (i + STRLEN(name + llen) < FLEN_FIXED) {
- STRCPY(fname_buf + i, name + llen);
- fname = fname_buf;
- } else {
- fname = xmalloc(i + STRLEN(name + llen) + 1);
- *tofree = fname;
- memmove(fname, fname_buf, (size_t)i);
- STRCPY(fname + i, name + llen);
- }
- } else {
- fname = (char_u *)name;
- }
-
- return fname;
-}
-
-/// Mark all lists and dicts referenced through function "name" with "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.
-bool set_ref_in_func(char_u *name, ufunc_T *fp_in, int copyID)
-{
- ufunc_T *fp = fp_in;
- funccall_T *fc;
- int error = ERROR_NONE;
- char_u fname_buf[FLEN_FIXED + 1];
- char_u *tofree = NULL;
- char_u *fname;
- bool abort = false;
- if (name == NULL && fp_in == NULL) {
- return false;
- }
-
- if (fp_in == NULL) {
- fname = fname_trans_sid(name, fname_buf, &tofree, &error);
- fp = find_func(fname);
- }
- if (fp != NULL) {
- for (fc = fp->uf_scoped; fc != NULL; fc = fc->func->uf_scoped) {
- abort = abort || set_ref_in_funccal(fc, copyID);
- }
- }
- xfree(tofree);
- return abort;
-}
-
-/// Call a function with its resolved parameters
-///
-/// "argv_func", when not NULL, can be used to fill in arguments only when the
-/// invoked function uses them. It is called like this:
-/// new_argcount = argv_func(current_argcount, argv, called_func_argcount)
-///
-/// @return FAIL if function cannot be called, else OK (even if an error
-/// occurred while executing the function! Set `msg_list` to capture
-/// the error, see do_cmdline()).
-int
-call_func(
- const char_u *funcname, // name of the function
- int len, // length of "name"
- typval_T *rettv, // [out] value goes here
- int argcount_in, // number of "argvars"
- typval_T *argvars_in, // vars for arguments, must have "argcount"
- // PLUS ONE elements!
- ArgvFunc argv_func, // function to fill in argvars
- linenr_T firstline, // first line of range
- linenr_T lastline, // last line of range
- int *doesrange, // [out] function handled range
- bool evaluate,
- partial_T *partial, // optional, can be NULL
- dict_T *selfdict_in // Dictionary for "self"
-)
- FUNC_ATTR_NONNULL_ARG(1, 3, 5, 9)
-{
- int ret = FAIL;
- int error = ERROR_NONE;
- ufunc_T *fp;
- char_u fname_buf[FLEN_FIXED + 1];
- char_u *tofree = NULL;
- char_u *fname;
- char_u *name;
- int argcount = argcount_in;
- typval_T *argvars = argvars_in;
- dict_T *selfdict = selfdict_in;
- typval_T argv[MAX_FUNC_ARGS + 1]; // used when "partial" is not NULL
- int argv_clear = 0;
-
- // Initialize rettv so that it is safe for caller to invoke clear_tv(rettv)
- // even when call_func() returns FAIL.
- rettv->v_type = VAR_UNKNOWN;
-
- // Make a copy of the name, if it comes from a funcref variable it could
- // be changed or deleted in the called function.
- name = vim_strnsave(funcname, len);
-
- fname = fname_trans_sid(name, fname_buf, &tofree, &error);
-
- *doesrange = false;
-
- if (partial != NULL) {
- // When the function has a partial with a dict and there is a dict
- // argument, use the dict argument. That is backwards compatible.
- // When the dict was bound explicitly use the one from the partial.
- if (partial->pt_dict != NULL
- && (selfdict_in == NULL || !partial->pt_auto)) {
- selfdict = partial->pt_dict;
- }
- if (error == ERROR_NONE && partial->pt_argc > 0) {
- for (argv_clear = 0; argv_clear < partial->pt_argc; argv_clear++) {
- tv_copy(&partial->pt_argv[argv_clear], &argv[argv_clear]);
- }
- for (int i = 0; i < argcount_in; i++) {
- argv[i + argv_clear] = argvars_in[i];
- }
- argvars = argv;
- argcount = partial->pt_argc + argcount_in;
- }
- }
-
- if (error == ERROR_NONE && evaluate) {
- char_u *rfname = fname;
-
- /* Ignore "g:" before a function name. */
- if (fname[0] == 'g' && fname[1] == ':') {
- rfname = fname + 2;
- }
-
- rettv->v_type = VAR_NUMBER; /* default rettv is number zero */
- rettv->vval.v_number = 0;
- error = ERROR_UNKNOWN;
-
- if (partial == vvlua_partial) {
- if (len > 0) {
- error = ERROR_NONE;
- executor_call_lua((const char *)funcname, len,
- argvars, argcount, rettv);
- }
- } else if (!builtin_function((const char *)rfname, -1)) {
- // User defined function.
- if (partial != NULL && partial->pt_func != NULL) {
- fp = partial->pt_func;
- } else {
- fp = find_func(rfname);
- }
-
- // Trigger FuncUndefined event, may load the function.
- if (fp == NULL
- && apply_autocmds(EVENT_FUNCUNDEFINED, rfname, rfname, TRUE, NULL)
- && !aborting()) {
- /* executed an autocommand, search for the function again */
- fp = find_func(rfname);
- }
- // Try loading a package.
- if (fp == NULL && script_autoload((const char *)rfname, STRLEN(rfname),
- true) && !aborting()) {
- // Loaded a package, search for the function again.
- fp = find_func(rfname);
- }
-
- if (fp != NULL && (fp->uf_flags & FC_DELETED)) {
- error = ERROR_DELETED;
- } else if (fp != NULL) {
- if (argv_func != NULL) {
- argcount = argv_func(argcount, argvars, fp->uf_args.ga_len);
- }
- if (fp->uf_flags & FC_RANGE) {
- *doesrange = true;
- }
- if (argcount < fp->uf_args.ga_len) {
- error = ERROR_TOOFEW;
- } else if (!fp->uf_varargs && argcount > fp->uf_args.ga_len) {
- error = ERROR_TOOMANY;
- } else if ((fp->uf_flags & FC_DICT) && selfdict == NULL) {
- error = ERROR_DICT;
- } else {
- // Call the user function.
- call_user_func(fp, argcount, argvars, rettv, firstline, lastline,
- (fp->uf_flags & FC_DICT) ? selfdict : NULL);
- error = ERROR_NONE;
- }
- }
- } else {
- // Find the function name in the table, call its implementation.
- const VimLFuncDef *const fdef = find_internal_func((const char *)fname);
- if (fdef != NULL) {
- if (argcount < fdef->min_argc) {
- error = ERROR_TOOFEW;
- } else if (argcount > fdef->max_argc) {
- error = ERROR_TOOMANY;
- } else {
- argvars[argcount].v_type = VAR_UNKNOWN;
- fdef->func(argvars, rettv, fdef->data);
- error = ERROR_NONE;
- }
- }
- }
- /*
- * The function call (or "FuncUndefined" autocommand sequence) might
- * have been aborted by an error, an interrupt, or an explicitly thrown
- * exception that has not been caught so far. This situation can be
- * tested for by calling aborting(). For an error in an internal
- * function or for the "E132" error in call_user_func(), however, the
- * throw point at which the "force_abort" flag (temporarily reset by
- * emsg()) is normally updated has not been reached yet. We need to
- * update that flag first to make aborting() reliable.
- */
- update_force_abort();
- }
- if (error == ERROR_NONE)
- ret = OK;
-
- /*
- * Report an error unless the argument evaluation or function call has been
- * cancelled due to an aborting error, an interrupt, or an exception.
- */
- if (!aborting()) {
- switch (error) {
- case ERROR_UNKNOWN:
- emsg_funcname(N_("E117: Unknown function: %s"), name);
- break;
- case ERROR_DELETED:
- emsg_funcname(N_("E933: Function was deleted: %s"), name);
- break;
- case ERROR_TOOMANY:
- emsg_funcname(e_toomanyarg, name);
- break;
- case ERROR_TOOFEW:
- emsg_funcname(N_("E119: Not enough arguments for function: %s"),
- name);
- break;
- case ERROR_SCRIPT:
- emsg_funcname(N_("E120: Using <SID> not in a script context: %s"),
- name);
- break;
- case ERROR_DICT:
- emsg_funcname(N_("E725: Calling dict function without Dictionary: %s"),
- name);
- break;
- }
- }
-
- while (argv_clear > 0) {
- tv_clear(&argv[--argv_clear]);
- }
- xfree(tofree);
- xfree(name);
-
- return ret;
-}
-
-/// Give an error message with a function name. Handle <SNR> things.
-///
-/// @param ermsg must be passed without translation (use N_() instead of _()).
-/// @param name function name
-static void emsg_funcname(char *ermsg, const char_u *name)
-{
- char_u *p;
-
- if (*name == K_SPECIAL) {
- p = concat_str((char_u *)"<SNR>", name + 3);
- } else {
- p = (char_u *)name;
- }
-
- EMSG2(_(ermsg), p);
-
- if (p != name) {
- xfree(p);
- }
-}
-
-/*
- * Return TRUE for a non-zero Number and a non-empty String.
- */
-static int non_zero_arg(typval_T *argvars)
-{
- return ((argvars[0].v_type == VAR_NUMBER
- && argvars[0].vval.v_number != 0)
- || (argvars[0].v_type == VAR_SPECIAL
- && argvars[0].vval.v_special == kSpecialVarTrue)
- || (argvars[0].v_type == VAR_STRING
- && argvars[0].vval.v_string != NULL
- && *argvars[0].vval.v_string != NUL));
-}
-
-/*********************************************
- * Implementation of the built-in functions
- */
-
-
-// Apply a floating point C function on a typval with one float_T.
-//
-// Some versions of glibc on i386 have an optimization that makes it harder to
-// call math functions indirectly from inside an inlined function, causing
-// compile-time errors. Avoid `inline` in that case. #3072
-static void float_op_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- float_T f;
- float_T (*function)(float_T) = (float_T (*)(float_T))fptr;
-
- rettv->v_type = VAR_FLOAT;
- if (tv_get_float_chk(argvars, &f)) {
- rettv->vval.v_float = function(f);
- } else {
- rettv->vval.v_float = 0.0;
- }
-}
-
-static void api_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (check_restricted() || check_secure()) {
- return;
- }
-
- ApiDispatchWrapper fn = (ApiDispatchWrapper)fptr;
-
- Array args = ARRAY_DICT_INIT;
-
- for (typval_T *tv = argvars; tv->v_type != VAR_UNKNOWN; tv++) {
- ADD(args, vim_to_object(tv));
- }
-
- Error err = ERROR_INIT;
- Object result = fn(VIML_INTERNAL_CALL, args, &err);
-
- if (ERROR_SET(&err)) {
- emsgf_multiline((const char *)e_api_error, err.msg);
- goto end;
- }
-
- if (!object_to_vim(result, rettv, &err)) {
- EMSG2(_("Error converting the call result: %s"), err.msg);
- }
-
-end:
- api_free_array(args);
- api_free_object(result);
- api_clear_error(&err);
-}
-
-/*
- * "abs(expr)" function
- */
-static void f_abs(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (argvars[0].v_type == VAR_FLOAT) {
- float_op_wrapper(argvars, rettv, (FunPtr)&fabs);
- } else {
- varnumber_T n;
- bool error = false;
-
- n = tv_get_number_chk(&argvars[0], &error);
- if (error) {
- rettv->vval.v_number = -1;
- } else if (n > 0) {
- rettv->vval.v_number = n;
- } else {
- rettv->vval.v_number = -n;
- }
- }
-}
-
-/*
- * "add(list, item)" function
- */
-static void f_add(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = 1; // Default: failed.
- if (argvars[0].v_type == VAR_LIST) {
- list_T *const l = argvars[0].vval.v_list;
- if (!tv_check_lock(tv_list_locked(l), N_("add() argument"), TV_TRANSLATE)) {
- tv_list_append_tv(l, &argvars[1]);
- tv_copy(&argvars[0], rettv);
- }
- } else {
- EMSG(_(e_listreq));
- }
-}
-
-/*
- * "and(expr, expr)" function
- */
-static void f_and(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL)
- & tv_get_number_chk(&argvars[1], NULL);
-}
-
-
-/// "api_info()" function
-static void f_api_info(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- Dictionary metadata = api_metadata();
- (void)object_to_vim(DICTIONARY_OBJ(metadata), rettv, NULL);
- api_free_dictionary(metadata);
-}
-
-// "append(lnum, string/list)" function
-static void f_append(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const linenr_T lnum = tv_get_lnum(&argvars[0]);
-
- set_buffer_lines(curbuf, lnum, true, &argvars[1], rettv);
-}
-
-// "appendbufline(buf, lnum, string/list)" function
-static void f_appendbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- buf_T *const buf = tv_get_buf(&argvars[0], false);
- if (buf == NULL) {
- rettv->vval.v_number = 1; // FAIL
- } else {
- const linenr_T lnum = tv_get_lnum_buf(&argvars[1], buf);
- set_buffer_lines(buf, lnum, true, &argvars[2], rettv);
- }
-}
-
-static void f_argc(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (argvars[0].v_type == VAR_UNKNOWN) {
- // use the current window
- rettv->vval.v_number = ARGCOUNT;
- } else if (argvars[0].v_type == VAR_NUMBER
- && tv_get_number(&argvars[0]) == -1) {
- // use the global argument list
- rettv->vval.v_number = GARGCOUNT;
- } else {
- // use the argument list of the specified window
- win_T *wp = find_win_by_nr_or_id(&argvars[0]);
- if (wp != NULL) {
- rettv->vval.v_number = WARGCOUNT(wp);
- } else {
- rettv->vval.v_number = -1;
- }
- }
-}
-
-/*
- * "argidx()" function
- */
-static void f_argidx(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = curwin->w_arg_idx;
-}
-
-/// "arglistid" function
-static void f_arglistid(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = -1;
- win_T *wp = find_tabwin(&argvars[0], &argvars[1]);
- if (wp != NULL) {
- rettv->vval.v_number = wp->w_alist->id;
- }
-}
-
/// Get the argument list for a given window
-static void get_arglist_as_rettv(aentry_T *arglist, int argcount,
- typval_T *rettv)
+void get_arglist_as_rettv(aentry_T *arglist, int argcount,
+ typval_T *rettv)
{
tv_list_alloc_ret(rettv, argcount);
if (arglist != NULL) {
@@ -6958,46 +5519,8 @@ static void get_arglist_as_rettv(aentry_T *arglist, int argcount,
}
}
-/*
- * "argv(nr)" function
- */
-static void f_argv(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- aentry_T *arglist = NULL;
- int argcount = -1;
-
- if (argvars[0].v_type != VAR_UNKNOWN) {
- if (argvars[1].v_type == VAR_UNKNOWN) {
- arglist = ARGLIST;
- argcount = ARGCOUNT;
- } else if (argvars[1].v_type == VAR_NUMBER
- && tv_get_number(&argvars[1]) == -1) {
- arglist = GARGLIST;
- argcount = GARGCOUNT;
- } else {
- win_T *wp = find_win_by_nr_or_id(&argvars[1]);
- if (wp != NULL) {
- // Use the argument list of the specified window
- arglist = WARGLIST(wp);
- argcount = WARGCOUNT(wp);
- }
- }
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
- int idx = tv_get_number_chk(&argvars[0], NULL);
- if (arglist != NULL && idx >= 0 && idx < argcount) {
- rettv->vval.v_string = (char_u *)xstrdup(
- (const char *)alist_name(&arglist[idx]));
- } else if (idx == -1) {
- get_arglist_as_rettv(arglist, argcount, rettv);
- }
- } else {
- get_arglist_as_rettv(ARGLIST, ARGCOUNT, rettv);
- }
-}
-
// Prepare "gap" for an assert error and add the sourcing position.
-static void prepare_assert_error(garray_T *gap)
+void prepare_assert_error(garray_T *gap)
{
char buf[NUMBUFLEN];
@@ -7051,9 +5574,9 @@ static void ga_concat_esc(garray_T *gap, char_u *str)
}
// Fill "gap" with information about an assert error.
-static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv,
- char_u *exp_str, typval_T *exp_tv,
- typval_T *got_tv, assert_type_T atype)
+void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv,
+ char_u *exp_str, typval_T *exp_tv,
+ typval_T *got_tv, assert_type_T atype)
{
char_u *tofree;
@@ -7095,7 +5618,7 @@ static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv,
}
// Add an assert error to v:errors.
-static void assert_error(garray_T *gap)
+void assert_error(garray_T *gap)
{
struct vimvar *vp = &vimvars[VV_ERRORS];
@@ -7107,7 +5630,7 @@ static void assert_error(garray_T *gap)
(const char *)gap->ga_data, (ptrdiff_t)gap->ga_len);
}
-static int assert_equal_common(typval_T *argvars, assert_type_T atype)
+int assert_equal_common(typval_T *argvars, assert_type_T atype)
FUNC_ATTR_NONNULL_ALL
{
garray_T ga;
@@ -7124,7 +5647,7 @@ static int assert_equal_common(typval_T *argvars, assert_type_T atype)
return 0;
}
-static int assert_equalfile(typval_T *argvars)
+int assert_equalfile(typval_T *argvars)
FUNC_ATTR_NONNULL_ALL
{
char buf1[NUMBUFLEN];
@@ -7178,120 +5701,7 @@ static int assert_equalfile(typval_T *argvars)
return 0;
}
-static void f_assert_beeps(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *const cmd = tv_get_string_chk(&argvars[0]);
- garray_T ga;
- int ret = 0;
-
- called_vim_beep = false;
- suppress_errthrow = true;
- emsg_silent = false;
- do_cmdline_cmd(cmd);
- if (!called_vim_beep) {
- prepare_assert_error(&ga);
- ga_concat(&ga, (const char_u *)"command did not beep: ");
- ga_concat(&ga, (const char_u *)cmd);
- assert_error(&ga);
- ga_clear(&ga);
- ret = 1;
- }
-
- suppress_errthrow = false;
- emsg_on_display = false;
- rettv->vval.v_number = ret;
-}
-
-// "assert_equal(expected, actual[, msg])" function
-static void f_assert_equal(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = assert_equal_common(argvars, ASSERT_EQUAL);
-}
-
-// "assert_equalfile(fname-one, fname-two)" function
-static void f_assert_equalfile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = assert_equalfile(argvars);
-}
-
-// "assert_notequal(expected, actual[, msg])" function
-static void f_assert_notequal(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = assert_equal_common(argvars, ASSERT_NOTEQUAL);
-}
-
-/// "assert_report(msg)
-static void f_assert_report(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- garray_T ga;
-
- prepare_assert_error(&ga);
- ga_concat(&ga, (const char_u *)tv_get_string(&argvars[0]));
- assert_error(&ga);
- ga_clear(&ga);
- rettv->vval.v_number = 1;
-}
-
-/// "assert_exception(string[, msg])" function
-static void f_assert_exception(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = assert_exception(argvars);
-}
-
-/// "assert_fails(cmd [, error [, msg]])" function
-static void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *const cmd = tv_get_string_chk(&argvars[0]);
- garray_T ga;
- int ret = 0;
- int save_trylevel = trylevel;
-
- // trylevel must be zero for a ":throw" command to be considered failed
- trylevel = 0;
- called_emsg = false;
- suppress_errthrow = true;
- emsg_silent = true;
-
- do_cmdline_cmd(cmd);
- if (!called_emsg) {
- prepare_assert_error(&ga);
- ga_concat(&ga, (const char_u *)"command did not fail: ");
- if (argvars[1].v_type != VAR_UNKNOWN
- && argvars[2].v_type != VAR_UNKNOWN) {
- char *const tofree = encode_tv2echo(&argvars[2], NULL);
- ga_concat(&ga, (char_u *)tofree);
- xfree(tofree);
- } else {
- ga_concat(&ga, (const char_u *)cmd);
- }
- assert_error(&ga);
- ga_clear(&ga);
- ret = 1;
- } else if (argvars[1].v_type != VAR_UNKNOWN) {
- char buf[NUMBUFLEN];
- const char *const error = tv_get_string_buf_chk(&argvars[1], buf);
-
- if (error == NULL
- || strstr((char *)vimvars[VV_ERRMSG].vv_str, error) == NULL) {
- prepare_assert_error(&ga);
- fill_assert_error(&ga, &argvars[2], NULL, &argvars[1],
- &vimvars[VV_ERRMSG].vv_tv, ASSERT_OTHER);
- assert_error(&ga);
- ga_clear(&ga);
- ret = 1;
- }
- }
-
- trylevel = save_trylevel;
- called_emsg = false;
- suppress_errthrow = false;
- emsg_silent = false;
- emsg_on_display = false;
- set_vim_var_string(VV_ERRMSG, NULL, 0);
- rettv->vval.v_number = ret;
-}
-
-static int assert_inrange(typval_T *argvars)
+int assert_inrange(typval_T *argvars)
FUNC_ATTR_NONNULL_ALL
{
bool error = false;
@@ -7320,7 +5730,7 @@ static int assert_inrange(typval_T *argvars)
}
// Common for assert_true() and assert_false().
-static int assert_bool(typval_T *argvars, bool is_true)
+int assert_bool(typval_T *argvars, bool is_true)
FUNC_ATTR_NONNULL_ALL
{
bool error = false;
@@ -7345,7 +5755,7 @@ static int assert_bool(typval_T *argvars, bool is_true)
return 0;
}
-static int assert_exception(typval_T *argvars)
+int assert_exception(typval_T *argvars)
FUNC_ATTR_NONNULL_ALL
{
garray_T ga;
@@ -7369,13 +5779,60 @@ static int assert_exception(typval_T *argvars)
return 0;
}
-// "assert_false(actual[, msg])" function
-static void f_assert_false(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+int assert_fails(typval_T *argvars)
+ FUNC_ATTR_NONNULL_ALL
{
- rettv->vval.v_number = assert_bool(argvars, false);
+ const char *const cmd = tv_get_string_chk(&argvars[0]);
+ garray_T ga;
+ int ret = 0;
+ int save_trylevel = trylevel;
+
+ // trylevel must be zero for a ":throw" command to be considered failed
+ trylevel = 0;
+ called_emsg = false;
+ suppress_errthrow = true;
+ emsg_silent = true;
+
+ do_cmdline_cmd(cmd);
+ if (!called_emsg) {
+ prepare_assert_error(&ga);
+ ga_concat(&ga, (const char_u *)"command did not fail: ");
+ if (argvars[1].v_type != VAR_UNKNOWN
+ && argvars[2].v_type != VAR_UNKNOWN) {
+ char *const tofree = encode_tv2echo(&argvars[2], NULL);
+ ga_concat(&ga, (char_u *)tofree);
+ xfree(tofree);
+ } else {
+ ga_concat(&ga, (const char_u *)cmd);
+ }
+ assert_error(&ga);
+ ga_clear(&ga);
+ ret = 1;
+ } else if (argvars[1].v_type != VAR_UNKNOWN) {
+ char buf[NUMBUFLEN];
+ const char *const error = tv_get_string_buf_chk(&argvars[1], buf);
+
+ if (error == NULL
+ || strstr((char *)vimvars[VV_ERRMSG].vv_str, error) == NULL) {
+ prepare_assert_error(&ga);
+ fill_assert_error(&ga, &argvars[2], NULL, &argvars[1],
+ &vimvars[VV_ERRMSG].vv_tv, ASSERT_OTHER);
+ assert_error(&ga);
+ ga_clear(&ga);
+ ret = 1;
+ }
+ }
+
+ trylevel = save_trylevel;
+ called_emsg = false;
+ suppress_errthrow = false;
+ emsg_silent = false;
+ emsg_on_display = false;
+ set_vim_var_string(VV_ERRMSG, NULL, 0);
+ return ret;
}
-static int assert_match_common(typval_T *argvars, assert_type_T atype)
+int assert_match_common(typval_T *argvars, assert_type_T atype)
FUNC_ATTR_NONNULL_ALL
{
char buf1[NUMBUFLEN];
@@ -7397,1523 +5854,6 @@ static int assert_match_common(typval_T *argvars, assert_type_T atype)
return 0;
}
-/// "assert_inrange(lower, upper[, msg])" function
-static void f_assert_inrange(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = assert_inrange(argvars);
-}
-
-/// "assert_match(pattern, actual[, msg])" function
-static void f_assert_match(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = assert_match_common(argvars, ASSERT_MATCH);
-}
-
-/// "assert_notmatch(pattern, actual[, msg])" function
-static void f_assert_notmatch(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = assert_match_common(argvars, ASSERT_NOTMATCH);
-}
-
-// "assert_true(actual[, msg])" function
-static void f_assert_true(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = assert_bool(argvars, true);
-}
-
-/*
- * "atan2()" function
- */
-static void f_atan2(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- float_T fx;
- float_T fy;
-
- rettv->v_type = VAR_FLOAT;
- if (tv_get_float_chk(argvars, &fx) && tv_get_float_chk(&argvars[1], &fy)) {
- rettv->vval.v_float = atan2(fx, fy);
- } else {
- rettv->vval.v_float = 0.0;
- }
-}
-
-/*
- * "browse(save, title, initdir, default)" function
- */
-static void f_browse(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_string = NULL;
- rettv->v_type = VAR_STRING;
-}
-
-/*
- * "browsedir(title, initdir)" function
- */
-static void f_browsedir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- f_browse(argvars, rettv, NULL);
-}
-
-
-/*
- * Find a buffer by number or exact name.
- */
-static buf_T *find_buffer(typval_T *avar)
-{
- buf_T *buf = NULL;
-
- if (avar->v_type == VAR_NUMBER)
- buf = buflist_findnr((int)avar->vval.v_number);
- else if (avar->v_type == VAR_STRING && avar->vval.v_string != NULL) {
- buf = buflist_findname_exp(avar->vval.v_string);
- if (buf == NULL) {
- /* No full path name match, try a match with a URL or a "nofile"
- * buffer, these don't use the full path. */
- FOR_ALL_BUFFERS(bp) {
- if (bp->b_fname != NULL
- && (path_with_url((char *)bp->b_fname)
- || bt_nofile(bp)
- )
- && STRCMP(bp->b_fname, avar->vval.v_string) == 0) {
- buf = bp;
- break;
- }
- }
- }
- }
- return buf;
-}
-
-// "bufadd(expr)" function
-static void f_bufadd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char_u *name = (char_u *)tv_get_string(&argvars[0]);
-
- rettv->vval.v_number = buflist_add(*name == NUL ? NULL : name, 0);
-}
-
-/*
- * "bufexists(expr)" function
- */
-static void f_bufexists(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = (find_buffer(&argvars[0]) != NULL);
-}
-
-/*
- * "buflisted(expr)" function
- */
-static void f_buflisted(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- buf_T *buf;
-
- buf = find_buffer(&argvars[0]);
- rettv->vval.v_number = (buf != NULL && buf->b_p_bl);
-}
-
-// "bufload(expr)" function
-static void f_bufload(typval_T *argvars, typval_T *unused, FunPtr fptr)
-{
- buf_T *buf = get_buf_arg(&argvars[0]);
-
- if (buf != NULL && buf->b_ml.ml_mfp == NULL) {
- aco_save_T aco;
-
- aucmd_prepbuf(&aco, buf);
- swap_exists_action = SEA_NONE;
- open_buffer(false, NULL, 0);
- aucmd_restbuf(&aco);
- }
-}
-
-/*
- * "bufloaded(expr)" function
- */
-static void f_bufloaded(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- buf_T *buf;
-
- buf = find_buffer(&argvars[0]);
- rettv->vval.v_number = (buf != NULL && buf->b_ml.ml_mfp != NULL);
-}
-
-
-/*
- * Get buffer by number or pattern.
- */
-static buf_T *tv_get_buf(typval_T *tv, int curtab_only)
-{
- char_u *name = tv->vval.v_string;
- int save_magic;
- char_u *save_cpo;
- buf_T *buf;
-
- if (tv->v_type == VAR_NUMBER)
- return buflist_findnr((int)tv->vval.v_number);
- if (tv->v_type != VAR_STRING)
- return NULL;
- if (name == NULL || *name == NUL)
- return curbuf;
- if (name[0] == '$' && name[1] == NUL)
- return lastbuf;
-
- // Ignore 'magic' and 'cpoptions' here to make scripts portable
- save_magic = p_magic;
- p_magic = TRUE;
- save_cpo = p_cpo;
- p_cpo = (char_u *)"";
-
- buf = buflist_findnr(buflist_findpat(name, name + STRLEN(name),
- TRUE, FALSE, curtab_only));
-
- p_magic = save_magic;
- p_cpo = save_cpo;
-
- // If not found, try expanding the name, like done for bufexists().
- if (buf == NULL) {
- buf = find_buffer(tv);
- }
-
- return buf;
-}
-
-/// Get the buffer from "arg" and give an error and return NULL if it is not
-/// valid.
-static buf_T * get_buf_arg(typval_T *arg)
-{
- buf_T *buf;
-
- emsg_off++;
- buf = tv_get_buf(arg, false);
- emsg_off--;
- if (buf == NULL) {
- EMSG2(_("E158: Invalid buffer name: %s"), tv_get_string(arg));
- }
- return buf;
-}
-
-/*
- * "bufname(expr)" function
- */
-static void f_bufname(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const buf_T *buf;
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
- if (argvars[0].v_type == VAR_UNKNOWN) {
- buf = curbuf;
- } else {
- if (!tv_check_str_or_nr(&argvars[0])) {
- return;
- }
- emsg_off++;
- buf = tv_get_buf(&argvars[0], false);
- emsg_off--;
- }
- if (buf != NULL && buf->b_fname != NULL) {
- rettv->vval.v_string = (char_u *)xstrdup((char *)buf->b_fname);
- }
-}
-
-/*
- * "bufnr(expr)" function
- */
-static void f_bufnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const buf_T *buf;
- bool error = false;
-
- rettv->vval.v_number = -1;
-
- if (argvars[0].v_type == VAR_UNKNOWN) {
- buf = curbuf;
- } else {
- if (!tv_check_str_or_nr(&argvars[0])) {
- return;
- }
- emsg_off++;
- buf = tv_get_buf(&argvars[0], false);
- emsg_off--;
- }
-
- // If the buffer isn't found and the second argument is not zero create a
- // new buffer.
- const char *name;
- if (buf == NULL
- && argvars[1].v_type != VAR_UNKNOWN
- && tv_get_number_chk(&argvars[1], &error) != 0
- && !error
- && (name = tv_get_string_chk(&argvars[0])) != NULL) {
- buf = buflist_new((char_u *)name, NULL, 1, 0);
- }
-
- if (buf != NULL) {
- rettv->vval.v_number = buf->b_fnum;
- }
-}
-
-static void buf_win_common(typval_T *argvars, typval_T *rettv, bool get_nr)
-{
- if (!tv_check_str_or_nr(&argvars[0])) {
- rettv->vval.v_number = -1;
- return;
- }
-
- emsg_off++;
- buf_T *buf = tv_get_buf(&argvars[0], true);
- if (buf == NULL) { // no need to search if buffer was not found
- rettv->vval.v_number = -1;
- goto end;
- }
-
- int winnr = 0;
- int winid;
- bool found_buf = false;
- FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- winnr++;
- if (wp->w_buffer == buf) {
- found_buf = true;
- winid = wp->handle;
- break;
- }
- }
- rettv->vval.v_number = (found_buf ? (get_nr ? winnr : winid) : -1);
-end:
- emsg_off--;
-}
-
-/// "bufwinid(nr)" function
-static void f_bufwinid(typval_T *argvars, typval_T *rettv, FunPtr fptr) {
- buf_win_common(argvars, rettv, false);
-}
-
-/// "bufwinnr(nr)" function
-static void f_bufwinnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- buf_win_common(argvars, rettv, true);
-}
-
-/*
- * "byte2line(byte)" function
- */
-static void f_byte2line(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- long boff = tv_get_number(&argvars[0]) - 1;
- if (boff < 0) {
- rettv->vval.v_number = -1;
- } else {
- rettv->vval.v_number = (varnumber_T)ml_find_line_or_offset(curbuf, 0,
- &boff, false);
- }
-}
-
-static void byteidx(typval_T *argvars, typval_T *rettv, int comp)
-{
- const char *const str = tv_get_string_chk(&argvars[0]);
- varnumber_T idx = tv_get_number_chk(&argvars[1], NULL);
- rettv->vval.v_number = -1;
- if (str == NULL || idx < 0) {
- return;
- }
-
- const char *t = str;
- for (; idx > 0; idx--) {
- if (*t == NUL) { // EOL reached.
- return;
- }
- if (enc_utf8 && comp) {
- t += utf_ptr2len((const char_u *)t);
- } else {
- t += (*mb_ptr2len)((const char_u *)t);
- }
- }
- rettv->vval.v_number = (varnumber_T)(t - str);
-}
-
-/*
- * "byteidx()" function
- */
-static void f_byteidx(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- byteidx(argvars, rettv, FALSE);
-}
-
-/*
- * "byteidxcomp()" function
- */
-static void f_byteidxcomp(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- byteidx(argvars, rettv, TRUE);
-}
-
-int func_call(char_u *name, typval_T *args, partial_T *partial,
- dict_T *selfdict, typval_T *rettv)
-{
- typval_T argv[MAX_FUNC_ARGS + 1];
- int argc = 0;
- int dummy;
- int r = 0;
-
- TV_LIST_ITER(args->vval.v_list, item, {
- if (argc == MAX_FUNC_ARGS - (partial == NULL ? 0 : partial->pt_argc)) {
- EMSG(_("E699: Too many arguments"));
- goto func_call_skip_call;
- }
- // Make a copy of each argument. This is needed to be able to set
- // v_lock to VAR_FIXED in the copy without changing the original list.
- tv_copy(TV_LIST_ITEM_TV(item), &argv[argc++]);
- });
-
- r = call_func(name, (int)STRLEN(name), rettv, argc, argv, NULL,
- curwin->w_cursor.lnum, curwin->w_cursor.lnum,
- &dummy, true, partial, selfdict);
-
-func_call_skip_call:
- // Free the arguments.
- while (argc > 0) {
- tv_clear(&argv[--argc]);
- }
-
- return r;
-}
-
-/// "call(func, arglist [, dict])" function
-static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (argvars[1].v_type != VAR_LIST) {
- EMSG(_(e_listreq));
- return;
- }
- if (argvars[1].vval.v_list == NULL) {
- return;
- }
-
- char_u *func;
- partial_T *partial = NULL;
- dict_T *selfdict = NULL;
- if (argvars[0].v_type == VAR_FUNC) {
- func = argvars[0].vval.v_string;
- } else if (argvars[0].v_type == VAR_PARTIAL) {
- partial = argvars[0].vval.v_partial;
- func = partial_name(partial);
- } else {
- func = (char_u *)tv_get_string(&argvars[0]);
- }
- if (*func == NUL) {
- return; // type error or empty name
- }
-
- if (argvars[2].v_type != VAR_UNKNOWN) {
- if (argvars[2].v_type != VAR_DICT) {
- EMSG(_(e_dictreq));
- return;
- }
- selfdict = argvars[2].vval.v_dict;
- }
-
- func_call(func, &argvars[1], partial, selfdict, rettv);
-}
-
-/*
- * "changenr()" function
- */
-static void f_changenr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = curbuf->b_u_seq_cur;
-}
-
-// "chanclose(id[, stream])" function
-static void f_chanclose(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = 0;
-
- if (check_restricted() || check_secure()) {
- return;
- }
-
- if (argvars[0].v_type != VAR_NUMBER || (argvars[1].v_type != VAR_STRING
- && argvars[1].v_type != VAR_UNKNOWN)) {
- EMSG(_(e_invarg));
- return;
- }
-
- ChannelPart part = kChannelPartAll;
- if (argvars[1].v_type == VAR_STRING) {
- char *stream = (char *)argvars[1].vval.v_string;
- if (!strcmp(stream, "stdin")) {
- part = kChannelPartStdin;
- } else if (!strcmp(stream, "stdout")) {
- part = kChannelPartStdout;
- } else if (!strcmp(stream, "stderr")) {
- part = kChannelPartStderr;
- } else if (!strcmp(stream, "rpc")) {
- part = kChannelPartRpc;
- } else {
- EMSG2(_("Invalid channel stream \"%s\""), stream);
- return;
- }
- }
- const char *error;
- rettv->vval.v_number = channel_close(argvars[0].vval.v_number, part, &error);
- if (!rettv->vval.v_number) {
- EMSG(error);
- }
-}
-
-// "chansend(id, data)" function
-static void f_chansend(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = 0;
-
- if (check_restricted() || check_secure()) {
- return;
- }
-
- if (argvars[0].v_type != VAR_NUMBER || argvars[1].v_type == VAR_UNKNOWN) {
- // First argument is the channel id and second is the data to write
- EMSG(_(e_invarg));
- return;
- }
-
- ptrdiff_t input_len = 0;
- char *input = save_tv_as_string(&argvars[1], &input_len, false);
- if (!input) {
- // Either the error has been handled by save_tv_as_string(),
- // or there is no input to send.
- return;
- }
- uint64_t id = argvars[0].vval.v_number;
- const char *error = NULL;
- rettv->vval.v_number = channel_send(id, input, input_len, &error);
- if (error) {
- EMSG(error);
- }
-}
-
-/*
- * "char2nr(string)" function
- */
-static void f_char2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (argvars[1].v_type != VAR_UNKNOWN) {
- if (!tv_check_num(&argvars[1])) {
- return;
- }
- }
-
- rettv->vval.v_number = utf_ptr2char(
- (const char_u *)tv_get_string(&argvars[0]));
-}
-
-/*
- * "cindent(lnum)" function
- */
-static void f_cindent(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- pos_T pos;
- linenr_T lnum;
-
- pos = curwin->w_cursor;
- lnum = tv_get_lnum(argvars);
- if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) {
- curwin->w_cursor.lnum = lnum;
- rettv->vval.v_number = get_c_indent();
- curwin->w_cursor = pos;
- } else
- rettv->vval.v_number = -1;
-}
-
-/*
- * "clearmatches()" function
- */
-static void f_clearmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- clear_matches(curwin);
-}
-
-/*
- * "col(string)" function
- */
-static void f_col(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- colnr_T col = 0;
- pos_T *fp;
- int fnum = curbuf->b_fnum;
-
- fp = var2fpos(&argvars[0], FALSE, &fnum);
- if (fp != NULL && fnum == curbuf->b_fnum) {
- if (fp->col == MAXCOL) {
- /* '> can be MAXCOL, get the length of the line then */
- if (fp->lnum <= curbuf->b_ml.ml_line_count)
- col = (colnr_T)STRLEN(ml_get(fp->lnum)) + 1;
- else
- col = MAXCOL;
- } else {
- col = fp->col + 1;
- /* col(".") when the cursor is on the NUL at the end of the line
- * because of "coladd" can be seen as an extra column. */
- if (virtual_active() && fp == &curwin->w_cursor) {
- char_u *p = get_cursor_pos_ptr();
-
- if (curwin->w_cursor.coladd >= (colnr_T)chartabsize(p,
- curwin->w_virtcol - curwin->w_cursor.coladd)) {
- int l;
-
- if (*p != NUL && p[(l = (*mb_ptr2len)(p))] == NUL)
- col += l;
- }
- }
- }
- }
- rettv->vval.v_number = col;
-}
-
-/*
- * "complete()" function
- */
-static void f_complete(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if ((State & INSERT) == 0) {
- EMSG(_("E785: complete() can only be used in Insert mode"));
- return;
- }
-
- /* Check for undo allowed here, because if something was already inserted
- * the line was already saved for undo and this check isn't done. */
- if (!undo_allowed())
- return;
-
- if (argvars[1].v_type != VAR_LIST) {
- EMSG(_(e_invarg));
- return;
- }
-
- const colnr_T startcol = tv_get_number_chk(&argvars[0], NULL);
- if (startcol <= 0) {
- return;
- }
-
- set_completion(startcol - 1, argvars[1].vval.v_list);
-}
-
-/*
- * "complete_add()" function
- */
-static void f_complete_add(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = ins_compl_add_tv(&argvars[0], 0);
-}
-
-/*
- * "complete_check()" function
- */
-static void f_complete_check(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int saved = RedrawingDisabled;
-
- RedrawingDisabled = 0;
- ins_compl_check_keys(0, true);
- rettv->vval.v_number = compl_interrupted;
- RedrawingDisabled = saved;
-}
-
-// "complete_info()" function
-static void f_complete_info(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- tv_dict_alloc_ret(rettv);
-
- list_T *what_list = NULL;
-
- if (argvars[0].v_type != VAR_UNKNOWN) {
- if (argvars[0].v_type != VAR_LIST) {
- EMSG(_(e_listreq));
- return;
- }
- what_list = argvars[0].vval.v_list;
- }
- get_complete_info(what_list, rettv->vval.v_dict);
-}
-
-/*
- * "confirm(message, buttons[, default [, type]])" function
- */
-static void f_confirm(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char buf[NUMBUFLEN];
- char buf2[NUMBUFLEN];
- const char *message;
- const char *buttons = NULL;
- int def = 1;
- int type = VIM_GENERIC;
- const char *typestr;
- bool error = false;
-
- message = tv_get_string_chk(&argvars[0]);
- if (message == NULL) {
- error = true;
- }
- if (argvars[1].v_type != VAR_UNKNOWN) {
- buttons = tv_get_string_buf_chk(&argvars[1], buf);
- if (buttons == NULL) {
- error = true;
- }
- if (argvars[2].v_type != VAR_UNKNOWN) {
- def = tv_get_number_chk(&argvars[2], &error);
- if (argvars[3].v_type != VAR_UNKNOWN) {
- typestr = tv_get_string_buf_chk(&argvars[3], buf2);
- if (typestr == NULL) {
- error = true;
- } else {
- switch (TOUPPER_ASC(*typestr)) {
- case 'E': type = VIM_ERROR; break;
- case 'Q': type = VIM_QUESTION; break;
- case 'I': type = VIM_INFO; break;
- case 'W': type = VIM_WARNING; break;
- case 'G': type = VIM_GENERIC; break;
- }
- }
- }
- }
- }
-
- if (buttons == NULL || *buttons == NUL) {
- buttons = _("&Ok");
- }
-
- if (!error) {
- rettv->vval.v_number = do_dialog(
- type, NULL, (char_u *)message, (char_u *)buttons, def, NULL, false);
- }
-}
-
-/*
- * "copy()" function
- */
-static void f_copy(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- var_item_copy(NULL, &argvars[0], rettv, false, 0);
-}
-
-/*
- * "count()" function
- */
-static void f_count(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- long n = 0;
- int ic = 0;
- bool error = false;
-
- if (argvars[2].v_type != VAR_UNKNOWN) {
- ic = tv_get_number_chk(&argvars[2], &error);
- }
-
- if (argvars[0].v_type == VAR_STRING) {
- const char_u *expr = (char_u *)tv_get_string_chk(&argvars[1]);
- const char_u *p = argvars[0].vval.v_string;
-
- if (!error && expr != NULL && *expr != NUL && p != NULL) {
- if (ic) {
- const size_t len = STRLEN(expr);
-
- while (*p != NUL) {
- if (mb_strnicmp(p, expr, len) == 0) {
- n++;
- p += len;
- } else {
- MB_PTR_ADV(p);
- }
- }
- } else {
- char_u *next;
- while ((next = (char_u *)strstr((char *)p, (char *)expr)) != NULL) {
- n++;
- p = next + STRLEN(expr);
- }
- }
- }
- } else if (argvars[0].v_type == VAR_LIST) {
- listitem_T *li;
- list_T *l;
- long idx;
-
- if ((l = argvars[0].vval.v_list) != NULL) {
- li = tv_list_first(l);
- if (argvars[2].v_type != VAR_UNKNOWN) {
- if (argvars[3].v_type != VAR_UNKNOWN) {
- idx = tv_get_number_chk(&argvars[3], &error);
- if (!error) {
- li = tv_list_find(l, idx);
- if (li == NULL) {
- EMSGN(_(e_listidx), idx);
- }
- }
- }
- if (error)
- li = NULL;
- }
-
- for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) {
- if (tv_equal(TV_LIST_ITEM_TV(li), &argvars[1], ic, false)) {
- n++;
- }
- }
- }
- } else if (argvars[0].v_type == VAR_DICT) {
- int todo;
- dict_T *d;
- hashitem_T *hi;
-
- if ((d = argvars[0].vval.v_dict) != NULL) {
- if (argvars[2].v_type != VAR_UNKNOWN) {
- if (argvars[3].v_type != VAR_UNKNOWN) {
- EMSG(_(e_invarg));
- }
- }
-
- todo = error ? 0 : (int)d->dv_hashtab.ht_used;
- for (hi = d->dv_hashtab.ht_array; todo > 0; ++hi) {
- if (!HASHITEM_EMPTY(hi)) {
- todo--;
- if (tv_equal(&TV_DICT_HI2DI(hi)->di_tv, &argvars[1], ic, false)) {
- n++;
- }
- }
- }
- }
- } else {
- EMSG2(_(e_listdictarg), "count()");
- }
- rettv->vval.v_number = n;
-}
-
-/*
- * "cscope_connection([{num} , {dbpath} [, {prepend}]])" function
- *
- * Checks the existence of a cscope connection.
- */
-static void f_cscope_connection(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int num = 0;
- const char *dbpath = NULL;
- const char *prepend = NULL;
- char buf[NUMBUFLEN];
-
- if (argvars[0].v_type != VAR_UNKNOWN
- && argvars[1].v_type != VAR_UNKNOWN) {
- num = (int)tv_get_number(&argvars[0]);
- dbpath = tv_get_string(&argvars[1]);
- if (argvars[2].v_type != VAR_UNKNOWN) {
- prepend = tv_get_string_buf(&argvars[2], buf);
- }
- }
-
- rettv->vval.v_number = cs_connection(num, (char_u *)dbpath,
- (char_u *)prepend);
-}
-
-/// "ctxget([{index}])" function
-static void f_ctxget(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- size_t index = 0;
- if (argvars[0].v_type == VAR_NUMBER) {
- index = argvars[0].vval.v_number;
- } else if (argvars[0].v_type != VAR_UNKNOWN) {
- EMSG2(_(e_invarg2), "expected nothing or a Number as an argument");
- return;
- }
-
- Context *ctx = ctx_get(index);
- if (ctx == NULL) {
- EMSG3(_(e_invargNval), "index", "out of bounds");
- return;
- }
-
- Dictionary ctx_dict = ctx_to_dict(ctx);
- Error err = ERROR_INIT;
- object_to_vim(DICTIONARY_OBJ(ctx_dict), rettv, &err);
- api_free_dictionary(ctx_dict);
- api_clear_error(&err);
-}
-
-/// "ctxpop()" function
-static void f_ctxpop(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (!ctx_restore(NULL, kCtxAll)) {
- EMSG(_("Context stack is empty"));
- }
-}
-
-/// "ctxpush([{types}])" function
-static void f_ctxpush(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int types = kCtxAll;
- if (argvars[0].v_type == VAR_LIST) {
- types = 0;
- TV_LIST_ITER(argvars[0].vval.v_list, li, {
- typval_T *tv_li = TV_LIST_ITEM_TV(li);
- if (tv_li->v_type == VAR_STRING) {
- if (strequal((char *)tv_li->vval.v_string, "regs")) {
- types |= kCtxRegs;
- } else if (strequal((char *)tv_li->vval.v_string, "jumps")) {
- types |= kCtxJumps;
- } else if (strequal((char *)tv_li->vval.v_string, "bufs")) {
- types |= kCtxBufs;
- } else if (strequal((char *)tv_li->vval.v_string, "gvars")) {
- types |= kCtxGVars;
- } else if (strequal((char *)tv_li->vval.v_string, "sfuncs")) {
- types |= kCtxSFuncs;
- } else if (strequal((char *)tv_li->vval.v_string, "funcs")) {
- types |= kCtxFuncs;
- }
- }
- });
- } else if (argvars[0].v_type != VAR_UNKNOWN) {
- EMSG2(_(e_invarg2), "expected nothing or a List as an argument");
- return;
- }
- ctx_save(NULL, types);
-}
-
-/// "ctxset({context}[, {index}])" function
-static void f_ctxset(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (argvars[0].v_type != VAR_DICT) {
- EMSG2(_(e_invarg2), "expected dictionary as first argument");
- return;
- }
-
- size_t index = 0;
- if (argvars[1].v_type == VAR_NUMBER) {
- index = argvars[1].vval.v_number;
- } else if (argvars[1].v_type != VAR_UNKNOWN) {
- EMSG2(_(e_invarg2), "expected nothing or a Number as second argument");
- return;
- }
-
- Context *ctx = ctx_get(index);
- if (ctx == NULL) {
- EMSG3(_(e_invargNval), "index", "out of bounds");
- return;
- }
-
- int save_did_emsg = did_emsg;
- did_emsg = false;
-
- Dictionary dict = vim_to_object(&argvars[0]).data.dictionary;
- Context tmp = CONTEXT_INIT;
- ctx_from_dict(dict, &tmp);
-
- if (did_emsg) {
- ctx_free(&tmp);
- } else {
- ctx_free(ctx);
- *ctx = tmp;
- }
-
- api_free_dictionary(dict);
- did_emsg = save_did_emsg;
-}
-
-/// "ctxsize()" function
-static void f_ctxsize(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = ctx_size();
-}
-
-/// "cursor(lnum, col)" function, or
-/// "cursor(list)"
-///
-/// Moves the cursor to the specified line and column.
-///
-/// @returns 0 when the position could be set, -1 otherwise.
-static void f_cursor(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- long line, col;
- long coladd = 0;
- bool set_curswant = true;
-
- rettv->vval.v_number = -1;
- if (argvars[1].v_type == VAR_UNKNOWN) {
- pos_T pos;
- colnr_T curswant = -1;
-
- if (list2fpos(argvars, &pos, NULL, &curswant) == FAIL) {
- EMSG(_(e_invarg));
- return;
- }
-
- line = pos.lnum;
- col = pos.col;
- coladd = pos.coladd;
- if (curswant >= 0) {
- curwin->w_curswant = curswant - 1;
- set_curswant = false;
- }
- } else {
- line = tv_get_lnum(argvars);
- col = (long)tv_get_number_chk(&argvars[1], NULL);
- if (argvars[2].v_type != VAR_UNKNOWN) {
- coladd = (long)tv_get_number_chk(&argvars[2], NULL);
- }
- }
- if (line < 0 || col < 0
- || coladd < 0) {
- return; // type error; errmsg already given
- }
- if (line > 0) {
- curwin->w_cursor.lnum = line;
- }
- if (col > 0) {
- curwin->w_cursor.col = col - 1;
- }
- curwin->w_cursor.coladd = coladd;
-
- // Make sure the cursor is in a valid position.
- check_cursor();
- // Correct cursor for multi-byte character.
- if (has_mbyte) {
- mb_adjust_cursor();
- }
-
- curwin->w_set_curswant = set_curswant;
- rettv->vval.v_number = 0;
-}
-
-/*
- * "deepcopy()" function
- */
-static void f_deepcopy(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int noref = 0;
-
- if (argvars[1].v_type != VAR_UNKNOWN) {
- noref = tv_get_number_chk(&argvars[1], NULL);
- }
- if (noref < 0 || noref > 1) {
- EMSG(_(e_invarg));
- } else {
- var_item_copy(NULL, &argvars[0], rettv, true, (noref == 0
- ? get_copyID()
- : 0));
- }
-}
-
-// "delete()" function
-static void f_delete(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = -1;
- if (check_restricted() || check_secure()) {
- return;
- }
-
- const char *const name = tv_get_string(&argvars[0]);
- if (*name == NUL) {
- EMSG(_(e_invarg));
- return;
- }
-
- char nbuf[NUMBUFLEN];
- const char *flags;
- if (argvars[1].v_type != VAR_UNKNOWN) {
- flags = tv_get_string_buf(&argvars[1], nbuf);
- } else {
- flags = "";
- }
-
- if (*flags == NUL) {
- // delete a file
- rettv->vval.v_number = os_remove(name) == 0 ? 0 : -1;
- } else if (strcmp(flags, "d") == 0) {
- // delete an empty directory
- rettv->vval.v_number = os_rmdir(name) == 0 ? 0 : -1;
- } else if (strcmp(flags, "rf") == 0) {
- // delete a directory recursively
- rettv->vval.v_number = delete_recursive(name);
- } else {
- emsgf(_(e_invexpr2), flags);
- }
-}
-
-// dictwatcheradd(dict, key, funcref) function
-static void f_dictwatcheradd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (check_restricted() || check_secure()) {
- return;
- }
-
- if (argvars[0].v_type != VAR_DICT) {
- emsgf(_(e_invarg2), "dict");
- return;
- } else if (argvars[0].vval.v_dict == NULL) {
- const char *const arg_errmsg = _("dictwatcheradd() argument");
- const size_t arg_errmsg_len = strlen(arg_errmsg);
- emsgf(_(e_readonlyvar), (int)arg_errmsg_len, arg_errmsg);
- return;
- }
-
- if (argvars[1].v_type != VAR_STRING && argvars[1].v_type != VAR_NUMBER) {
- emsgf(_(e_invarg2), "key");
- return;
- }
-
- const char *const key_pattern = tv_get_string_chk(argvars + 1);
- if (key_pattern == NULL) {
- return;
- }
- const size_t key_pattern_len = strlen(key_pattern);
-
- Callback callback;
- if (!callback_from_typval(&callback, &argvars[2])) {
- emsgf(_(e_invarg2), "funcref");
- return;
- }
-
- tv_dict_watcher_add(argvars[0].vval.v_dict, key_pattern, key_pattern_len,
- callback);
-}
-
-// dictwatcherdel(dict, key, funcref) function
-static void f_dictwatcherdel(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (check_restricted() || check_secure()) {
- return;
- }
-
- if (argvars[0].v_type != VAR_DICT) {
- emsgf(_(e_invarg2), "dict");
- return;
- }
-
- if (argvars[2].v_type != VAR_FUNC && argvars[2].v_type != VAR_STRING) {
- emsgf(_(e_invarg2), "funcref");
- return;
- }
-
- const char *const key_pattern = tv_get_string_chk(argvars + 1);
- if (key_pattern == NULL) {
- return;
- }
-
- Callback callback;
- if (!callback_from_typval(&callback, &argvars[2])) {
- return;
- }
-
- if (!tv_dict_watcher_remove(argvars[0].vval.v_dict, key_pattern,
- strlen(key_pattern), callback)) {
- EMSG("Couldn't find a watcher matching key and callback");
- }
-
- callback_free(&callback);
-}
-
-/// "deletebufline()" function
-static void f_deletebufline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- linenr_T last;
- buf_T *curbuf_save = NULL;
- win_T *curwin_save = NULL;
-
- buf_T *const buf = tv_get_buf(&argvars[0], false);
- if (buf == NULL) {
- rettv->vval.v_number = 1; // FAIL
- return;
- }
- const bool is_curbuf = buf == curbuf;
-
- const linenr_T first = tv_get_lnum_buf(&argvars[1], buf);
- if (argvars[2].v_type != VAR_UNKNOWN) {
- last = tv_get_lnum_buf(&argvars[2], buf);
- } else {
- last = first;
- }
-
- if (buf->b_ml.ml_mfp == NULL || first < 1
- || first > buf->b_ml.ml_line_count || last < first) {
- rettv->vval.v_number = 1; // FAIL
- return;
- }
-
- if (!is_curbuf) {
- curbuf_save = curbuf;
- curwin_save = curwin;
- curbuf = buf;
- find_win_for_curbuf();
- }
- if (last > curbuf->b_ml.ml_line_count) {
- last = curbuf->b_ml.ml_line_count;
- }
- const long count = last - first + 1;
-
- // When coming here from Insert mode, sync undo, so that this can be
- // undone separately from what was previously inserted.
- if (u_sync_once == 2) {
- u_sync_once = 1; // notify that u_sync() was called
- u_sync(true);
- }
-
- if (u_save(first - 1, last + 1) == FAIL) {
- rettv->vval.v_number = 1; // FAIL
- return;
- }
-
- for (linenr_T lnum = first; lnum <= last; lnum++) {
- ml_delete(first, true);
- }
-
- FOR_ALL_TAB_WINDOWS(tp, wp) {
- if (wp->w_buffer == buf) {
- if (wp->w_cursor.lnum > last) {
- wp->w_cursor.lnum -= count;
- } else if (wp->w_cursor.lnum> first) {
- wp->w_cursor.lnum = first;
- }
- if (wp->w_cursor.lnum > wp->w_buffer->b_ml.ml_line_count) {
- wp->w_cursor.lnum = wp->w_buffer->b_ml.ml_line_count;
- }
- }
- }
- check_cursor_col();
- deleted_lines_mark(first, count);
-
- if (!is_curbuf) {
- curbuf = curbuf_save;
- curwin = curwin_save;
- }
-}
-
-/*
- * "did_filetype()" function
- */
-static void f_did_filetype(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = did_filetype;
-}
-
-/*
- * "diff_filler()" function
- */
-static void f_diff_filler(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = diff_check_fill(curwin, tv_get_lnum(argvars));
-}
-
-/*
- * "diff_hlID()" function
- */
-static void f_diff_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- linenr_T lnum = tv_get_lnum(argvars);
- static linenr_T prev_lnum = 0;
- static int changedtick = 0;
- static int fnum = 0;
- static int change_start = 0;
- static int change_end = 0;
- static hlf_T hlID = (hlf_T)0;
- int filler_lines;
- int col;
-
- if (lnum < 0) /* ignore type error in {lnum} arg */
- lnum = 0;
- if (lnum != prev_lnum
- || changedtick != buf_get_changedtick(curbuf)
- || fnum != curbuf->b_fnum) {
- /* New line, buffer, change: need to get the values. */
- filler_lines = diff_check(curwin, lnum);
- if (filler_lines < 0) {
- if (filler_lines == -1) {
- change_start = MAXCOL;
- change_end = -1;
- if (diff_find_change(curwin, lnum, &change_start, &change_end))
- hlID = HLF_ADD; /* added line */
- else
- hlID = HLF_CHD; /* changed line */
- } else
- hlID = HLF_ADD; /* added line */
- } else
- hlID = (hlf_T)0;
- prev_lnum = lnum;
- changedtick = buf_get_changedtick(curbuf);
- fnum = curbuf->b_fnum;
- }
-
- if (hlID == HLF_CHD || hlID == HLF_TXD) {
- col = tv_get_number(&argvars[1]) - 1; // Ignore type error in {col}.
- if (col >= change_start && col <= change_end) {
- hlID = HLF_TXD; // Changed text.
- } else {
- hlID = HLF_CHD; // Changed line.
- }
- }
- rettv->vval.v_number = hlID == (hlf_T)0 ? 0 : (int)(hlID + 1);
-}
-
-/*
- * "empty({expr})" function
- */
-static void f_empty(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- bool n = true;
-
- switch (argvars[0].v_type) {
- case VAR_STRING:
- case VAR_FUNC: {
- n = argvars[0].vval.v_string == NULL
- || *argvars[0].vval.v_string == NUL;
- break;
- }
- case VAR_PARTIAL: {
- n = false;
- break;
- }
- case VAR_NUMBER: {
- n = argvars[0].vval.v_number == 0;
- break;
- }
- case VAR_FLOAT: {
- n = argvars[0].vval.v_float == 0.0;
- break;
- }
- case VAR_LIST: {
- n = (tv_list_len(argvars[0].vval.v_list) == 0);
- break;
- }
- case VAR_DICT: {
- n = (tv_dict_len(argvars[0].vval.v_dict) == 0);
- break;
- }
- case VAR_SPECIAL: {
- // Using switch to get warning if SpecialVarValue receives more values.
- switch (argvars[0].vval.v_special) {
- case kSpecialVarTrue: {
- n = false;
- break;
- }
- case kSpecialVarFalse:
- case kSpecialVarNull: {
- n = true;
- break;
- }
- }
- break;
- }
- case VAR_UNKNOWN: {
- internal_error("f_empty(UNKNOWN)");
- break;
- }
- }
-
- rettv->vval.v_number = n;
-}
-
-/// "environ()" function
-static void f_environ(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- tv_dict_alloc_ret(rettv);
-
- size_t env_size = os_get_fullenv_size();
- char **env = xmalloc(sizeof(*env) * (env_size + 1));
- env[env_size] = NULL;
-
- os_copy_fullenv(env, env_size);
-
- for (size_t i = 0; i < env_size; i++) {
- const char * str = env[i];
- const char * const end = strchr(str + (str[0] == '=' ? 1 : 0),
- '=');
- assert(end != NULL);
- ptrdiff_t len = end - str;
- assert(len > 0);
- const char * value = str + len + 1;
- tv_dict_add_str(rettv->vval.v_dict,
- str, len,
- value);
- }
- os_free_fullenv(env);
-}
-
-/*
- * "escape({string}, {chars})" function
- */
-static void f_escape(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char buf[NUMBUFLEN];
-
- rettv->vval.v_string = vim_strsave_escaped(
- (const char_u *)tv_get_string(&argvars[0]),
- (const char_u *)tv_get_string_buf(&argvars[1], buf));
- rettv->v_type = VAR_STRING;
-}
-
-/// "getenv()" function
-static void f_getenv(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char_u *p = (char_u *)vim_getenv(tv_get_string(&argvars[0]));
-
- if (p == NULL) {
- rettv->v_type = VAR_SPECIAL;
- rettv->vval.v_special = kSpecialVarNull;
- return;
- }
- rettv->vval.v_string = p;
- rettv->v_type = VAR_STRING;
-}
-
-/*
- * "eval()" function
- */
-static void f_eval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *s = tv_get_string_chk(&argvars[0]);
- if (s != NULL) {
- s = (const char *)skipwhite((const char_u *)s);
- }
-
- const char *const expr_start = s;
- if (s == NULL || eval1((char_u **)&s, rettv, true) == FAIL) {
- if (expr_start != NULL && !aborting()) {
- EMSG2(_(e_invexpr2), expr_start);
- }
- need_clr_eos = FALSE;
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = 0;
- } else if (*s != NUL) {
- EMSG(_(e_trailing));
- }
-}
-
-/*
- * "eventhandler()" function
- */
-static void f_eventhandler(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = vgetc_busy;
-}
-
-/*
- * "executable()" function
- */
-static void f_executable(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *name = tv_get_string(&argvars[0]);
-
- // Check in $PATH and also check directly if there is a directory name
- rettv->vval.v_number = os_can_exe(name, NULL, true);
-}
-
-typedef struct {
- const list_T *const l;
- const listitem_T *li;
-} GetListLineCookie;
-
-static char_u *get_list_line(int c, void *cookie, int indent, bool do_concat)
-{
- GetListLineCookie *const p = (GetListLineCookie *)cookie;
-
- const listitem_T *const item = p->li;
- if (item == NULL) {
- return NULL;
- }
- char buf[NUMBUFLEN];
- const char *const s = tv_get_string_buf_chk(TV_LIST_ITEM_TV(item), buf);
- p->li = TV_LIST_ITEM_NEXT(p->l, item);
- return (char_u *)(s == NULL ? NULL : xstrdup(s));
-}
-
-// "execute(command)" function
-static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const int save_msg_silent = msg_silent;
- const int save_emsg_silent = emsg_silent;
- const bool save_emsg_noredir = emsg_noredir;
- const bool save_redir_off = redir_off;
- garray_T *const save_capture_ga = capture_ga;
- const int save_msg_col = msg_col;
- bool echo_output = false;
-
- if (check_secure()) {
- return;
- }
-
- if (argvars[1].v_type != VAR_UNKNOWN) {
- char buf[NUMBUFLEN];
- const char *const s = tv_get_string_buf_chk(&argvars[1], buf);
-
- if (s == NULL) {
- return;
- }
- if (*s == NUL) {
- echo_output = true;
- }
- if (strncmp(s, "silent", 6) == 0) {
- msg_silent++;
- }
- if (strcmp(s, "silent!") == 0) {
- emsg_silent = true;
- emsg_noredir = true;
- }
- } else {
- msg_silent++;
- }
-
- garray_T capture_local;
- ga_init(&capture_local, (int)sizeof(char), 80);
- capture_ga = &capture_local;
- redir_off = false;
- if (!echo_output) {
- msg_col = 0; // prevent leading spaces
- }
-
- if (argvars[0].v_type != VAR_LIST) {
- do_cmdline_cmd(tv_get_string(&argvars[0]));
- } else if (argvars[0].vval.v_list != NULL) {
- list_T *const list = argvars[0].vval.v_list;
- tv_list_ref(list);
- GetListLineCookie cookie = {
- .l = list,
- .li = tv_list_first(list),
- };
- do_cmdline(NULL, get_list_line, (void *)&cookie,
- DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT|DOCMD_KEYTYPED);
- tv_list_unref(list);
- }
- msg_silent = save_msg_silent;
- emsg_silent = save_emsg_silent;
- emsg_noredir = save_emsg_noredir;
- redir_off = save_redir_off;
- // "silent reg" or "silent echo x" leaves msg_col somewhere in the line.
- if (echo_output) {
- // When not working silently: put it in column zero. A following
- // "echon" will overwrite the message, unavoidably.
- msg_col = 0;
- } else {
- // When working silently: Put it back where it was, since nothing
- // should have been written.
- msg_col = save_msg_col;
- }
-
- ga_append(capture_ga, NUL);
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = capture_ga->ga_data;
-
- capture_ga = save_capture_ga;
-}
-
-/// "exepath()" function
-static void f_exepath(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *arg = tv_get_string(&argvars[0]);
- char *path = NULL;
-
- (void)os_can_exe(arg, &path, true);
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = (char_u *)path;
-}
-
/// Find a window: When using a Window ID in any tab page, when using a number
/// in the current tab page.
win_T * find_win_by_nr_or_id(typval_T *vp)
@@ -8928,329 +5868,9 @@ win_T * find_win_by_nr_or_id(typval_T *vp)
}
/*
- * "exists()" function
- */
-static void f_exists(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int n = false;
- int len = 0;
-
- const char *p = tv_get_string(&argvars[0]);
- if (*p == '$') { // Environment variable.
- // First try "normal" environment variables (fast).
- if (os_env_exists(p + 1)) {
- n = true;
- } else {
- // Try expanding things like $VIM and ${HOME}.
- char_u *const exp = expand_env_save((char_u *)p);
- if (exp != NULL && *exp != '$') {
- n = true;
- }
- xfree(exp);
- }
- } else if (*p == '&' || *p == '+') { // Option.
- n = (get_option_tv(&p, NULL, true) == OK);
- if (*skipwhite((const char_u *)p) != NUL) {
- n = false; // Trailing garbage.
- }
- } else if (*p == '*') { // Internal or user defined function.
- n = function_exists(p + 1, false);
- } else if (*p == ':') {
- n = cmd_exists(p + 1);
- } else if (*p == '#') {
- if (p[1] == '#') {
- n = autocmd_supported(p + 2);
- } else {
- n = au_exists(p + 1);
- }
- } else { // Internal variable.
- typval_T tv;
-
- // get_name_len() takes care of expanding curly braces
- const char *name = p;
- char *tofree;
- len = get_name_len((const char **)&p, &tofree, true, false);
- if (len > 0) {
- if (tofree != NULL) {
- name = tofree;
- }
- n = (get_var_tv(name, len, &tv, NULL, false, true) == OK);
- if (n) {
- // Handle d.key, l[idx], f(expr).
- n = (handle_subscript(&p, &tv, true, false) == OK);
- if (n) {
- tv_clear(&tv);
- }
- }
- }
- if (*p != NUL)
- n = FALSE;
-
- xfree(tofree);
- }
-
- rettv->vval.v_number = n;
-}
-
-/*
- * "expand()" function
- */
-static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- size_t len;
- char_u *errormsg;
- int options = WILD_SILENT|WILD_USE_NL|WILD_LIST_NOTFOUND;
- expand_T xpc;
- bool error = false;
- char_u *result;
-
- rettv->v_type = VAR_STRING;
- if (argvars[1].v_type != VAR_UNKNOWN
- && argvars[2].v_type != VAR_UNKNOWN
- && tv_get_number_chk(&argvars[2], &error)
- && !error) {
- tv_list_set_ret(rettv, NULL);
- }
-
- const char *s = tv_get_string(&argvars[0]);
- if (*s == '%' || *s == '#' || *s == '<') {
- emsg_off++;
- result = eval_vars((char_u *)s, (char_u *)s, &len, NULL, &errormsg, NULL);
- emsg_off--;
- if (rettv->v_type == VAR_LIST) {
- tv_list_alloc_ret(rettv, (result != NULL));
- if (result != NULL) {
- tv_list_append_string(rettv->vval.v_list, (const char *)result, -1);
- }
- } else
- rettv->vval.v_string = result;
- } else {
- /* When the optional second argument is non-zero, don't remove matches
- * for 'wildignore' and don't put matches for 'suffixes' at the end. */
- if (argvars[1].v_type != VAR_UNKNOWN
- && tv_get_number_chk(&argvars[1], &error)) {
- options |= WILD_KEEP_ALL;
- }
- if (!error) {
- ExpandInit(&xpc);
- xpc.xp_context = EXPAND_FILES;
- if (p_wic) {
- options += WILD_ICASE;
- }
- if (rettv->v_type == VAR_STRING) {
- rettv->vval.v_string = ExpandOne(&xpc, (char_u *)s, NULL, options,
- WILD_ALL);
- } else {
- ExpandOne(&xpc, (char_u *)s, NULL, options, WILD_ALL_KEEP);
- tv_list_alloc_ret(rettv, xpc.xp_numfiles);
- for (int i = 0; i < xpc.xp_numfiles; i++) {
- tv_list_append_string(rettv->vval.v_list,
- (const char *)xpc.xp_files[i], -1);
- }
- ExpandCleanup(&xpc);
- }
- } else {
- rettv->vval.v_string = NULL;
- }
- }
-}
-
-
-/// "menu_get(path [, modes])" function
-static void f_menu_get(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- tv_list_alloc_ret(rettv, kListLenMayKnow);
- int modes = MENU_ALL_MODES;
- if (argvars[1].v_type == VAR_STRING) {
- const char_u *const strmodes = (char_u *)tv_get_string(&argvars[1]);
- modes = get_menu_cmd_modes(strmodes, false, NULL, NULL);
- }
- menu_get((char_u *)tv_get_string(&argvars[0]), modes, rettv->vval.v_list);
-}
-
-/*
- * "extend(list, list [, idx])" function
- * "extend(dict, dict [, action])" function
- */
-static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *const arg_errmsg = N_("extend() argument");
-
- if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_LIST) {
- long before;
- bool error = false;
-
- list_T *const l1 = argvars[0].vval.v_list;
- list_T *const l2 = argvars[1].vval.v_list;
- if (!tv_check_lock(tv_list_locked(l1), arg_errmsg, TV_TRANSLATE)) {
- listitem_T *item;
- if (argvars[2].v_type != VAR_UNKNOWN) {
- before = (long)tv_get_number_chk(&argvars[2], &error);
- if (error) {
- return; // Type error; errmsg already given.
- }
-
- if (before == tv_list_len(l1)) {
- item = NULL;
- } else {
- item = tv_list_find(l1, before);
- if (item == NULL) {
- EMSGN(_(e_listidx), before);
- return;
- }
- }
- } else {
- item = NULL;
- }
- tv_list_extend(l1, l2, item);
-
- tv_copy(&argvars[0], rettv);
- }
- } else if (argvars[0].v_type == VAR_DICT && argvars[1].v_type ==
- VAR_DICT) {
- dict_T *const d1 = argvars[0].vval.v_dict;
- dict_T *const d2 = argvars[1].vval.v_dict;
- if (d1 == NULL) {
- const bool locked = tv_check_lock(VAR_FIXED, arg_errmsg, TV_TRANSLATE);
- (void)locked;
- assert(locked == true);
- } else if (d2 == NULL) {
- // Do nothing
- tv_copy(&argvars[0], rettv);
- } else if (!tv_check_lock(d1->dv_lock, arg_errmsg, TV_TRANSLATE)) {
- const char *action = "force";
- // Check the third argument.
- if (argvars[2].v_type != VAR_UNKNOWN) {
- const char *const av[] = { "keep", "force", "error" };
-
- action = tv_get_string_chk(&argvars[2]);
- if (action == NULL) {
- return; // Type error; error message already given.
- }
- size_t i;
- for (i = 0; i < ARRAY_SIZE(av); i++) {
- if (strcmp(action, av[i]) == 0) {
- break;
- }
- }
- if (i == 3) {
- EMSG2(_(e_invarg2), action);
- return;
- }
- }
-
- tv_dict_extend(d1, d2, action);
-
- tv_copy(&argvars[0], rettv);
- }
- } else {
- EMSG2(_(e_listdictarg), "extend()");
- }
-}
-
-/*
- * "feedkeys()" function
- */
-static void f_feedkeys(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- // This is not allowed in the sandbox. If the commands would still be
- // executed in the sandbox it would be OK, but it probably happens later,
- // when "sandbox" is no longer set.
- if (check_secure()) {
- return;
- }
-
- const char *const keys = tv_get_string(&argvars[0]);
- char nbuf[NUMBUFLEN];
- const char *flags = NULL;
- if (argvars[1].v_type != VAR_UNKNOWN) {
- flags = tv_get_string_buf(&argvars[1], nbuf);
- }
-
- nvim_feedkeys(cstr_as_string((char *)keys),
- cstr_as_string((char *)flags), true);
-}
-
-/// "filereadable()" function
-static void f_filereadable(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *const p = tv_get_string(&argvars[0]);
- rettv->vval.v_number =
- (*p && !os_isdir((const char_u *)p) && os_file_is_readable(p));
-}
-
-/*
- * Return 0 for not writable, 1 for writable file, 2 for a dir which we have
- * rights to write into.
- */
-static void f_filewritable(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *filename = tv_get_string(&argvars[0]);
- rettv->vval.v_number = os_file_is_writable(filename);
-}
-
-
-static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what)
-{
- char_u *fresult = NULL;
- char_u *path = *curbuf->b_p_path == NUL ? p_path : curbuf->b_p_path;
- int count = 1;
- bool first = true;
- bool error = false;
-
- rettv->vval.v_string = NULL;
- rettv->v_type = VAR_STRING;
-
- const char *fname = tv_get_string(&argvars[0]);
-
- char pathbuf[NUMBUFLEN];
- if (argvars[1].v_type != VAR_UNKNOWN) {
- const char *p = tv_get_string_buf_chk(&argvars[1], pathbuf);
- if (p == NULL) {
- error = true;
- } else {
- if (*p != NUL) {
- path = (char_u *)p;
- }
-
- if (argvars[2].v_type != VAR_UNKNOWN) {
- count = tv_get_number_chk(&argvars[2], &error);
- }
- }
- }
-
- if (count < 0) {
- tv_list_alloc_ret(rettv, kListLenUnknown);
- }
-
- if (*fname != NUL && !error) {
- do {
- if (rettv->v_type == VAR_STRING || rettv->v_type == VAR_LIST)
- xfree(fresult);
- fresult = find_file_in_path_option(first ? (char_u *)fname : NULL,
- first ? strlen(fname) : 0,
- 0, first, path,
- find_what, curbuf->b_ffname,
- (find_what == FINDFILE_DIR
- ? (char_u *)""
- : curbuf->b_p_sua));
- first = false;
-
- if (fresult != NULL && rettv->v_type == VAR_LIST) {
- tv_list_append_string(rettv->vval.v_list, (const char *)fresult, -1);
- }
- } while ((rettv->v_type == VAR_LIST || --count > 0) && fresult != NULL);
- }
-
- if (rettv->v_type == VAR_STRING)
- rettv->vval.v_string = fresult;
-}
-
-
-/*
* Implementation of map() and filter().
*/
-static void filter_map(typval_T *argvars, typval_T *rettv, int map)
+void filter_map(typval_T *argvars, typval_T *rettv, int map)
{
typval_T *expr;
list_T *l = NULL;
@@ -9400,248 +6020,8 @@ theend:
return retval;
}
-/*
- * "filter()" function
- */
-static void f_filter(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- filter_map(argvars, rettv, FALSE);
-}
-
-/*
- * "finddir({fname}[, {path}[, {count}]])" function
- */
-static void f_finddir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- findfilendir(argvars, rettv, FINDFILE_DIR);
-}
-
-/*
- * "findfile({fname}[, {path}[, {count}]])" function
- */
-static void f_findfile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- findfilendir(argvars, rettv, FINDFILE_FILE);
-}
-
-/*
- * "float2nr({float})" function
- */
-static void f_float2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- float_T f;
-
- if (tv_get_float_chk(argvars, &f)) {
- if (f <= -VARNUMBER_MAX + DBL_EPSILON) {
- rettv->vval.v_number = -VARNUMBER_MAX;
- } else if (f >= VARNUMBER_MAX - DBL_EPSILON) {
- rettv->vval.v_number = VARNUMBER_MAX;
- } else {
- rettv->vval.v_number = (varnumber_T)f;
- }
- }
-}
-
-/*
- * "fmod()" function
- */
-static void f_fmod(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- float_T fx;
- float_T fy;
-
- rettv->v_type = VAR_FLOAT;
- if (tv_get_float_chk(argvars, &fx) && tv_get_float_chk(&argvars[1], &fy)) {
- rettv->vval.v_float = fmod(fx, fy);
- } else {
- rettv->vval.v_float = 0.0;
- }
-}
-
-/*
- * "fnameescape({string})" function
- */
-static void f_fnameescape(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_string = (char_u *)vim_strsave_fnameescape(
- tv_get_string(&argvars[0]), false);
- rettv->v_type = VAR_STRING;
-}
-
-/*
- * "fnamemodify({fname}, {mods})" function
- */
-static void f_fnamemodify(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char_u *fbuf = NULL;
- size_t len;
- char buf[NUMBUFLEN];
- const char *fname = tv_get_string_chk(&argvars[0]);
- const char *const mods = tv_get_string_buf_chk(&argvars[1], buf);
- if (fname == NULL || mods == NULL) {
- fname = NULL;
- } else {
- len = strlen(fname);
- size_t usedlen = 0;
- (void)modify_fname((char_u *)mods, false, &usedlen,
- (char_u **)&fname, &fbuf, &len);
- }
-
- rettv->v_type = VAR_STRING;
- if (fname == NULL) {
- rettv->vval.v_string = NULL;
- } else {
- rettv->vval.v_string = (char_u *)xmemdupz(fname, len);
- }
- xfree(fbuf);
-}
-
-
-/*
- * "foldclosed()" function
- */
-static void foldclosed_both(typval_T *argvars, typval_T *rettv, int end)
-{
- const linenr_T lnum = tv_get_lnum(argvars);
- if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) {
- linenr_T first;
- linenr_T last;
- if (hasFoldingWin(curwin, lnum, &first, &last, false, NULL)) {
- if (end) {
- rettv->vval.v_number = (varnumber_T)last;
- } else {
- rettv->vval.v_number = (varnumber_T)first;
- }
- return;
- }
- }
- rettv->vval.v_number = -1;
-}
-
-/*
- * "foldclosed()" function
- */
-static void f_foldclosed(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- foldclosed_both(argvars, rettv, FALSE);
-}
-
-/*
- * "foldclosedend()" function
- */
-static void f_foldclosedend(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- foldclosed_both(argvars, rettv, TRUE);
-}
-
-/*
- * "foldlevel()" function
- */
-static void f_foldlevel(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const linenr_T lnum = tv_get_lnum(argvars);
- if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) {
- rettv->vval.v_number = foldLevel(lnum);
- }
-}
-
-/*
- * "foldtext()" function
- */
-static void f_foldtext(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- linenr_T foldstart;
- linenr_T foldend;
- char_u *dashes;
- linenr_T lnum;
- char_u *s;
- char_u *r;
- int len;
- char *txt;
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
-
- foldstart = (linenr_T)get_vim_var_nr(VV_FOLDSTART);
- foldend = (linenr_T)get_vim_var_nr(VV_FOLDEND);
- dashes = get_vim_var_str(VV_FOLDDASHES);
- if (foldstart > 0 && foldend <= curbuf->b_ml.ml_line_count) {
- // Find first non-empty line in the fold.
- for (lnum = foldstart; lnum < foldend; lnum++) {
- if (!linewhite(lnum)) {
- break;
- }
- }
-
- /* Find interesting text in this line. */
- s = skipwhite(ml_get(lnum));
- /* skip C comment-start */
- if (s[0] == '/' && (s[1] == '*' || s[1] == '/')) {
- s = skipwhite(s + 2);
- if (*skipwhite(s) == NUL && lnum + 1 < foldend) {
- s = skipwhite(ml_get(lnum + 1));
- if (*s == '*')
- s = skipwhite(s + 1);
- }
- }
- unsigned long count = (unsigned long)(foldend - foldstart + 1);
- txt = NGETTEXT("+-%s%3ld line: ", "+-%s%3ld lines: ", count);
- r = xmalloc(STRLEN(txt)
- + STRLEN(dashes) // for %s
- + 20 // for %3ld
- + STRLEN(s)); // concatenated
- sprintf((char *)r, txt, dashes, count);
- len = (int)STRLEN(r);
- STRCAT(r, s);
- /* remove 'foldmarker' and 'commentstring' */
- foldtext_cleanup(r + len);
- rettv->vval.v_string = r;
- }
-}
-
-/*
- * "foldtextresult(lnum)" function
- */
-static void f_foldtextresult(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char_u *text;
- char_u buf[FOLD_TEXT_LEN];
- foldinfo_T foldinfo;
- int fold_count;
- static bool entered = false;
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
- if (entered) {
- return; // reject recursive use
- }
- entered = true;
- linenr_T lnum = tv_get_lnum(argvars);
- // Treat illegal types and illegal string values for {lnum} the same.
- if (lnum < 0) {
- lnum = 0;
- }
- fold_count = foldedCount(curwin, lnum, &foldinfo);
- if (fold_count > 0) {
- text = get_foldtext(curwin, lnum, lnum + fold_count - 1, &foldinfo, buf);
- if (text == buf) {
- text = vim_strsave(text);
- }
- rettv->vval.v_string = text;
- }
-
- entered = false;
-}
-
-/*
- * "foreground()" function
- */
-static void f_foreground(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
-}
-
-static void common_function(typval_T *argvars, typval_T *rettv,
- bool is_funcref, FunPtr fptr)
+void common_function(typval_T *argvars, typval_T *rettv,
+ bool is_funcref, FunPtr fptr)
{
char_u *s;
char_u *name;
@@ -9735,6 +6115,10 @@ static void common_function(typval_T *argvars, typval_T *rettv,
list = argvars[arg_idx].vval.v_list;
if (tv_list_len(list) == 0) {
arg_idx = 0;
+ } else if (tv_list_len(list) > MAX_FUNC_ARGS) {
+ emsg_funcname((char *)e_toomanyarg, name);
+ xfree(name);
+ goto theend;
}
}
}
@@ -9802,116 +6186,8 @@ theend:
xfree(trans_name);
}
-static void f_funcref(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- common_function(argvars, rettv, true, fptr);
-}
-
-static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- common_function(argvars, rettv, false, fptr);
-}
-
-/// "garbagecollect()" function
-static void f_garbagecollect(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- // This is postponed until we are back at the toplevel, because we may be
- // using Lists and Dicts internally. E.g.: ":echo [garbagecollect()]".
- want_garbage_collect = true;
-
- if (argvars[0].v_type != VAR_UNKNOWN && tv_get_number(&argvars[0]) == 1) {
- garbage_collect_at_exit = true;
- }
-}
-
-/*
- * "get()" function
- */
-static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- listitem_T *li;
- list_T *l;
- dictitem_T *di;
- dict_T *d;
- typval_T *tv = NULL;
- bool what_is_dict = false;
-
- if (argvars[0].v_type == VAR_LIST) {
- if ((l = argvars[0].vval.v_list) != NULL) {
- bool error = false;
-
- li = tv_list_find(l, tv_get_number_chk(&argvars[1], &error));
- if (!error && li != NULL) {
- tv = TV_LIST_ITEM_TV(li);
- }
- }
- } else if (argvars[0].v_type == VAR_DICT) {
- if ((d = argvars[0].vval.v_dict) != NULL) {
- di = tv_dict_find(d, tv_get_string(&argvars[1]), -1);
- if (di != NULL) {
- tv = &di->di_tv;
- }
- }
- } else if (tv_is_func(argvars[0])) {
- partial_T *pt;
- partial_T fref_pt;
-
- if (argvars[0].v_type == VAR_PARTIAL) {
- pt = argvars[0].vval.v_partial;
- } else {
- memset(&fref_pt, 0, sizeof(fref_pt));
- fref_pt.pt_name = argvars[0].vval.v_string;
- pt = &fref_pt;
- }
-
- if (pt != NULL) {
- const char *const what = tv_get_string(&argvars[1]);
-
- if (strcmp(what, "func") == 0 || strcmp(what, "name") == 0) {
- rettv->v_type = (*what == 'f' ? VAR_FUNC : VAR_STRING);
- const char *const n = (const char *)partial_name(pt);
- assert(n != NULL);
- rettv->vval.v_string = (char_u *)xstrdup(n);
- if (rettv->v_type == VAR_FUNC) {
- func_ref(rettv->vval.v_string);
- }
- } else if (strcmp(what, "dict") == 0) {
- what_is_dict = true;
- if (pt->pt_dict != NULL) {
- tv_dict_set_ret(rettv, pt->pt_dict);
- }
- } else if (strcmp(what, "args") == 0) {
- rettv->v_type = VAR_LIST;
- if (tv_list_alloc_ret(rettv, pt->pt_argc) != NULL) {
- for (int i = 0; i < pt->pt_argc; i++) {
- tv_list_append_tv(rettv->vval.v_list, &pt->pt_argv[i]);
- }
- }
- } else {
- EMSG2(_(e_invarg2), what);
- }
-
- // When {what} == "dict" and pt->pt_dict == NULL, evaluate the
- // third argument
- if (!what_is_dict) {
- return;
- }
- }
- } else {
- EMSG2(_(e_listdictarg), "get()");
- }
-
- if (tv == NULL) {
- if (argvars[2].v_type != VAR_UNKNOWN) {
- tv_copy(&argvars[2], rettv);
- }
- } else {
- tv_copy(tv, rettv);
- }
-}
-
/// Returns buffer options, variables and other attributes in a dictionary.
-static dict_T *get_buffer_info(buf_T *buf)
+dict_T *get_buffer_info(buf_T *buf)
{
dict_T *const dict = tv_dict_alloc();
@@ -9945,110 +6221,9 @@ static dict_T *get_buffer_info(buf_T *buf)
tv_dict_add_list(dict, S_LEN("signs"), get_buffer_signs(buf));
}
- return dict;
-}
-
-/// "getbufinfo()" function
-static void f_getbufinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- buf_T *argbuf = NULL;
- bool filtered = false;
- bool sel_buflisted = false;
- bool sel_bufloaded = false;
- bool sel_bufmodified = false;
-
- tv_list_alloc_ret(rettv, kListLenMayKnow);
-
- // List of all the buffers or selected buffers
- if (argvars[0].v_type == VAR_DICT) {
- dict_T *sel_d = argvars[0].vval.v_dict;
-
- if (sel_d != NULL) {
- dictitem_T *di;
-
- filtered = true;
-
- di = tv_dict_find(sel_d, S_LEN("buflisted"));
- if (di != NULL && tv_get_number(&di->di_tv)) {
- sel_buflisted = true;
- }
-
- di = tv_dict_find(sel_d, S_LEN("bufloaded"));
- if (di != NULL && tv_get_number(&di->di_tv)) {
- sel_bufloaded = true;
- }
- di = tv_dict_find(sel_d, S_LEN("bufmodified"));
- if (di != NULL && tv_get_number(&di->di_tv)) {
- sel_bufmodified = true;
- }
- }
- } else if (argvars[0].v_type != VAR_UNKNOWN) {
- // Information about one buffer. Argument specifies the buffer
- if (tv_check_num(&argvars[0])) { // issue errmsg if type error
- emsg_off++;
- argbuf = tv_get_buf(&argvars[0], false);
- emsg_off--;
- if (argbuf == NULL) {
- return;
- }
- }
- }
-
- // Return information about all the buffers or a specified buffer
- FOR_ALL_BUFFERS(buf) {
- if (argbuf != NULL && argbuf != buf) {
- continue;
- }
- if (filtered && ((sel_bufloaded && buf->b_ml.ml_mfp == NULL)
- || (sel_buflisted && !buf->b_p_bl)
- || (sel_bufmodified && !buf->b_changed))) {
- continue;
- }
-
- dict_T *const d = get_buffer_info(buf);
- tv_list_append_dict(rettv->vval.v_list, d);
- if (argbuf != NULL) {
- return;
- }
- }
-}
-
-/*
- * Get line or list of lines from buffer "buf" into "rettv".
- * Return a range (from start to end) of lines in rettv from the specified
- * buffer.
- * If 'retlist' is TRUE, then the lines are returned as a Vim List.
- */
-static void get_buffer_lines(buf_T *buf, linenr_T start, linenr_T end, int retlist, typval_T *rettv)
-{
- rettv->v_type = (retlist ? VAR_LIST : VAR_STRING);
- rettv->vval.v_string = NULL;
-
- if (buf == NULL || buf->b_ml.ml_mfp == NULL || start < 0 || end < start) {
- if (retlist) {
- tv_list_alloc_ret(rettv, 0);
- }
- return;
- }
+ tv_dict_add_nr(dict, S_LEN("lastused"), buf->b_last_used);
- if (retlist) {
- if (start < 1) {
- start = 1;
- }
- if (end > buf->b_ml.ml_line_count) {
- end = buf->b_ml.ml_line_count;
- }
- tv_list_alloc_ret(rettv, end - start + 1);
- while (start <= end) {
- tv_list_append_string(rettv->vval.v_list,
- (const char *)ml_get_buf(buf, start++, false), -1);
- }
- } else {
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = ((start >= 1 && start <= buf->b_ml.ml_line_count)
- ? vim_strsave(ml_get_buf(buf, start, false))
- : NULL);
- }
+ return dict;
}
/// Get the line number from VimL object
@@ -10061,8 +6236,8 @@ static void get_buffer_lines(buf_T *buf, linenr_T start, linenr_T end, int retli
/// be NULL, in this case "$" results in zero return.
///
/// @return Line number or 0 in case of error.
-static linenr_T tv_get_lnum_buf(const typval_T *const tv,
- const buf_T *const buf)
+linenr_T tv_get_lnum_buf(const typval_T *const tv,
+ const buf_T *const buf)
FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_WARN_UNUSED_RESULT
{
if (tv->v_type == VAR_STRING
@@ -10074,675 +6249,8 @@ static linenr_T tv_get_lnum_buf(const typval_T *const tv,
return tv_get_number_chk(tv, NULL);
}
-/*
- * "getbufline()" function
- */
-static void f_getbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- buf_T *buf = NULL;
-
- if (tv_check_str_or_nr(&argvars[0])) {
- emsg_off++;
- buf = tv_get_buf(&argvars[0], false);
- emsg_off--;
- }
-
- const linenr_T lnum = tv_get_lnum_buf(&argvars[1], buf);
- const linenr_T end = (argvars[2].v_type == VAR_UNKNOWN
- ? lnum
- : tv_get_lnum_buf(&argvars[2], buf));
-
- get_buffer_lines(buf, lnum, end, true, rettv);
-}
-
-/*
- * "getbufvar()" function
- */
-static void f_getbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- bool done = false;
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
-
- if (!tv_check_str_or_nr(&argvars[0])) {
- goto f_getbufvar_end;
- }
-
- const char *varname = tv_get_string_chk(&argvars[1]);
- emsg_off++;
- buf_T *const buf = tv_get_buf(&argvars[0], false);
-
- if (buf != NULL && varname != NULL) {
- // set curbuf to be our buf, temporarily
- buf_T *const save_curbuf = curbuf;
- curbuf = buf;
-
- if (*varname == '&') { // buffer-local-option
- if (varname[1] == NUL) {
- // get all buffer-local options in a dict
- dict_T *opts = get_winbuf_options(true);
-
- if (opts != NULL) {
- tv_dict_set_ret(rettv, opts);
- done = true;
- }
- } else if (get_option_tv(&varname, rettv, true) == OK) {
- // buffer-local-option
- done = true;
- }
- } else {
- // Look up the variable.
- // Let getbufvar({nr}, "") return the "b:" dictionary.
- dictitem_T *const v = find_var_in_ht(&curbuf->b_vars->dv_hashtab, 'b',
- varname, strlen(varname), false);
- if (v != NULL) {
- tv_copy(&v->di_tv, rettv);
- done = true;
- }
- }
-
- // restore previous notion of curbuf
- curbuf = save_curbuf;
- }
- emsg_off--;
-
-f_getbufvar_end:
- if (!done && argvars[2].v_type != VAR_UNKNOWN) {
- // use the default value
- tv_copy(&argvars[2], rettv);
- }
-}
-
-// "getchangelist()" function
-static void f_getchangelist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- tv_list_alloc_ret(rettv, 2);
- vim_ignored = tv_get_number(&argvars[0]); // issue errmsg if type error
- emsg_off++;
- const buf_T *const buf = tv_get_buf(&argvars[0], false);
- emsg_off--;
- if (buf == NULL) {
- return;
- }
-
- list_T *const l = tv_list_alloc(buf->b_changelistlen);
- tv_list_append_list(rettv->vval.v_list, l);
- // The current window change list index tracks only the position in the
- // current buffer change list. For other buffers, use the change list
- // length as the current index.
- tv_list_append_number(rettv->vval.v_list,
- (buf == curwin->w_buffer)
- ? curwin->w_changelistidx
- : buf->b_changelistlen);
-
- for (int i = 0; i < buf->b_changelistlen; i++) {
- if (buf->b_changelist[i].mark.lnum == 0) {
- continue;
- }
- dict_T *const d = tv_dict_alloc();
- tv_list_append_dict(l, d);
- tv_dict_add_nr(d, S_LEN("lnum"), buf->b_changelist[i].mark.lnum);
- tv_dict_add_nr(d, S_LEN("col"), buf->b_changelist[i].mark.col);
- tv_dict_add_nr(d, S_LEN("coladd"), buf->b_changelist[i].mark.coladd);
- }
-}
-
-/*
- * "getchar()" function
- */
-static void f_getchar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- varnumber_T n;
- bool error = false;
-
- no_mapping++;
- for (;; ) {
- // Position the cursor. Needed after a message that ends in a space,
- // or if event processing caused a redraw.
- ui_cursor_goto(msg_row, msg_col);
-
- if (argvars[0].v_type == VAR_UNKNOWN) {
- // getchar(): blocking wait.
- if (!(char_avail() || using_script() || input_available())) {
- (void)os_inchar(NULL, 0, -1, 0, main_loop.events);
- if (!multiqueue_empty(main_loop.events)) {
- multiqueue_process_events(main_loop.events);
- continue;
- }
- }
- n = safe_vgetc();
- } else if (tv_get_number_chk(&argvars[0], &error) == 1) {
- // getchar(1): only check if char avail
- n = vpeekc_any();
- } else if (error || vpeekc_any() == NUL) {
- // illegal argument or getchar(0) and no char avail: return zero
- n = 0;
- } else {
- // getchar(0) and char avail: return char
- n = safe_vgetc();
- }
-
- if (n == K_IGNORE) {
- continue;
- }
- break;
- }
- no_mapping--;
-
- vimvars[VV_MOUSE_WIN].vv_nr = 0;
- vimvars[VV_MOUSE_WINID].vv_nr = 0;
- vimvars[VV_MOUSE_LNUM].vv_nr = 0;
- vimvars[VV_MOUSE_COL].vv_nr = 0;
-
- rettv->vval.v_number = n;
- if (IS_SPECIAL(n) || mod_mask != 0) {
- char_u temp[10]; /* modifier: 3, mbyte-char: 6, NUL: 1 */
- int i = 0;
-
- /* Turn a special key into three bytes, plus modifier. */
- if (mod_mask != 0) {
- temp[i++] = K_SPECIAL;
- temp[i++] = KS_MODIFIER;
- temp[i++] = mod_mask;
- }
- if (IS_SPECIAL(n)) {
- temp[i++] = K_SPECIAL;
- temp[i++] = K_SECOND(n);
- temp[i++] = K_THIRD(n);
- } else {
- i += utf_char2bytes(n, temp + i);
- }
- temp[i++] = NUL;
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = vim_strsave(temp);
-
- if (is_mouse_key(n)) {
- int row = mouse_row;
- int col = mouse_col;
- int grid = mouse_grid;
- win_T *win;
- linenr_T lnum;
- win_T *wp;
- int winnr = 1;
-
- if (row >= 0 && col >= 0) {
- /* Find the window at the mouse coordinates and compute the
- * text position. */
- win = mouse_find_win(&grid, &row, &col);
- if (win == NULL) {
- return;
- }
- (void)mouse_comp_pos(win, &row, &col, &lnum);
- for (wp = firstwin; wp != win; wp = wp->w_next)
- ++winnr;
- vimvars[VV_MOUSE_WIN].vv_nr = winnr;
- vimvars[VV_MOUSE_WINID].vv_nr = wp->handle;
- vimvars[VV_MOUSE_LNUM].vv_nr = lnum;
- vimvars[VV_MOUSE_COL].vv_nr = col + 1;
- }
- }
- }
-}
-
-/*
- * "getcharmod()" function
- */
-static void f_getcharmod(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = mod_mask;
-}
-
-/*
- * "getcharsearch()" function
- */
-static void f_getcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- tv_dict_alloc_ret(rettv);
-
- dict_T *dict = rettv->vval.v_dict;
-
- tv_dict_add_str(dict, S_LEN("char"), last_csearch());
- tv_dict_add_nr(dict, S_LEN("forward"), last_csearch_forward());
- tv_dict_add_nr(dict, S_LEN("until"), last_csearch_until());
-}
-
-/*
- * "getcmdline()" function
- */
-static void f_getcmdline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = get_cmdline_str();
-}
-
-/*
- * "getcmdpos()" function
- */
-static void f_getcmdpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = get_cmdline_pos() + 1;
-}
-
-/*
- * "getcmdtype()" function
- */
-static void f_getcmdtype(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = xmallocz(1);
- rettv->vval.v_string[0] = get_cmdline_type();
-}
-
-/*
- * "getcmdwintype()" function
- */
-static void f_getcmdwintype(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
- rettv->vval.v_string = xmallocz(1);
- rettv->vval.v_string[0] = cmdwin_type;
-}
-
-// "getcompletion()" function
-static void f_getcompletion(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char_u *pat;
- expand_T xpc;
- bool filtered = false;
- int options = WILD_SILENT | WILD_USE_NL | WILD_ADD_SLASH
- | WILD_NO_BEEP;
-
- if (argvars[2].v_type != VAR_UNKNOWN) {
- filtered = (bool)tv_get_number_chk(&argvars[2], NULL);
- }
-
- if (p_wic) {
- options |= WILD_ICASE;
- }
-
- // For filtered results, 'wildignore' is used
- if (!filtered) {
- options |= WILD_KEEP_ALL;
- }
-
- if (argvars[0].v_type != VAR_STRING || argvars[1].v_type != VAR_STRING) {
- EMSG(_(e_invarg));
- return;
- }
-
- if (strcmp(tv_get_string(&argvars[1]), "cmdline") == 0) {
- set_one_cmd_context(&xpc, tv_get_string(&argvars[0]));
- xpc.xp_pattern_len = STRLEN(xpc.xp_pattern);
- goto theend;
- }
-
- ExpandInit(&xpc);
- xpc.xp_pattern = (char_u *)tv_get_string(&argvars[0]);
- xpc.xp_pattern_len = STRLEN(xpc.xp_pattern);
- xpc.xp_context = cmdcomplete_str_to_type(
- (char_u *)tv_get_string(&argvars[1]));
- if (xpc.xp_context == EXPAND_NOTHING) {
- EMSG2(_(e_invarg2), argvars[1].vval.v_string);
- return;
- }
-
- if (xpc.xp_context == EXPAND_MENUS) {
- set_context_in_menu_cmd(&xpc, (char_u *)"menu", xpc.xp_pattern, false);
- xpc.xp_pattern_len = STRLEN(xpc.xp_pattern);
- }
-
- if (xpc.xp_context == EXPAND_CSCOPE) {
- set_context_in_cscope_cmd(&xpc, (const char *)xpc.xp_pattern, CMD_cscope);
- xpc.xp_pattern_len = STRLEN(xpc.xp_pattern);
- }
-
- if (xpc.xp_context == EXPAND_SIGN) {
- set_context_in_sign_cmd(&xpc, xpc.xp_pattern);
- xpc.xp_pattern_len = STRLEN(xpc.xp_pattern);
- }
-
-theend:
- pat = addstar(xpc.xp_pattern, xpc.xp_pattern_len, xpc.xp_context);
- ExpandOne(&xpc, pat, NULL, options, WILD_ALL_KEEP);
- tv_list_alloc_ret(rettv, xpc.xp_numfiles);
-
- for (int i = 0; i < xpc.xp_numfiles; i++) {
- tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i],
- -1);
- }
- xfree(pat);
- ExpandCleanup(&xpc);
-}
-
-/// `getcwd([{win}[, {tab}]])` function
-///
-/// Every scope not specified implies the currently selected scope object.
-///
-/// @pre The arguments must be of type number.
-/// @pre There may not be more than two arguments.
-/// @pre An argument may not be -1 if preceding arguments are not all -1.
-///
-/// @post The return value will be a string.
-static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- // Possible scope of working directory to return.
- CdScope scope = kCdScopeInvalid;
-
- // Numbers of the scope objects (window, tab) we want the working directory
- // of. A `-1` means to skip this scope, a `0` means the current object.
- int scope_number[] = {
- [kCdScopeWindow] = 0, // Number of window to look at.
- [kCdScopeTab ] = 0, // Number of tab to look at.
- };
-
- char_u *cwd = NULL; // Current working directory to print
- char_u *from = NULL; // The original string to copy
-
- tabpage_T *tp = curtab; // The tabpage to look at.
- win_T *win = curwin; // The window to look at.
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
-
- // Pre-conditions and scope extraction together
- for (int i = MIN_CD_SCOPE; i < MAX_CD_SCOPE; i++) {
- // If there is no argument there are no more scopes after it, break out.
- if (argvars[i].v_type == VAR_UNKNOWN) {
- break;
- }
- if (argvars[i].v_type != VAR_NUMBER) {
- EMSG(_(e_invarg));
- return;
- }
- scope_number[i] = argvars[i].vval.v_number;
- // It is an error for the scope number to be less than `-1`.
- if (scope_number[i] < -1) {
- EMSG(_(e_invarg));
- return;
- }
- // Use the narrowest scope the user requested
- if (scope_number[i] >= 0 && scope == kCdScopeInvalid) {
- // The scope is the current iteration step.
- scope = i;
- } else if (scope_number[i] < 0) {
- scope = i + 1;
- }
- }
-
- // If the user didn't specify anything, default to window scope
- if (scope == kCdScopeInvalid) {
- scope = MIN_CD_SCOPE;
- }
-
- // Find the tabpage by number
- if (scope_number[kCdScopeTab] > 0) {
- tp = find_tabpage(scope_number[kCdScopeTab]);
- if (!tp) {
- EMSG(_("E5000: Cannot find tab number."));
- return;
- }
- }
-
- // Find the window in `tp` by number, `NULL` if none.
- if (scope_number[kCdScopeWindow] >= 0) {
- if (scope_number[kCdScopeTab] < 0) {
- EMSG(_("E5001: Higher scope cannot be -1 if lower scope is >= 0."));
- return;
- }
-
- if (scope_number[kCdScopeWindow] > 0) {
- win = find_win_by_nr(&argvars[0], tp);
- if (!win) {
- EMSG(_("E5002: Cannot find window number."));
- return;
- }
- }
- }
-
- cwd = xmalloc(MAXPATHL);
-
- switch (scope) {
- case kCdScopeWindow:
- assert(win);
- from = win->w_localdir;
- if (from) {
- break;
- }
- FALLTHROUGH;
- case kCdScopeTab:
- assert(tp);
- from = tp->tp_localdir;
- if (from) {
- break;
- }
- FALLTHROUGH;
- case kCdScopeGlobal:
- if (globaldir) { // `globaldir` is not always set.
- from = globaldir;
- } else if (os_dirname(cwd, MAXPATHL) == FAIL) { // Get the OS CWD.
- from = (char_u *)""; // Return empty string on failure.
- }
- break;
- case kCdScopeInvalid: // We should never get here
- assert(false);
- }
-
- if (from) {
- xstrlcpy((char *)cwd, (char *)from, MAXPATHL);
- }
-
- rettv->vval.v_string = vim_strsave(cwd);
-#ifdef BACKSLASH_IN_FILENAME
- slash_adjust(rettv->vval.v_string);
-#endif
-
- xfree(cwd);
-}
-
-/*
- * "getfontname()" function
- */
-static void f_getfontname(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
-}
-
-/*
- * "getfperm({fname})" function
- */
-static void f_getfperm(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char *perm = NULL;
- char_u flags[] = "rwx";
-
- const char *filename = tv_get_string(&argvars[0]);
- int32_t file_perm = os_getperm(filename);
- if (file_perm >= 0) {
- perm = xstrdup("---------");
- for (int i = 0; i < 9; i++) {
- if (file_perm & (1 << (8 - i))) {
- perm[i] = flags[i % 3];
- }
- }
- }
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = (char_u *)perm;
-}
-
-/*
- * "getfsize({fname})" function
- */
-static void f_getfsize(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *fname = tv_get_string(&argvars[0]);
-
- rettv->v_type = VAR_NUMBER;
-
- FileInfo file_info;
- if (os_fileinfo(fname, &file_info)) {
- uint64_t filesize = os_fileinfo_size(&file_info);
- if (os_isdir((const char_u *)fname)) {
- rettv->vval.v_number = 0;
- } else {
- rettv->vval.v_number = (varnumber_T)filesize;
-
- /* non-perfect check for overflow */
- if ((uint64_t)rettv->vval.v_number != filesize) {
- rettv->vval.v_number = -2;
- }
- }
- } else {
- rettv->vval.v_number = -1;
- }
-}
-
-/*
- * "getftime({fname})" function
- */
-static void f_getftime(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *fname = tv_get_string(&argvars[0]);
-
- FileInfo file_info;
- if (os_fileinfo(fname, &file_info)) {
- rettv->vval.v_number = (varnumber_T)file_info.stat.st_mtim.tv_sec;
- } else {
- rettv->vval.v_number = -1;
- }
-}
-
-/*
- * "getftype({fname})" function
- */
-static void f_getftype(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char_u *type = NULL;
- char *t;
-
- const char *fname = tv_get_string(&argvars[0]);
-
- rettv->v_type = VAR_STRING;
- FileInfo file_info;
- if (os_fileinfo_link(fname, &file_info)) {
- uint64_t mode = file_info.stat.st_mode;
-#ifdef S_ISREG
- if (S_ISREG(mode))
- t = "file";
- else if (S_ISDIR(mode))
- t = "dir";
-# ifdef S_ISLNK
- else if (S_ISLNK(mode))
- t = "link";
-# endif
-# ifdef S_ISBLK
- else if (S_ISBLK(mode))
- t = "bdev";
-# endif
-# ifdef S_ISCHR
- else if (S_ISCHR(mode))
- t = "cdev";
-# endif
-# ifdef S_ISFIFO
- else if (S_ISFIFO(mode))
- t = "fifo";
-# endif
-# ifdef S_ISSOCK
- else if (S_ISSOCK(mode))
- t = "socket";
-# endif
- else
- t = "other";
-#else
-# ifdef S_IFMT
- switch (mode & S_IFMT) {
- case S_IFREG: t = "file"; break;
- case S_IFDIR: t = "dir"; break;
-# ifdef S_IFLNK
- case S_IFLNK: t = "link"; break;
-# endif
-# ifdef S_IFBLK
- case S_IFBLK: t = "bdev"; break;
-# endif
-# ifdef S_IFCHR
- case S_IFCHR: t = "cdev"; break;
-# endif
-# ifdef S_IFIFO
- case S_IFIFO: t = "fifo"; break;
-# endif
-# ifdef S_IFSOCK
- case S_IFSOCK: t = "socket"; break;
-# endif
- default: t = "other";
- }
-# else
- if (os_isdir((const char_u *)fname)) {
- t = "dir";
- } else {
- t = "file";
- }
-# endif
-#endif
- type = vim_strsave((char_u *)t);
- }
- rettv->vval.v_string = type;
-}
-
-// "getjumplist()" function
-static void f_getjumplist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- tv_list_alloc_ret(rettv, kListLenMayKnow);
- win_T *const wp = find_tabwin(&argvars[0], &argvars[1]);
- if (wp == NULL) {
- return;
- }
-
- cleanup_jumplist(wp, true);
-
- list_T *const l = tv_list_alloc(wp->w_jumplistlen);
- tv_list_append_list(rettv->vval.v_list, l);
- tv_list_append_number(rettv->vval.v_list, wp->w_jumplistidx);
-
- for (int i = 0; i < wp->w_jumplistlen; i++) {
- if (wp->w_jumplist[i].fmark.mark.lnum == 0) {
- continue;
- }
- dict_T *const d = tv_dict_alloc();
- tv_list_append_dict(l, d);
- tv_dict_add_nr(d, S_LEN("lnum"), wp->w_jumplist[i].fmark.mark.lnum);
- tv_dict_add_nr(d, S_LEN("col"), wp->w_jumplist[i].fmark.mark.col);
- tv_dict_add_nr(d, S_LEN("coladd"), wp->w_jumplist[i].fmark.mark.coladd);
- tv_dict_add_nr(d, S_LEN("bufnr"), wp->w_jumplist[i].fmark.fnum);
- if (wp->w_jumplist[i].fname != NULL) {
- tv_dict_add_str(d, S_LEN("filename"), (char *)wp->w_jumplist[i].fname);
- }
- }
-}
-
-/*
- * "getline(lnum, [end])" function
- */
-static void f_getline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- linenr_T end;
- bool retlist;
-
- const linenr_T lnum = tv_get_lnum(argvars);
- if (argvars[1].v_type == VAR_UNKNOWN) {
- end = lnum;
- retlist = false;
- } else {
- end = tv_get_lnum(&argvars[1]);
- retlist = true;
- }
-
- get_buffer_lines(curbuf, lnum, end, retlist, rettv);
-}
-
-static void get_qf_loc_list(int is_qf, win_T *wp, typval_T *what_arg,
- typval_T *rettv)
+void get_qf_loc_list(int is_qf, win_T *wp, typval_T *what_arg,
+ typval_T *rettv)
{
if (what_arg->v_type == VAR_UNKNOWN) {
tv_list_alloc_ret(rettv, kListLenMayKnow);
@@ -10765,217 +6273,9 @@ static void get_qf_loc_list(int is_qf, win_T *wp, typval_T *what_arg,
}
}
-/// "getloclist()" function
-static void f_getloclist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- win_T *wp = find_win_by_nr_or_id(&argvars[0]);
- get_qf_loc_list(false, wp, &argvars[1], rettv);
-}
-
-/*
- * "getmatches()" function
- */
-static void f_getmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- matchitem_T *cur = curwin->w_match_head;
- int i;
-
- tv_list_alloc_ret(rettv, kListLenMayKnow);
- 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((int)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;
- }
-}
-
-/*
- * "getpid()" function
- */
-static void f_getpid(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = os_get_pid();
-}
-
-static void getpos_both(typval_T *argvars, typval_T *rettv, bool getcurpos)
-{
- pos_T *fp;
- int fnum = -1;
-
- if (getcurpos) {
- fp = &curwin->w_cursor;
- } else {
- fp = var2fpos(&argvars[0], true, &fnum);
- }
-
- list_T *const l = tv_list_alloc_ret(rettv, 4 + (!!getcurpos));
- tv_list_append_number(l, (fnum != -1) ? (varnumber_T)fnum : (varnumber_T)0);
- tv_list_append_number(l, ((fp != NULL)
- ? (varnumber_T)fp->lnum
- : (varnumber_T)0));
- tv_list_append_number(
- l, ((fp != NULL)
- ? (varnumber_T)(fp->col == MAXCOL ? MAXCOL : fp->col + 1)
- : (varnumber_T)0));
- tv_list_append_number(
- l, (fp != NULL) ? (varnumber_T)fp->coladd : (varnumber_T)0);
- if (getcurpos) {
- const int save_set_curswant = curwin->w_set_curswant;
- const colnr_T save_curswant = curwin->w_curswant;
- const colnr_T save_virtcol = curwin->w_virtcol;
-
- update_curswant();
- tv_list_append_number(l, (curwin->w_curswant == MAXCOL
- ? (varnumber_T)MAXCOL
- : (varnumber_T)curwin->w_curswant + 1));
-
- // Do not change "curswant", as it is unexpected that a get
- // function has a side effect.
- if (save_set_curswant) {
- curwin->w_set_curswant = save_set_curswant;
- curwin->w_curswant = save_curswant;
- curwin->w_virtcol = save_virtcol;
- curwin->w_valid &= ~VALID_VIRTCOL;
- }
- }
-}
-
-/*
- * "getcurpos(string)" function
- */
-static void f_getcurpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- getpos_both(argvars, rettv, true);
-}
-
-/*
- * "getpos(string)" function
- */
-static void f_getpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- getpos_both(argvars, rettv, false);
-}
-
-/// "getqflist()" functions
-static void f_getqflist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- get_qf_loc_list(true, NULL, &argvars[0], rettv);
-}
-
-/// "getreg()" function
-static void f_getreg(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *strregname;
- int arg2 = false;
- bool return_list = false;
- bool error = false;
-
- if (argvars[0].v_type != VAR_UNKNOWN) {
- strregname = tv_get_string_chk(&argvars[0]);
- error = strregname == NULL;
- if (argvars[1].v_type != VAR_UNKNOWN) {
- arg2 = tv_get_number_chk(&argvars[1], &error);
- if (!error && argvars[2].v_type != VAR_UNKNOWN) {
- return_list = tv_get_number_chk(&argvars[2], &error);
- }
- }
- } else {
- strregname = (const char *)vimvars[VV_REG].vv_str;
- }
-
- if (error) {
- return;
- }
-
- int regname = (uint8_t)(strregname == NULL ? '"' : *strregname);
- if (regname == 0) {
- regname = '"';
- }
-
- if (return_list) {
- rettv->v_type = VAR_LIST;
- rettv->vval.v_list =
- get_reg_contents(regname, (arg2 ? kGRegExprSrc : 0) | kGRegList);
- if (rettv->vval.v_list == NULL) {
- rettv->vval.v_list = tv_list_alloc(0);
- }
- tv_list_ref(rettv->vval.v_list);
- } else {
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = get_reg_contents(regname, arg2 ? kGRegExprSrc : 0);
- }
-}
-
-/*
- * "getregtype()" function
- */
-static void f_getregtype(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *strregname;
-
- if (argvars[0].v_type != VAR_UNKNOWN) {
- strregname = tv_get_string_chk(&argvars[0]);
- if (strregname == NULL) { // Type error; errmsg already given.
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
- return;
- }
- } else {
- // Default to v:register.
- strregname = (const char *)vimvars[VV_REG].vv_str;
- }
-
- int regname = (uint8_t)(strregname == NULL ? '"' : *strregname);
- if (regname == 0) {
- regname = '"';
- }
-
- colnr_T reglen = 0;
- char buf[NUMBUFLEN + 2];
- MotionType reg_type = get_reg_type(regname, &reglen);
- format_reg_type(reg_type, reglen, buf, ARRAY_SIZE(buf));
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = (char_u *)xstrdup(buf);
-}
-
/// Returns information (variables, options, etc.) about a tab page
/// as a dictionary.
-static dict_T *get_tabpage_info(tabpage_T *tp, int tp_idx)
+dict_T *get_tabpage_info(tabpage_T *tp, int tp_idx)
{
dict_T *const dict = tv_dict_alloc();
@@ -10993,106 +6293,8 @@ static dict_T *get_tabpage_info(tabpage_T *tp, int tp_idx)
return dict;
}
-/// "gettabinfo()" function
-static void f_gettabinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- tabpage_T *tparg = NULL;
-
- tv_list_alloc_ret(rettv, (argvars[0].v_type == VAR_UNKNOWN
- ? 1
- : kListLenMayKnow));
-
- if (argvars[0].v_type != VAR_UNKNOWN) {
- // Information about one tab page
- tparg = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL));
- if (tparg == NULL) {
- return;
- }
- }
-
- // Get information about a specific tab page or all tab pages
- int tpnr = 0;
- FOR_ALL_TABS(tp) {
- tpnr++;
- if (tparg != NULL && tp != tparg) {
- continue;
- }
- dict_T *const d = get_tabpage_info(tp, tpnr);
- tv_list_append_dict(rettv->vval.v_list, d);
- if (tparg != NULL) {
- return;
- }
- }
-}
-
-/*
- * "gettabvar()" function
- */
-static void f_gettabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- win_T *oldcurwin;
- tabpage_T *oldtabpage;
- bool done = false;
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
-
- const char *const varname = tv_get_string_chk(&argvars[1]);
- tabpage_T *const tp = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL));
- if (tp != NULL && varname != NULL) {
- // Set tp to be our tabpage, temporarily. Also set the window to the
- // first window in the tabpage, otherwise the window is not valid.
- win_T *const window = tp == curtab || tp->tp_firstwin == NULL
- ? firstwin
- : tp->tp_firstwin;
- if (switch_win(&oldcurwin, &oldtabpage, window, tp, true) == OK) {
- // look up the variable
- // Let gettabvar({nr}, "") return the "t:" dictionary.
- const dictitem_T *const v = find_var_in_ht(&tp->tp_vars->dv_hashtab, 't',
- varname, strlen(varname),
- false);
- if (v != NULL) {
- tv_copy(&v->di_tv, rettv);
- done = true;
- }
- }
-
- // restore previous notion of curwin
- restore_win(oldcurwin, oldtabpage, true);
- }
-
- if (!done && argvars[2].v_type != VAR_UNKNOWN) {
- tv_copy(&argvars[2], rettv);
- }
-}
-
-/*
- * "gettabwinvar()" function
- */
-static void f_gettabwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- getwinvar(argvars, rettv, 1);
-}
-
-// "gettagstack()" function
-static void f_gettagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- win_T *wp = curwin; // default is current window
-
- tv_dict_alloc_ret(rettv);
-
- if (argvars[0].v_type != VAR_UNKNOWN) {
- wp = find_win_by_nr_or_id(&argvars[0]);
- if (wp == NULL) {
- return;
- }
- }
-
- get_tagstack(wp, rettv->vval.v_dict);
-}
-
/// Returns information about a window as a dictionary.
-static dict_T *get_win_info(win_T *wp, int16_t tpnr, int16_t winnr)
+dict_T *get_win_info(win_T *wp, int16_t tpnr, int16_t winnr)
{
dict_T *const dict = tv_dict_alloc();
@@ -11103,6 +6305,7 @@ static dict_T *get_win_info(win_T *wp, int16_t tpnr, int16_t winnr)
tv_dict_add_nr(dict, S_LEN("winrow"), wp->w_winrow + 1);
tv_dict_add_nr(dict, S_LEN("topline"), wp->w_topline);
tv_dict_add_nr(dict, S_LEN("botline"), wp->w_botline - 1);
+ tv_dict_add_nr(dict, S_LEN("winbar"), wp->w_winbar_height);
tv_dict_add_nr(dict, S_LEN("width"), wp->w_width);
tv_dict_add_nr(dict, S_LEN("bufnr"), wp->w_buffer->b_fnum);
tv_dict_add_nr(dict, S_LEN("wincol"), wp->w_wincol + 1);
@@ -11118,149 +6321,11 @@ static dict_T *get_win_info(win_T *wp, int16_t tpnr, int16_t winnr)
return dict;
}
-/// "getwininfo()" function
-static void f_getwininfo(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- win_T *wparg = NULL;
-
- tv_list_alloc_ret(rettv, kListLenMayKnow);
-
- if (argvars[0].v_type != VAR_UNKNOWN) {
- wparg = win_id2wp(argvars);
- if (wparg == NULL) {
- return;
- }
- }
-
- // Collect information about either all the windows across all the tab
- // pages or one particular window.
- int16_t tabnr = 0;
- FOR_ALL_TABS(tp) {
- tabnr++;
- int16_t winnr = 0;
- FOR_ALL_WINDOWS_IN_TAB(wp, tp) {
- winnr++;
- if (wparg != NULL && wp != wparg) {
- continue;
- }
- dict_T *const d = get_win_info(wp, tabnr, winnr);
- tv_list_append_dict(rettv->vval.v_list, d);
- if (wparg != NULL) {
- // found information about a specific window
- return;
- }
- }
- }
-}
-
-// Dummy timer callback. Used by f_wait().
-static void dummy_timer_due_cb(TimeWatcher *tw, void *data)
-{
-}
-
-// Dummy timer close callback. Used by f_wait().
-static void dummy_timer_close_cb(TimeWatcher *tw, void *data)
-{
- xfree(tw);
-}
-
-/// "wait(timeout, condition[, interval])" function
-static void f_wait(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = -1;
-
- if (argvars[0].v_type != VAR_NUMBER) {
- EMSG2(_(e_invargval), "1");
- return;
- }
- if ((argvars[2].v_type != VAR_NUMBER && argvars[2].v_type != VAR_UNKNOWN)
- || (argvars[2].v_type == VAR_NUMBER && argvars[2].vval.v_number <= 0)) {
- EMSG2(_(e_invargval), "3");
- return;
- }
-
- int timeout = argvars[0].vval.v_number;
- typval_T expr = argvars[1];
- int interval = argvars[2].v_type == VAR_NUMBER
- ? argvars[2].vval.v_number
- : 200; // Default.
- TimeWatcher *tw = xmalloc(sizeof(TimeWatcher));
-
- // Start dummy timer.
- time_watcher_init(&main_loop, tw, NULL);
- tw->events = main_loop.events;
- tw->blockable = true;
- time_watcher_start(tw, dummy_timer_due_cb, interval, interval);
-
- typval_T argv = TV_INITIAL_VALUE;
- typval_T exprval = TV_INITIAL_VALUE;
- bool error = false;
- int save_called_emsg = called_emsg;
- called_emsg = false;
-
- LOOP_PROCESS_EVENTS_UNTIL(&main_loop, main_loop.events, timeout,
- eval_expr_typval(&expr, &argv, 0, &exprval) != OK
- || tv_get_number_chk(&exprval, &error)
- || called_emsg || error || got_int);
-
- if (called_emsg || error) {
- rettv->vval.v_number = -3;
- } else if (got_int) {
- got_int = false;
- vgetc();
- rettv->vval.v_number = -2;
- } else if (tv_get_number_chk(&exprval, &error)) {
- rettv->vval.v_number = 0;
- }
-
- called_emsg = save_called_emsg;
-
- // Stop dummy timer
- time_watcher_stop(tw);
- time_watcher_close(tw, dummy_timer_close_cb);
-}
-
-// "win_screenpos()" function
-static void f_win_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- tv_list_alloc_ret(rettv, 2);
- const win_T *const wp = find_win_by_nr_or_id(&argvars[0]);
- tv_list_append_number(rettv->vval.v_list, wp == NULL ? 0 : wp->w_winrow + 1);
- tv_list_append_number(rettv->vval.v_list, wp == NULL ? 0 : wp->w_wincol + 1);
-}
-
-// "getwinpos({timeout})" function
-static void f_getwinpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- tv_list_alloc_ret(rettv, 2);
- tv_list_append_number(rettv->vval.v_list, -1);
- tv_list_append_number(rettv->vval.v_list, -1);
-}
-
-/*
- * "getwinposx()" function
- */
-static void f_getwinposx(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = -1;
-}
-
-/*
- * "getwinposy()" function
- */
-static void f_getwinposy(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = -1;
-}
-
-/*
- * Find window specified by "vp" in tabpage "tp".
- */
-static win_T *
-find_win_by_nr (
+// Find window specified by "vp" in tabpage "tp".
+win_T *
+find_win_by_nr(
typval_T *vp,
- tabpage_T *tp /* NULL for current tab page */
+ tabpage_T *tp // NULL for current tab page
)
{
int nr = (int)tv_get_number_chk(vp, NULL);
@@ -11291,7 +6356,7 @@ find_win_by_nr (
}
/// Find window specified by "wvp" in tabpage "tvp".
-static win_T *find_tabwin(typval_T *wvp, typval_T *tvp)
+win_T *find_tabwin(typval_T *wvp, typval_T *tvp)
{
win_T *wp = NULL;
tabpage_T *tp = NULL;
@@ -11316,20 +6381,14 @@ static win_T *find_tabwin(typval_T *wvp, typval_T *tvp)
return wp;
}
-/// "getwinvar()" function
-static void f_getwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- getwinvar(argvars, rettv, 0);
-}
-
/*
* getwinvar() and gettabwinvar()
*/
-static void
+void
getwinvar(
typval_T *argvars,
typval_T *rettv,
- int off /* 1 for gettabwinvar() */
+ int off // 1 for gettabwinvar()
)
{
win_T *win, *oldcurwin;
@@ -11396,655 +6455,15 @@ getwinvar(
}
/*
- * "glob()" function
- */
-static void f_glob(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int options = WILD_SILENT|WILD_USE_NL;
- expand_T xpc;
- bool error = false;
-
- /* When the optional second argument is non-zero, don't remove matches
- * for 'wildignore' and don't put matches for 'suffixes' at the end. */
- rettv->v_type = VAR_STRING;
- if (argvars[1].v_type != VAR_UNKNOWN) {
- if (tv_get_number_chk(&argvars[1], &error)) {
- options |= WILD_KEEP_ALL;
- }
- if (argvars[2].v_type != VAR_UNKNOWN) {
- if (tv_get_number_chk(&argvars[2], &error)) {
- tv_list_set_ret(rettv, NULL);
- }
- if (argvars[3].v_type != VAR_UNKNOWN
- && tv_get_number_chk(&argvars[3], &error)) {
- options |= WILD_ALLLINKS;
- }
- }
- }
- if (!error) {
- ExpandInit(&xpc);
- xpc.xp_context = EXPAND_FILES;
- if (p_wic)
- options += WILD_ICASE;
- if (rettv->v_type == VAR_STRING) {
- rettv->vval.v_string = ExpandOne(
- &xpc, (char_u *)tv_get_string(&argvars[0]), NULL, options, WILD_ALL);
- } else {
- ExpandOne(&xpc, (char_u *)tv_get_string(&argvars[0]), NULL, options,
- WILD_ALL_KEEP);
- tv_list_alloc_ret(rettv, xpc.xp_numfiles);
- for (int i = 0; i < xpc.xp_numfiles; i++) {
- tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i],
- -1);
- }
- ExpandCleanup(&xpc);
- }
- } else
- rettv->vval.v_string = NULL;
-}
-
-/// "globpath()" function
-static void f_globpath(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int flags = 0; // Flags for globpath.
- bool error = false;
-
- // Return a string, or a list if the optional third argument is non-zero.
- rettv->v_type = VAR_STRING;
-
- if (argvars[2].v_type != VAR_UNKNOWN) {
- // When the optional second argument is non-zero, don't remove matches
- // for 'wildignore' and don't put matches for 'suffixes' at the end.
- if (tv_get_number_chk(&argvars[2], &error)) {
- flags |= WILD_KEEP_ALL;
- }
-
- if (argvars[3].v_type != VAR_UNKNOWN) {
- if (tv_get_number_chk(&argvars[3], &error)) {
- tv_list_set_ret(rettv, NULL);
- }
- if (argvars[4].v_type != VAR_UNKNOWN
- && tv_get_number_chk(&argvars[4], &error)) {
- flags |= WILD_ALLLINKS;
- }
- }
- }
-
- char buf1[NUMBUFLEN];
- const char *const file = tv_get_string_buf_chk(&argvars[1], buf1);
- if (file != NULL && !error) {
- garray_T ga;
- ga_init(&ga, (int)sizeof(char_u *), 10);
- globpath((char_u *)tv_get_string(&argvars[0]), (char_u *)file, &ga, flags);
-
- if (rettv->v_type == VAR_STRING) {
- rettv->vval.v_string = ga_concat_strings_sep(&ga, "\n");
- } else {
- tv_list_alloc_ret(rettv, ga.ga_len);
- for (int i = 0; i < ga.ga_len; i++) {
- tv_list_append_string(rettv->vval.v_list,
- ((const char **)(ga.ga_data))[i], -1);
- }
- }
-
- ga_clear_strings(&ga);
- } else {
- rettv->vval.v_string = NULL;
- }
-}
-
-// "glob2regpat()" function
-static void f_glob2regpat(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *const pat = tv_get_string_chk(&argvars[0]); // NULL on type error
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = ((pat == NULL)
- ? NULL
- : file_pat_to_reg_pat((char_u *)pat, NULL, NULL,
- false));
-}
-
-/// "has()" function
-static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- static const char *const has_list[] = {
-#if defined(BSD) && !defined(__APPLE__)
- "bsd",
-#endif
-#ifdef UNIX
- "unix",
-#endif
-#if defined(WIN32)
- "win32",
-#endif
-#if defined(WIN64) || defined(_WIN64)
- "win64",
-#endif
- "fname_case",
-#ifdef HAVE_ACL
- "acl",
-#endif
- "autochdir",
- "arabic",
- "autocmd",
- "browsefilter",
- "byte_offset",
- "cindent",
- "cmdline_compl",
- "cmdline_hist",
- "comments",
- "conceal",
- "cscope",
- "cursorbind",
- "cursorshape",
-#ifdef DEBUG
- "debug",
-#endif
- "dialog_con",
- "diff",
- "digraphs",
- "eval", /* always present, of course! */
- "ex_extra",
- "extra_search",
- "file_in_path",
- "filterpipe",
- "find_in_path",
- "float",
- "folding",
-#if defined(UNIX)
- "fork",
-#endif
- "gettext",
-#if defined(HAVE_ICONV)
- "iconv",
-#endif
- "insert_expand",
- "jumplist",
- "keymap",
- "lambda",
- "langmap",
- "libcall",
- "linebreak",
- "lispindent",
- "listcmds",
- "localmap",
-#ifdef __APPLE__
- "mac",
- "macunix",
- "osx",
- "osxdarwin",
-#endif
- "menu",
- "mksession",
- "modify_fname",
- "mouse",
- "multi_byte",
- "multi_lang",
- "num64",
- "packages",
- "path_extra",
- "persistent_undo",
- "postscript",
- "printer",
- "profile",
- "pythonx",
- "reltime",
- "quickfix",
- "rightleft",
- "scrollbind",
- "showcmd",
- "cmdline_info",
- "shada",
- "signs",
- "smartindent",
- "startuptime",
- "statusline",
- "spell",
- "syntax",
-#if !defined(UNIX)
- "system", // TODO(SplinterOfChaos): This IS defined for UNIX!
-#endif
- "tablineat",
- "tag_binary",
- "termguicolors",
- "termresponse",
- "textobjects",
- "timers",
- "title",
- "user-commands", /* was accidentally included in 5.4 */
- "user_commands",
- "vertsplit",
- "virtualedit",
- "visual",
- "visualextra",
- "vreplace",
- "wildignore",
- "wildmenu",
- "windows",
- "winaltkeys",
- "writebackup",
-#if defined(HAVE_WSL)
- "wsl",
-#endif
- "nvim",
- };
-
- bool n = false;
- const char *const name = tv_get_string(&argvars[0]);
- for (size_t i = 0; i < ARRAY_SIZE(has_list); i++) {
- if (STRICMP(name, has_list[i]) == 0) {
- n = true;
- break;
- }
- }
-
- if (!n) {
- if (STRNICMP(name, "patch", 5) == 0) {
- if (name[5] == '-'
- && strlen(name) >= 11
- && ascii_isdigit(name[6])
- && ascii_isdigit(name[8])
- && ascii_isdigit(name[10])) {
- int major = atoi(name + 6);
- int minor = atoi(name + 8);
-
- // Expect "patch-9.9.01234".
- n = (major < VIM_VERSION_MAJOR
- || (major == VIM_VERSION_MAJOR
- && (minor < VIM_VERSION_MINOR
- || (minor == VIM_VERSION_MINOR
- && has_vim_patch(atoi(name + 10))))));
- } else {
- n = has_vim_patch(atoi(name + 5));
- }
- } else if (STRNICMP(name, "nvim-", 5) == 0) {
- // Expect "nvim-x.y.z"
- n = has_nvim_version(name + 5);
- } else if (STRICMP(name, "vim_starting") == 0) {
- n = (starting != 0);
- } else if (STRICMP(name, "ttyin") == 0) {
- n = stdin_isatty;
- } else if (STRICMP(name, "ttyout") == 0) {
- n = stdout_isatty;
- } else if (STRICMP(name, "multi_byte_encoding") == 0) {
- n = has_mbyte != 0;
- } else if (STRICMP(name, "syntax_items") == 0) {
- n = syntax_present(curwin);
-#ifdef UNIX
- } else if (STRICMP(name, "unnamedplus") == 0) {
- n = eval_has_provider("clipboard");
-#endif
- }
- }
-
- if (!n && eval_has_provider(name)) {
- n = true;
- }
-
- rettv->vval.v_number = n;
-}
-
-/*
- * "has_key()" function
- */
-static void f_has_key(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (argvars[0].v_type != VAR_DICT) {
- EMSG(_(e_dictreq));
- return;
- }
- if (argvars[0].vval.v_dict == NULL)
- return;
-
- rettv->vval.v_number = tv_dict_find(argvars[0].vval.v_dict,
- tv_get_string(&argvars[1]),
- -1) != NULL;
-}
-
-/// `haslocaldir([{win}[, {tab}]])` function
-///
-/// Returns `1` if the scope object has a local directory, `0` otherwise. If a
-/// scope object is not specified the current one is implied. This function
-/// share a lot of code with `f_getcwd`.
-///
-/// @pre The arguments must be of type number.
-/// @pre There may not be more than two arguments.
-/// @pre An argument may not be -1 if preceding arguments are not all -1.
-///
-/// @post The return value will be either the number `1` or `0`.
-static void f_haslocaldir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- // Possible scope of working directory to return.
- CdScope scope = kCdScopeInvalid;
-
- // Numbers of the scope objects (window, tab) we want the working directory
- // of. A `-1` means to skip this scope, a `0` means the current object.
- int scope_number[] = {
- [kCdScopeWindow] = 0, // Number of window to look at.
- [kCdScopeTab ] = 0, // Number of tab to look at.
- };
-
- tabpage_T *tp = curtab; // The tabpage to look at.
- win_T *win = curwin; // The window to look at.
-
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = 0;
-
- // Pre-conditions and scope extraction together
- for (int i = MIN_CD_SCOPE; i < MAX_CD_SCOPE; i++) {
- if (argvars[i].v_type == VAR_UNKNOWN) {
- break;
- }
- if (argvars[i].v_type != VAR_NUMBER) {
- EMSG(_(e_invarg));
- return;
- }
- scope_number[i] = argvars[i].vval.v_number;
- if (scope_number[i] < -1) {
- EMSG(_(e_invarg));
- return;
- }
- // Use the narrowest scope the user requested
- if (scope_number[i] >= 0 && scope == kCdScopeInvalid) {
- // The scope is the current iteration step.
- scope = i;
- } else if (scope_number[i] < 0) {
- scope = i + 1;
- }
- }
-
- // If the user didn't specify anything, default to window scope
- if (scope == kCdScopeInvalid) {
- scope = MIN_CD_SCOPE;
- }
-
- // Find the tabpage by number
- if (scope_number[kCdScopeTab] > 0) {
- tp = find_tabpage(scope_number[kCdScopeTab]);
- if (!tp) {
- EMSG(_("E5000: Cannot find tab number."));
- return;
- }
- }
-
- // Find the window in `tp` by number, `NULL` if none.
- if (scope_number[kCdScopeWindow] >= 0) {
- if (scope_number[kCdScopeTab] < 0) {
- EMSG(_("E5001: Higher scope cannot be -1 if lower scope is >= 0."));
- return;
- }
-
- if (scope_number[kCdScopeWindow] > 0) {
- win = find_win_by_nr(&argvars[0], tp);
- if (!win) {
- EMSG(_("E5002: Cannot find window number."));
- return;
- }
- }
- }
-
- switch (scope) {
- case kCdScopeWindow:
- assert(win);
- rettv->vval.v_number = win->w_localdir ? 1 : 0;
- break;
- case kCdScopeTab:
- assert(tp);
- rettv->vval.v_number = tp->tp_localdir ? 1 : 0;
- break;
- case kCdScopeGlobal:
- // The global scope never has a local directory
- break;
- case kCdScopeInvalid:
- // We should never get here
- assert(false);
- }
-}
-
-/*
- * "hasmapto()" function
- */
-static void f_hasmapto(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *mode;
- const char *const name = tv_get_string(&argvars[0]);
- bool abbr = false;
- char buf[NUMBUFLEN];
- if (argvars[1].v_type == VAR_UNKNOWN) {
- mode = "nvo";
- } else {
- mode = tv_get_string_buf(&argvars[1], buf);
- if (argvars[2].v_type != VAR_UNKNOWN) {
- abbr = tv_get_number(&argvars[2]);
- }
- }
-
- if (map_to_exists(name, mode, abbr)) {
- rettv->vval.v_number = true;
- } else {
- rettv->vval.v_number = false;
- }
-}
-
-/*
- * "histadd()" function
- */
-static void f_histadd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- HistoryType histype;
-
- rettv->vval.v_number = false;
- if (check_restricted() || check_secure()) {
- return;
- }
- const char *str = tv_get_string_chk(&argvars[0]); // NULL on type error
- histype = str != NULL ? get_histtype(str, strlen(str), false) : HIST_INVALID;
- if (histype != HIST_INVALID) {
- char buf[NUMBUFLEN];
- str = tv_get_string_buf(&argvars[1], buf);
- if (*str != NUL) {
- init_history();
- add_to_history(histype, (char_u *)str, false, NUL);
- rettv->vval.v_number = true;
- return;
- }
- }
-}
-
-/*
- * "histdel()" function
- */
-static void f_histdel(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int n;
- const char *const str = tv_get_string_chk(&argvars[0]); // NULL on type error
- if (str == NULL) {
- n = 0;
- } else if (argvars[1].v_type == VAR_UNKNOWN) {
- // only one argument: clear entire history
- n = clr_history(get_histtype(str, strlen(str), false));
- } else if (argvars[1].v_type == VAR_NUMBER) {
- // index given: remove that entry
- n = del_history_idx(get_histtype(str, strlen(str), false),
- (int)tv_get_number(&argvars[1]));
- } else {
- // string given: remove all matching entries
- char buf[NUMBUFLEN];
- n = del_history_entry(get_histtype(str, strlen(str), false),
- (char_u *)tv_get_string_buf(&argvars[1], buf));
- }
- rettv->vval.v_number = n;
-}
-
-/*
- * "histget()" function
- */
-static void f_histget(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- HistoryType type;
- int idx;
-
- const char *const str = tv_get_string_chk(&argvars[0]); // NULL on type error
- if (str == NULL) {
- rettv->vval.v_string = NULL;
- } else {
- type = get_histtype(str, strlen(str), false);
- if (argvars[1].v_type == VAR_UNKNOWN) {
- idx = get_history_idx(type);
- } else {
- idx = (int)tv_get_number_chk(&argvars[1], NULL);
- }
- // -1 on type error
- rettv->vval.v_string = vim_strsave(get_history_entry(type, idx));
- }
- rettv->v_type = VAR_STRING;
-}
-
-/*
- * "histnr()" function
- */
-static void f_histnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *const history = tv_get_string_chk(&argvars[0]);
- HistoryType i = history == NULL
- ? HIST_INVALID
- : get_histtype(history, strlen(history), false);
- if (i != HIST_INVALID) {
- i = get_history_idx(i);
- }
- rettv->vval.v_number = i;
-}
-
-/*
- * "highlightID(name)" function
- */
-static void f_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = syn_name2id(
- (const char_u *)tv_get_string(&argvars[0]));
-}
-
-/*
- * "highlight_exists()" function
- */
-static void f_hlexists(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = highlight_exists(
- (const char_u *)tv_get_string(&argvars[0]));
-}
-
-/*
- * "hostname()" function
- */
-static void f_hostname(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char hostname[256];
-
- os_get_hostname(hostname, 256);
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = vim_strsave((char_u *)hostname);
-}
-
-/*
- * iconv() function
- */
-static void f_iconv(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- vimconv_T vimconv;
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
-
- const char *const str = tv_get_string(&argvars[0]);
- char buf1[NUMBUFLEN];
- char_u *const from = enc_canonize(enc_skip(
- (char_u *)tv_get_string_buf(&argvars[1], buf1)));
- char buf2[NUMBUFLEN];
- char_u *const to = enc_canonize(enc_skip(
- (char_u *)tv_get_string_buf(&argvars[2], buf2)));
- vimconv.vc_type = CONV_NONE;
- convert_setup(&vimconv, from, to);
-
- // If the encodings are equal, no conversion needed.
- if (vimconv.vc_type == CONV_NONE) {
- rettv->vval.v_string = (char_u *)xstrdup(str);
- } else {
- rettv->vval.v_string = string_convert(&vimconv, (char_u *)str, NULL);
- }
-
- convert_setup(&vimconv, NULL, NULL);
- xfree(from);
- xfree(to);
-}
-
-/*
- * "indent()" function
- */
-static void f_indent(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const linenr_T lnum = tv_get_lnum(argvars);
- if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) {
- rettv->vval.v_number = get_indent_lnum(lnum);
- } else {
- rettv->vval.v_number = -1;
- }
-}
-
-/*
- * "index()" function
- */
-static void f_index(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- long idx = 0;
- bool ic = false;
-
- rettv->vval.v_number = -1;
- if (argvars[0].v_type != VAR_LIST) {
- EMSG(_(e_listreq));
- return;
- }
- list_T *const l = argvars[0].vval.v_list;
- if (l != NULL) {
- listitem_T *item = tv_list_first(l);
- if (argvars[2].v_type != VAR_UNKNOWN) {
- bool error = false;
-
- // Start at specified item.
- idx = tv_list_uidx(l, tv_get_number_chk(&argvars[2], &error));
- if (error || idx == -1) {
- item = NULL;
- } else {
- item = tv_list_find(l, idx);
- assert(item != NULL);
- }
- if (argvars[3].v_type != VAR_UNKNOWN) {
- ic = !!tv_get_number_chk(&argvars[3], &error);
- if (error) {
- item = NULL;
- }
- }
- }
-
- for (; item != NULL; item = TV_LIST_ITEM_NEXT(l, item), idx++) {
- if (tv_equal(TV_LIST_ITEM_TV(item), &argvars[1], ic, false)) {
- rettv->vval.v_number = idx;
- break;
- }
- }
- }
-}
-
-static int inputsecret_flag = 0;
-
-/*
* This function is used by f_input() and f_inputdialog() functions. The third
* argument to f_input() specifies the type of completion to use at the
* prompt. The third argument to f_inputdialog() specifies the value to return
* when the user cancels the prompt.
*/
void get_user_input(const typval_T *const argvars,
- typval_T *const rettv, const bool inputdialog)
+ typval_T *const rettv,
+ const bool inputdialog,
+ const bool secret)
FUNC_ATTR_NONNULL_ALL
{
rettv->v_type = VAR_STRING;
@@ -12155,7 +6574,7 @@ void get_user_input(const typval_T *const argvars,
const int save_ex_normal_busy = ex_normal_busy;
ex_normal_busy = 0;
rettv->vval.v_string =
- (char_u *)getcmdline_prompt(inputsecret_flag ? NUL : '@', p, echo_attr,
+ (char_u *)getcmdline_prompt(secret ? NUL : '@', p, echo_attr,
xp_type, xp_arg, input_callback);
ex_normal_busy = save_ex_normal_busy;
callback_free(&input_callback);
@@ -12172,212 +6591,14 @@ void get_user_input(const typval_T *const argvars,
cmd_silent = cmd_silent_save;
}
-/*
- * "input()" function
- * Also handles inputsecret() when inputsecret is set.
- */
-static void f_input(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- get_user_input(argvars, rettv, FALSE);
-}
-
-/*
- * "inputdialog()" function
- */
-static void f_inputdialog(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- get_user_input(argvars, rettv, TRUE);
-}
-
-/*
- * "inputlist()" function
- */
-static void f_inputlist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int selected;
- int mouse_used;
-
- if (argvars[0].v_type != VAR_LIST) {
- EMSG2(_(e_listarg), "inputlist()");
- return;
- }
-
- msg_start();
- msg_row = Rows - 1; /* for when 'cmdheight' > 1 */
- lines_left = Rows; /* avoid more prompt */
- msg_scroll = TRUE;
- msg_clr_eos();
-
- TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, {
- msg_puts(tv_get_string(TV_LIST_ITEM_TV(li)));
- msg_putchar('\n');
- });
-
- // Ask for choice.
- selected = prompt_for_number(&mouse_used);
- if (mouse_used) {
- selected -= lines_left;
- }
-
- rettv->vval.v_number = selected;
-}
-
-
-static garray_T ga_userinput = {0, 0, sizeof(tasave_T), 4, NULL};
-
-/// "inputrestore()" function
-static void f_inputrestore(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (!GA_EMPTY(&ga_userinput)) {
- ga_userinput.ga_len--;
- restore_typeahead((tasave_T *)(ga_userinput.ga_data)
- + ga_userinput.ga_len);
- // default return is zero == OK
- } else if (p_verbose > 1) {
- verb_msg(_("called inputrestore() more often than inputsave()"));
- rettv->vval.v_number = 1; // Failed
- }
-}
-
-/// "inputsave()" function
-static void f_inputsave(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- // Add an entry to the stack of typeahead storage.
- tasave_T *p = GA_APPEND_VIA_PTR(tasave_T, &ga_userinput);
- save_typeahead(p);
-}
-
-/// "inputsecret()" function
-static void f_inputsecret(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- cmdline_star++;
- inputsecret_flag++;
- f_input(argvars, rettv, NULL);
- cmdline_star--;
- inputsecret_flag--;
-}
-
-/*
- * "insert()" function
- */
-static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- list_T *l;
- bool error = false;
-
- if (argvars[0].v_type != VAR_LIST) {
- EMSG2(_(e_listarg), "insert()");
- } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)),
- N_("insert() argument"), TV_TRANSLATE)) {
- long before = 0;
- if (argvars[2].v_type != VAR_UNKNOWN) {
- before = tv_get_number_chk(&argvars[2], &error);
- }
- if (error) {
- // type error; errmsg already given
- return;
- }
-
- listitem_T *item = NULL;
- if (before != tv_list_len(l)) {
- item = tv_list_find(l, before);
- if (item == NULL) {
- EMSGN(_(e_listidx), before);
- l = NULL;
- }
- }
- if (l != NULL) {
- tv_list_insert_tv(l, &argvars[1], item);
- tv_copy(&argvars[0], rettv);
- }
- }
-}
-
-/*
- * "invert(expr)" function
- */
-static void f_invert(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = ~tv_get_number_chk(&argvars[0], NULL);
-}
-
-/*
- * "isdirectory()" function
- */
-static void f_isdirectory(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = os_isdir((const char_u *)tv_get_string(&argvars[0]));
-}
-
-/*
- * "islocked()" function
- */
-static void f_islocked(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- lval_T lv;
- dictitem_T *di;
-
- rettv->vval.v_number = -1;
- const char_u *const end = get_lval((char_u *)tv_get_string(&argvars[0]),
- NULL,
- &lv, false, false,
- GLV_NO_AUTOLOAD|GLV_READ_ONLY,
- FNE_CHECK_START);
- if (end != NULL && lv.ll_name != NULL) {
- if (*end != NUL) {
- EMSG(_(e_trailing));
- } else {
- if (lv.ll_tv == NULL) {
- di = find_var((const char *)lv.ll_name, lv.ll_name_len, NULL, true);
- if (di != NULL) {
- // Consider a variable locked when:
- // 1. the variable itself is locked
- // 2. the value of the variable is locked.
- // 3. the List or Dict value is locked.
- rettv->vval.v_number = ((di->di_flags & DI_FLAGS_LOCK)
- || tv_islocked(&di->di_tv));
- }
- } else if (lv.ll_range) {
- EMSG(_("E786: Range not allowed"));
- } else if (lv.ll_newkey != NULL) {
- EMSG2(_(e_dictkey), lv.ll_newkey);
- } else if (lv.ll_list != NULL) {
- // List item.
- rettv->vval.v_number = tv_islocked(TV_LIST_ITEM_TV(lv.ll_li));
- } else {
- // Dictionary item.
- rettv->vval.v_number = tv_islocked(&lv.ll_di->di_tv);
- }
- }
- }
-
- clear_lval(&lv);
-}
-
-// "isinf()" function
-static void f_isinf(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (argvars[0].v_type == VAR_FLOAT
- && xisinf(argvars[0].vval.v_float)) {
- rettv->vval.v_number = argvars[0].vval.v_float > 0.0 ? 1 : -1;
- }
-}
-
-// "isnan()" function
-static void f_isnan(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = argvars[0].v_type == VAR_FLOAT
- && xisnan(argvars[0].vval.v_float);
-}
-
/// Turn a dictionary into a list
///
/// @param[in] tv Dictionary to convert. Is checked for actually being
/// a dictionary, will give an error if not.
/// @param[out] rettv Location where result will be saved.
/// @param[in] what What to save in rettv.
-static void dict_list(typval_T *const tv, typval_T *const rettv,
- const DictListType what)
+void dict_list(typval_T *const tv, typval_T *const rettv,
+ const DictListType what)
{
if (tv->v_type != VAR_DICT) {
EMSG(_(e_dictreq));
@@ -12425,82 +6646,6 @@ static void dict_list(typval_T *const tv, typval_T *const rettv,
});
}
-/// "id()" function
-static void f_id(typval_T *argvars, typval_T *rettv, FunPtr fptr)
- FUNC_ATTR_NONNULL_ALL
-{
- const int len = vim_vsnprintf_typval(NULL, 0, "%p", dummy_ap, argvars);
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = xmalloc(len + 1);
- vim_vsnprintf_typval((char *)rettv->vval.v_string, len + 1, "%p",
- dummy_ap, argvars);
-}
-
-/*
- * "items(dict)" function
- */
-static void f_items(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- dict_list(argvars, rettv, 2);
-}
-
-// "jobpid(id)" function
-static void f_jobpid(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = 0;
-
- if (check_restricted() || check_secure()) {
- return;
- }
-
- if (argvars[0].v_type != VAR_NUMBER) {
- EMSG(_(e_invarg));
- return;
- }
-
- Channel *data = find_job(argvars[0].vval.v_number, true);
- if (!data) {
- return;
- }
-
- Process *proc = (Process *)&data->stream.proc;
- rettv->vval.v_number = proc->pid;
-}
-
-// "jobresize(job, width, height)" function
-static void f_jobresize(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = 0;
-
- if (check_restricted() || check_secure()) {
- return;
- }
-
- if (argvars[0].v_type != VAR_NUMBER || argvars[1].v_type != VAR_NUMBER
- || argvars[2].v_type != VAR_NUMBER) {
- // job id, width, height
- EMSG(_(e_invarg));
- return;
- }
-
-
- Channel *data = find_job(argvars[0].vval.v_number, true);
- if (!data) {
- return;
- }
-
- if (data->stream.proc.type != kProcessTypePty) {
- EMSG(_(e_channotpty));
- return;
- }
-
- pty_process_resize(&data->stream.pty, argvars[1].vval.v_number,
- argvars[2].vval.v_number);
- rettv->vval.v_number = 1;
-}
-
/// Builds a process argument vector from a VimL object (typval_T).
///
/// @param[in] cmd_tv VimL object
@@ -12510,7 +6655,7 @@ static void f_jobresize(typval_T *argvars, typval_T *rettv, FunPtr fptr)
/// @returns Result of `shell_build_argv()` if `cmd_tv` is a String.
/// Else, string values of `cmd_tv` copied to a (char **) list with
/// argv[0] resolved to full path ($PATHEXT-resolved on Windows).
-static char **tv_to_argv(typval_T *cmd_tv, const char **cmd, bool *executable)
+char **tv_to_argv(typval_T *cmd_tv, const char **cmd, bool *executable)
{
if (cmd_tv->v_type == VAR_STRING) { // String => "shell semantics".
const char *cmd_str = tv_get_string(cmd_tv);
@@ -12569,568 +6714,6 @@ static char **tv_to_argv(typval_T *cmd_tv, const char **cmd, bool *executable)
return argv;
}
-// "jobstart()" function
-static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = 0;
-
- if (check_restricted() || check_secure()) {
- return;
- }
-
- bool executable = true;
- char **argv = tv_to_argv(&argvars[0], NULL, &executable);
- char **env = NULL;
- if (!argv) {
- rettv->vval.v_number = executable ? 0 : -1;
- return; // Did error message in tv_to_argv.
- }
-
- if (argvars[1].v_type != VAR_DICT && argvars[1].v_type != VAR_UNKNOWN) {
- // Wrong argument types
- EMSG2(_(e_invarg2), "expected dictionary");
- shell_free_argv(argv);
- return;
- }
-
-
- dict_T *job_opts = NULL;
- bool detach = false;
- bool rpc = false;
- bool pty = false;
- bool clear_env = false;
- CallbackReader on_stdout = CALLBACK_READER_INIT,
- on_stderr = CALLBACK_READER_INIT;
- Callback on_exit = CALLBACK_NONE;
- char *cwd = NULL;
- if (argvars[1].v_type == VAR_DICT) {
- job_opts = argvars[1].vval.v_dict;
-
- detach = tv_dict_get_number(job_opts, "detach") != 0;
- rpc = tv_dict_get_number(job_opts, "rpc") != 0;
- pty = tv_dict_get_number(job_opts, "pty") != 0;
- clear_env = tv_dict_get_number(job_opts, "clear_env") != 0;
- if (pty && rpc) {
- EMSG2(_(e_invarg2), "job cannot have both 'pty' and 'rpc' options set");
- shell_free_argv(argv);
- return;
- }
-
- char *new_cwd = tv_dict_get_string(job_opts, "cwd", false);
- if (new_cwd && strlen(new_cwd) > 0) {
- cwd = new_cwd;
- // The new cwd must be a directory.
- if (!os_isdir_executable((const char *)cwd)) {
- EMSG2(_(e_invarg2), "expected valid directory");
- shell_free_argv(argv);
- return;
- }
- }
- dictitem_T *job_env = tv_dict_find(job_opts, S_LEN("env"));
- if (job_env) {
- if (job_env->di_tv.v_type != VAR_DICT) {
- EMSG2(_(e_invarg2), "env");
- shell_free_argv(argv);
- return;
- }
-
- size_t custom_env_size = (size_t)tv_dict_len(job_env->di_tv.vval.v_dict);
- size_t i = 0;
- size_t env_size = 0;
-
- if (clear_env) {
- // + 1 for last null entry
- env = xmalloc((custom_env_size + 1) * sizeof(*env));
- env_size = 0;
- } else {
- env_size = os_get_fullenv_size();
-
- env = xmalloc((custom_env_size + env_size + 1) * sizeof(*env));
-
- os_copy_fullenv(env, env_size);
- i = env_size;
- }
- assert(env); // env must be allocated at this point
-
- TV_DICT_ITER(job_env->di_tv.vval.v_dict, var, {
- const char *str = tv_get_string(&var->di_tv);
- assert(str);
- size_t len = STRLEN(var->di_key) + strlen(str) + strlen("=") + 1;
- env[i] = xmalloc(len);
- snprintf(env[i], len, "%s=%s", (char *)var->di_key, str);
- i++;
- });
-
- // must be null terminated
- env[env_size + custom_env_size] = NULL;
- }
-
-
- if (!common_job_callbacks(job_opts, &on_stdout, &on_stderr, &on_exit)) {
- shell_free_argv(argv);
- return;
- }
- }
-
- uint16_t width = 0, height = 0;
- char *term_name = NULL;
-
- if (pty) {
- width = (uint16_t)tv_dict_get_number(job_opts, "width");
- height = (uint16_t)tv_dict_get_number(job_opts, "height");
- term_name = tv_dict_get_string(job_opts, "TERM", true);
- }
-
- Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit, pty,
- rpc, detach, cwd, width, height,
- term_name, env, &rettv->vval.v_number);
- if (chan) {
- channel_create_event(chan, NULL);
- }
-}
-
-// "jobstop()" function
-static void f_jobstop(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = 0;
-
- if (check_restricted() || check_secure()) {
- return;
- }
-
- if (argvars[0].v_type != VAR_NUMBER) {
- // Only argument is the job id
- EMSG(_(e_invarg));
- return;
- }
-
- Channel *data = find_job(argvars[0].vval.v_number, false);
- if (!data) {
- return;
- }
-
- const char *error = NULL;
- if (data->is_rpc) {
- // Ignore return code, but show error later.
- (void)channel_close(data->id, kChannelPartRpc, &error);
- }
- process_stop((Process *)&data->stream.proc);
- rettv->vval.v_number = 1;
- if (error) {
- EMSG(error);
- }
-}
-
-// "jobwait(ids[, timeout])" function
-static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = 0;
-
- if (check_restricted() || check_secure()) {
- return;
- }
- if (argvars[0].v_type != VAR_LIST || (argvars[1].v_type != VAR_NUMBER
- && argvars[1].v_type != VAR_UNKNOWN)) {
- EMSG(_(e_invarg));
- return;
- }
-
- ui_busy_start();
- list_T *args = argvars[0].vval.v_list;
- Channel **jobs = xcalloc(tv_list_len(args), sizeof(*jobs));
- MultiQueue *waiting_jobs = multiqueue_new_parent(loop_on_put, &main_loop);
-
- // Validate, prepare jobs for waiting.
- int i = 0;
- TV_LIST_ITER_CONST(args, arg, {
- Channel *chan = NULL;
- if (TV_LIST_ITEM_TV(arg)->v_type != VAR_NUMBER
- || !(chan = find_job(TV_LIST_ITEM_TV(arg)->vval.v_number, false))) {
- jobs[i] = NULL; // Invalid job.
- } else {
- jobs[i] = chan;
- channel_incref(chan);
- if (chan->stream.proc.status < 0) {
- // Process any pending events on the job's queue before temporarily
- // replacing it.
- multiqueue_process_events(chan->events);
- multiqueue_replace_parent(chan->events, waiting_jobs);
- }
- }
- i++;
- });
-
- int remaining = -1;
- uint64_t before = 0;
- if (argvars[1].v_type == VAR_NUMBER && argvars[1].vval.v_number >= 0) {
- remaining = argvars[1].vval.v_number;
- before = os_hrtime();
- }
-
- for (i = 0; i < tv_list_len(args); i++) {
- if (remaining == 0) {
- break; // Timeout.
- }
- if (jobs[i] == NULL) {
- continue; // Invalid job, will assign status=-3 below.
- }
- int status = process_wait(&jobs[i]->stream.proc, remaining,
- waiting_jobs);
- if (status < 0) {
- break; // Interrupted (CTRL-C) or timeout, skip remaining jobs.
- }
- if (remaining > 0) {
- uint64_t now = os_hrtime();
- remaining = MIN(0, remaining - (int)((now - before) / 1000000));
- before = now;
- }
- }
-
- list_T *const rv = tv_list_alloc(tv_list_len(args));
-
- // For each job:
- // * Restore its parent queue if the job is still alive.
- // * Append its status to the output list, or:
- // -3 for "invalid job id"
- // -2 for "interrupted" (user hit CTRL-C)
- // -1 for jobs that were skipped or timed out
- for (i = 0; i < tv_list_len(args); i++) {
- if (jobs[i] == NULL) {
- tv_list_append_number(rv, -3);
- continue;
- }
- multiqueue_process_events(jobs[i]->events);
- multiqueue_replace_parent(jobs[i]->events, main_loop.events);
-
- tv_list_append_number(rv, jobs[i]->stream.proc.status);
- channel_decref(jobs[i]);
- }
-
- multiqueue_free(waiting_jobs);
- xfree(jobs);
- ui_busy_stop();
- tv_list_ref(rv);
- rettv->v_type = VAR_LIST;
- rettv->vval.v_list = rv;
-}
-
-/*
- * "join()" function
- */
-static void f_join(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (argvars[0].v_type != VAR_LIST) {
- EMSG(_(e_listreq));
- return;
- }
- const char *const sep = (argvars[1].v_type == VAR_UNKNOWN
- ? " "
- : tv_get_string_chk(&argvars[1]));
-
- rettv->v_type = VAR_STRING;
-
- if (sep != NULL) {
- garray_T ga;
- ga_init(&ga, (int)sizeof(char), 80);
- tv_list_join(&ga, argvars[0].vval.v_list, sep);
- ga_append(&ga, NUL);
- rettv->vval.v_string = (char_u *)ga.ga_data;
- } else {
- rettv->vval.v_string = NULL;
- }
-}
-
-/// json_decode() function
-static void f_json_decode(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char numbuf[NUMBUFLEN];
- const char *s = NULL;
- char *tofree = NULL;
- size_t len;
- if (argvars[0].v_type == VAR_LIST) {
- if (!encode_vim_list_to_buf(argvars[0].vval.v_list, &len, &tofree)) {
- EMSG(_("E474: Failed to convert list to string"));
- return;
- }
- s = tofree;
- if (s == NULL) {
- assert(len == 0);
- s = "";
- }
- } else {
- s = tv_get_string_buf_chk(&argvars[0], numbuf);
- if (s) {
- len = strlen(s);
- } else {
- return;
- }
- }
- if (json_decode_string(s, len, rettv) == FAIL) {
- emsgf(_("E474: Failed to parse %.*s"), (int)len, s);
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = 0;
- }
- assert(rettv->v_type != VAR_UNKNOWN);
- xfree(tofree);
-}
-
-/// json_encode() function
-static void f_json_encode(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = (char_u *) encode_tv2json(&argvars[0], NULL);
-}
-
-/*
- * "keys()" function
- */
-static void f_keys(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- dict_list(argvars, rettv, 0);
-}
-
-/*
- * "last_buffer_nr()" function.
- */
-static void f_last_buffer_nr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int n = 0;
-
- FOR_ALL_BUFFERS(buf) {
- if (n < buf->b_fnum) {
- n = buf->b_fnum;
- }
- }
-
- rettv->vval.v_number = n;
-}
-
-/*
- * "len()" function
- */
-static void f_len(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- switch (argvars[0].v_type) {
- case VAR_STRING:
- case VAR_NUMBER: {
- rettv->vval.v_number = (varnumber_T)strlen(
- tv_get_string(&argvars[0]));
- break;
- }
- case VAR_LIST: {
- rettv->vval.v_number = tv_list_len(argvars[0].vval.v_list);
- break;
- }
- case VAR_DICT: {
- rettv->vval.v_number = tv_dict_len(argvars[0].vval.v_dict);
- break;
- }
- case VAR_UNKNOWN:
- case VAR_SPECIAL:
- case VAR_FLOAT:
- case VAR_PARTIAL:
- case VAR_FUNC: {
- EMSG(_("E701: Invalid type for len()"));
- break;
- }
- }
-}
-
-static void libcall_common(typval_T *argvars, typval_T *rettv, int out_type)
-{
- rettv->v_type = out_type;
- if (out_type != VAR_NUMBER) {
- rettv->vval.v_string = NULL;
- }
-
- if (check_restricted() || check_secure()) {
- return;
- }
-
- // The first two args (libname and funcname) must be strings
- if (argvars[0].v_type != VAR_STRING || argvars[1].v_type != VAR_STRING) {
- return;
- }
-
- const char *libname = (char *) argvars[0].vval.v_string;
- const char *funcname = (char *) argvars[1].vval.v_string;
-
- VarType in_type = argvars[2].v_type;
-
- // input variables
- char *str_in = (in_type == VAR_STRING)
- ? (char *) argvars[2].vval.v_string : NULL;
- int64_t int_in = argvars[2].vval.v_number;
-
- // output variables
- char **str_out = (out_type == VAR_STRING)
- ? (char **)&rettv->vval.v_string : NULL;
- int int_out = 0;
-
- bool success = os_libcall(libname, funcname,
- str_in, int_in,
- str_out, &int_out);
-
- if (!success) {
- EMSG2(_(e_libcall), funcname);
- return;
- }
-
- if (out_type == VAR_NUMBER) {
- rettv->vval.v_number = (varnumber_T)int_out;
- }
-}
-
-/*
- * "libcall()" function
- */
-static void f_libcall(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- libcall_common(argvars, rettv, VAR_STRING);
-}
-
-/*
- * "libcallnr()" function
- */
-static void f_libcallnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- libcall_common(argvars, rettv, VAR_NUMBER);
-}
-
-/*
- * "line(string)" function
- */
-static void f_line(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- linenr_T lnum = 0;
- pos_T *fp;
- int fnum;
-
- fp = var2fpos(&argvars[0], TRUE, &fnum);
- if (fp != NULL)
- lnum = fp->lnum;
- rettv->vval.v_number = lnum;
-}
-
-/*
- * "line2byte(lnum)" function
- */
-static void f_line2byte(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const linenr_T lnum = tv_get_lnum(argvars);
- if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count + 1) {
- rettv->vval.v_number = -1;
- } else {
- rettv->vval.v_number = ml_find_line_or_offset(curbuf, lnum, NULL, false);
- }
- if (rettv->vval.v_number >= 0) {
- rettv->vval.v_number++;
- }
-}
-
-/*
- * "lispindent(lnum)" function
- */
-static void f_lispindent(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const pos_T pos = curwin->w_cursor;
- const linenr_T lnum = tv_get_lnum(argvars);
- if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) {
- curwin->w_cursor.lnum = lnum;
- rettv->vval.v_number = get_lisp_indent();
- curwin->w_cursor = pos;
- } else {
- rettv->vval.v_number = -1;
- }
-}
-
-/*
- * "localtime()" function
- */
-static void f_localtime(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = (varnumber_T)time(NULL);
-}
-
-
-static void get_maparg(typval_T *argvars, typval_T *rettv, int exact)
-{
- char_u *keys_buf = NULL;
- char_u *rhs;
- int mode;
- int abbr = FALSE;
- int get_dict = FALSE;
- mapblock_T *mp;
- int buffer_local;
-
- // Return empty string for failure.
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
-
- char_u *keys = (char_u *)tv_get_string(&argvars[0]);
- if (*keys == NUL) {
- return;
- }
-
- char buf[NUMBUFLEN];
- const char *which;
- if (argvars[1].v_type != VAR_UNKNOWN) {
- which = tv_get_string_buf_chk(&argvars[1], buf);
- if (argvars[2].v_type != VAR_UNKNOWN) {
- abbr = tv_get_number(&argvars[2]);
- if (argvars[3].v_type != VAR_UNKNOWN) {
- get_dict = tv_get_number(&argvars[3]);
- }
- }
- } else {
- which = "";
- }
- if (which == NULL) {
- return;
- }
-
- mode = get_map_mode((char_u **)&which, 0);
-
- keys = replace_termcodes(keys, STRLEN(keys), &keys_buf, true, true, true,
- CPO_TO_CPO_FLAGS);
- rhs = check_map(keys, mode, exact, false, abbr, &mp, &buffer_local);
- xfree(keys_buf);
-
- if (!get_dict) {
- // Return a string.
- if (rhs != NULL) {
- if (*rhs == NUL) {
- rettv->vval.v_string = vim_strsave((char_u *)"<Nop>");
- } else {
- rettv->vval.v_string = (char_u *)str2special_save(
- (char *)rhs, false, false);
- }
- }
-
- } else {
- tv_dict_alloc_ret(rettv);
- if (rhs != NULL) {
- // Return a dictionary.
- mapblock_fill_dict(rettv->vval.v_dict, mp, buffer_local, true);
- }
- }
-}
-
-/// luaeval() function implementation
-static void f_luaeval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
- FUNC_ATTR_NONNULL_ALL
-{
- const char *const str = (const char *)tv_get_string_chk(&argvars[0]);
- if (str == NULL) {
- return;
- }
-
- executor_eval_lua(cstr_as_string((char *)str), &argvars[1], rettv);
-}
-
/// Fill a dictionary with all applicable maparg() like dictionaries
///
/// @param dict The dictionary to be filled
@@ -13176,260 +6759,8 @@ void mapblock_fill_dict(dict_T *const dict,
tv_dict_add_allocated_str(dict, S_LEN("mode"), mapmode);
}
-/*
- * "map()" function
- */
-static void f_map(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- filter_map(argvars, rettv, TRUE);
-}
-
-/*
- * "maparg()" function
- */
-static void f_maparg(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- get_maparg(argvars, rettv, TRUE);
-}
-
-/*
- * "mapcheck()" function
- */
-static void f_mapcheck(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- get_maparg(argvars, rettv, FALSE);
-}
-
-
-static void find_some_match(typval_T *const argvars, typval_T *const rettv,
- const SomeMatchType type)
-{
- char_u *str = NULL;
- long len = 0;
- char_u *expr = NULL;
- regmatch_T regmatch;
- char_u *save_cpo;
- long start = 0;
- long nth = 1;
- colnr_T startcol = 0;
- bool match = false;
- list_T *l = NULL;
- listitem_T *li = NULL;
- long idx = 0;
- char_u *tofree = NULL;
-
- /* Make 'cpoptions' empty, the 'l' flag should not be used here. */
- save_cpo = p_cpo;
- p_cpo = (char_u *)"";
-
- rettv->vval.v_number = -1;
- switch (type) {
- // matchlist(): return empty list when there are no matches.
- case kSomeMatchList: {
- tv_list_alloc_ret(rettv, kListLenMayKnow);
- break;
- }
- // matchstrpos(): return ["", -1, -1, -1]
- case kSomeMatchStrPos: {
- tv_list_alloc_ret(rettv, 4);
- tv_list_append_string(rettv->vval.v_list, "", 0);
- tv_list_append_number(rettv->vval.v_list, -1);
- tv_list_append_number(rettv->vval.v_list, -1);
- tv_list_append_number(rettv->vval.v_list, -1);
- break;
- }
- case kSomeMatchStr: {
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
- break;
- }
- case kSomeMatch:
- case kSomeMatchEnd: {
- // Do nothing: zero is default.
- break;
- }
- }
-
- if (argvars[0].v_type == VAR_LIST) {
- if ((l = argvars[0].vval.v_list) == NULL) {
- goto theend;
- }
- li = tv_list_first(l);
- } else {
- expr = str = (char_u *)tv_get_string(&argvars[0]);
- len = (long)STRLEN(str);
- }
-
- char patbuf[NUMBUFLEN];
- const char *const pat = tv_get_string_buf_chk(&argvars[1], patbuf);
- if (pat == NULL) {
- goto theend;
- }
-
- if (argvars[2].v_type != VAR_UNKNOWN) {
- bool error = false;
-
- start = tv_get_number_chk(&argvars[2], &error);
- if (error) {
- goto theend;
- }
- if (l != NULL) {
- idx = tv_list_uidx(l, start);
- if (idx == -1) {
- goto theend;
- }
- li = tv_list_find(l, idx);
- } else {
- if (start < 0)
- start = 0;
- if (start > len)
- goto theend;
- /* When "count" argument is there ignore matches before "start",
- * otherwise skip part of the string. Differs when pattern is "^"
- * or "\<". */
- if (argvars[3].v_type != VAR_UNKNOWN)
- startcol = start;
- else {
- str += start;
- len -= start;
- }
- }
-
- if (argvars[3].v_type != VAR_UNKNOWN) {
- nth = tv_get_number_chk(&argvars[3], &error);
- }
- if (error) {
- goto theend;
- }
- }
-
- regmatch.regprog = vim_regcomp((char_u *)pat, RE_MAGIC + RE_STRING);
- if (regmatch.regprog != NULL) {
- regmatch.rm_ic = p_ic;
-
- for (;; ) {
- if (l != NULL) {
- if (li == NULL) {
- match = false;
- break;
- }
- xfree(tofree);
- tofree = expr = str = (char_u *)encode_tv2echo(TV_LIST_ITEM_TV(li),
- NULL);
- if (str == NULL) {
- break;
- }
- }
-
- match = vim_regexec_nl(&regmatch, str, (colnr_T)startcol);
-
- if (match && --nth <= 0)
- break;
- if (l == NULL && !match)
- break;
-
- /* Advance to just after the match. */
- if (l != NULL) {
- li = TV_LIST_ITEM_NEXT(l, li);
- idx++;
- } else {
- startcol = (colnr_T)(regmatch.startp[0]
- + (*mb_ptr2len)(regmatch.startp[0]) - str);
- if (startcol > (colnr_T)len || str + startcol <= regmatch.startp[0]) {
- match = false;
- break;
- }
- }
- }
-
- if (match) {
- switch (type) {
- case kSomeMatchStrPos: {
- list_T *const ret_l = rettv->vval.v_list;
- listitem_T *li1 = tv_list_first(ret_l);
- listitem_T *li2 = TV_LIST_ITEM_NEXT(ret_l, li1);
- listitem_T *li3 = TV_LIST_ITEM_NEXT(ret_l, li2);
- listitem_T *li4 = TV_LIST_ITEM_NEXT(ret_l, li3);
- xfree(TV_LIST_ITEM_TV(li1)->vval.v_string);
-
- const size_t rd = (size_t)(regmatch.endp[0] - regmatch.startp[0]);
- TV_LIST_ITEM_TV(li1)->vval.v_string = xmemdupz(
- (const char *)regmatch.startp[0], rd);
- TV_LIST_ITEM_TV(li3)->vval.v_number = (varnumber_T)(
- regmatch.startp[0] - expr);
- TV_LIST_ITEM_TV(li4)->vval.v_number = (varnumber_T)(
- regmatch.endp[0] - expr);
- if (l != NULL) {
- TV_LIST_ITEM_TV(li2)->vval.v_number = (varnumber_T)idx;
- }
- break;
- }
- case kSomeMatchList: {
- // Return list with matched string and submatches.
- for (int i = 0; i < NSUBEXP; i++) {
- if (regmatch.endp[i] == NULL) {
- tv_list_append_string(rettv->vval.v_list, NULL, 0);
- } else {
- tv_list_append_string(rettv->vval.v_list,
- (const char *)regmatch.startp[i],
- (regmatch.endp[i] - regmatch.startp[i]));
- }
- }
- break;
- }
- case kSomeMatchStr: {
- // Return matched string.
- if (l != NULL) {
- tv_copy(TV_LIST_ITEM_TV(li), rettv);
- } else {
- rettv->vval.v_string = (char_u *)xmemdupz(
- (const char *)regmatch.startp[0],
- (size_t)(regmatch.endp[0] - regmatch.startp[0]));
- }
- break;
- }
- case kSomeMatch:
- case kSomeMatchEnd: {
- if (l != NULL) {
- rettv->vval.v_number = idx;
- } else {
- if (type == kSomeMatch) {
- rettv->vval.v_number =
- (varnumber_T)(regmatch.startp[0] - str);
- } else {
- rettv->vval.v_number =
- (varnumber_T)(regmatch.endp[0] - str);
- }
- rettv->vval.v_number += (varnumber_T)(str - expr);
- }
- break;
- }
- }
- }
- vim_regfree(regmatch.regprog);
- }
-
-theend:
- if (type == kSomeMatchStrPos && l == NULL && rettv->vval.v_list != NULL) {
- // matchstrpos() without a list: drop the second item
- list_T *const ret_l = rettv->vval.v_list;
- tv_list_item_remove(ret_l, TV_LIST_ITEM_NEXT(ret_l, tv_list_first(ret_l)));
- }
-
- xfree(tofree);
- p_cpo = save_cpo;
-}
-
-/*
- * "match()" function
- */
-static void f_match(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- find_some_match(argvars, rettv, kSomeMatch);
-}
-
-static int matchadd_dict_arg(typval_T *tv, const char **conceal_char,
- win_T **win)
+int matchadd_dict_arg(typval_T *tv, const char **conceal_char,
+ win_T **win)
{
dictitem_T *di;
@@ -13453,766 +6784,7 @@ static int matchadd_dict_arg(typval_T *tv, const char **conceal_char,
return OK;
}
-/*
- * "matchadd()" function
- */
-static void f_matchadd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char grpbuf[NUMBUFLEN];
- char patbuf[NUMBUFLEN];
- const char *const grp = tv_get_string_buf_chk(&argvars[0], grpbuf);
- const char *const pat = tv_get_string_buf_chk(&argvars[1], patbuf);
- 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 = tv_get_number_chk(&argvars[2], &error);
- if (argvars[3].v_type != VAR_UNKNOWN) {
- id = 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) {
- EMSGN(_("E798: ID is reserved for \":match\": %" PRId64), id);
- return;
- }
-
- rettv->vval.v_number = match_add(win, grp, pat, prio, id, NULL, conceal_char);
-}
-
-static 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) {
- EMSG2(_(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 = tv_get_number_chk(&argvars[2], &error);
- if (argvars[3].v_type != VAR_UNKNOWN) {
- id = 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) {
- EMSGN(_("E798: ID is reserved for \"match\": %" PRId64), id);
- return;
- }
-
- rettv->vval.v_number = match_add(win, group, NULL, prio, id, l, conceal_char);
-}
-
-/*
- * "matcharg()" function
- */
-static void f_matcharg(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const int id = 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 = (matchitem_T *)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
- */
-static void f_matchdelete(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = match_delete(curwin,
- (int)tv_get_number(&argvars[0]), true);
-}
-
-/*
- * "matchend()" function
- */
-static void f_matchend(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- find_some_match(argvars, rettv, kSomeMatchEnd);
-}
-
-/*
- * "matchlist()" function
- */
-static void f_matchlist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- find_some_match(argvars, rettv, kSomeMatchList);
-}
-
-/*
- * "matchstr()" function
- */
-static void f_matchstr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- find_some_match(argvars, rettv, kSomeMatchStr);
-}
-
-/// "matchstrpos()" function
-static void f_matchstrpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- find_some_match(argvars, rettv, kSomeMatchStrPos);
-}
-
-/// Get maximal/minimal number value in a list or dictionary
-///
-/// @param[in] tv List or dictionary to work with. If it contains something
-/// that is not an integer number (or cannot be coerced to
-/// it) error is given.
-/// @param[out] rettv Location where result will be saved. Only assigns
-/// vval.v_number, type is not touched. Returns zero for
-/// empty lists/dictionaries.
-/// @param[in] domax Determines whether maximal or minimal value is desired.
-static void max_min(const typval_T *const tv, typval_T *const rettv,
- const bool domax)
- FUNC_ATTR_NONNULL_ALL
-{
- bool error = false;
-
- rettv->vval.v_number = 0;
- varnumber_T n = (domax ? VARNUMBER_MIN : VARNUMBER_MAX);
- if (tv->v_type == VAR_LIST) {
- if (tv_list_len(tv->vval.v_list) == 0) {
- return;
- }
- TV_LIST_ITER_CONST(tv->vval.v_list, li, {
- const varnumber_T i = tv_get_number_chk(TV_LIST_ITEM_TV(li), &error);
- if (error) {
- return;
- }
- if (domax ? i > n : i < n) {
- n = i;
- }
- });
- } else if (tv->v_type == VAR_DICT) {
- if (tv_dict_len(tv->vval.v_dict) == 0) {
- return;
- }
- TV_DICT_ITER(tv->vval.v_dict, di, {
- const varnumber_T i = tv_get_number_chk(&di->di_tv, &error);
- if (error) {
- return;
- }
- if (domax ? i > n : i < n) {
- n = i;
- }
- });
- } else {
- EMSG2(_(e_listdictarg), domax ? "max()" : "min()");
- return;
- }
- rettv->vval.v_number = n;
-}
-
-/*
- * "max()" function
- */
-static void f_max(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- max_min(argvars, rettv, TRUE);
-}
-
-/*
- * "min()" function
- */
-static void f_min(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- max_min(argvars, rettv, FALSE);
-}
-
-/*
- * "mkdir()" function
- */
-static void f_mkdir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int prot = 0755; // -V536
-
- rettv->vval.v_number = FAIL;
- if (check_restricted() || check_secure())
- return;
-
- char buf[NUMBUFLEN];
- const char *const dir = tv_get_string_buf(&argvars[0], buf);
- if (*dir == NUL) {
- return;
- }
-
- if (*path_tail((char_u *)dir) == NUL) {
- // Remove trailing slashes.
- *path_tail_with_sep((char_u *)dir) = NUL;
- }
-
- if (argvars[1].v_type != VAR_UNKNOWN) {
- if (argvars[2].v_type != VAR_UNKNOWN) {
- prot = tv_get_number_chk(&argvars[2], NULL);
- if (prot == -1) {
- return;
- }
- }
- if (strcmp(tv_get_string(&argvars[1]), "p") == 0) {
- char *failed_dir;
- int ret = os_mkdir_recurse(dir, prot, &failed_dir);
- if (ret != 0) {
- EMSG3(_(e_mkdir), failed_dir, os_strerror(ret));
- xfree(failed_dir);
- rettv->vval.v_number = FAIL;
- return;
- } else {
- rettv->vval.v_number = OK;
- return;
- }
- }
- }
- rettv->vval.v_number = vim_mkdir_emsg(dir, prot);
-}
-
-/// "mode()" function
-static void f_mode(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char *mode = get_mode();
-
- // Clear out the minor mode when the argument is not a non-zero number or
- // non-empty string.
- if (!non_zero_arg(&argvars[0])) {
- mode[1] = NUL;
- }
-
- rettv->vval.v_string = (char_u *)mode;
- rettv->v_type = VAR_STRING;
-}
-
-/// "msgpackdump()" function
-static void f_msgpackdump(typval_T *argvars, typval_T *rettv, FunPtr fptr)
- FUNC_ATTR_NONNULL_ALL
-{
- if (argvars[0].v_type != VAR_LIST) {
- EMSG2(_(e_listarg), "msgpackdump()");
- return;
- }
- list_T *const ret_list = tv_list_alloc_ret(rettv, kListLenMayKnow);
- list_T *const list = argvars[0].vval.v_list;
- msgpack_packer *lpacker = msgpack_packer_new(ret_list, &encode_list_write);
- const char *const msg = _("msgpackdump() argument, index %i");
- // Assume that translation will not take more then 4 times more space
- char msgbuf[sizeof("msgpackdump() argument, index ") * 4 + NUMBUFLEN];
- int idx = 0;
- TV_LIST_ITER(list, li, {
- vim_snprintf(msgbuf, sizeof(msgbuf), (char *)msg, idx);
- idx++;
- if (encode_vim_to_msgpack(lpacker, TV_LIST_ITEM_TV(li), msgbuf) == FAIL) {
- break;
- }
- });
- msgpack_packer_free(lpacker);
-}
-
-/// "msgpackparse" function
-static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr)
- FUNC_ATTR_NONNULL_ALL
-{
- if (argvars[0].v_type != VAR_LIST) {
- EMSG2(_(e_listarg), "msgpackparse()");
- return;
- }
- list_T *const ret_list = tv_list_alloc_ret(rettv, kListLenMayKnow);
- const list_T *const list = argvars[0].vval.v_list;
- if (tv_list_len(list) == 0) {
- return;
- }
- if (TV_LIST_ITEM_TV(tv_list_first(list))->v_type != VAR_STRING) {
- EMSG2(_(e_invarg2), "List item is not a string");
- return;
- }
- ListReaderState lrstate = encode_init_lrstate(list);
- msgpack_unpacker *const unpacker = msgpack_unpacker_new(IOSIZE);
- if (unpacker == NULL) {
- EMSG(_(e_outofmem));
- return;
- }
- msgpack_unpacked unpacked;
- msgpack_unpacked_init(&unpacked);
- do {
- if (!msgpack_unpacker_reserve_buffer(unpacker, IOSIZE)) {
- EMSG(_(e_outofmem));
- goto f_msgpackparse_exit;
- }
- size_t read_bytes;
- const int rlret = encode_read_from_list(
- &lrstate, msgpack_unpacker_buffer(unpacker), IOSIZE, &read_bytes);
- if (rlret == FAIL) {
- EMSG2(_(e_invarg2), "List item is not a string");
- goto f_msgpackparse_exit;
- }
- msgpack_unpacker_buffer_consumed(unpacker, read_bytes);
- if (read_bytes == 0) {
- break;
- }
- while (unpacker->off < unpacker->used) {
- const msgpack_unpack_return result = msgpack_unpacker_next(unpacker,
- &unpacked);
- if (result == MSGPACK_UNPACK_PARSE_ERROR) {
- EMSG2(_(e_invarg2), "Failed to parse msgpack string");
- goto f_msgpackparse_exit;
- }
- if (result == MSGPACK_UNPACK_NOMEM_ERROR) {
- EMSG(_(e_outofmem));
- goto f_msgpackparse_exit;
- }
- if (result == MSGPACK_UNPACK_SUCCESS) {
- typval_T tv = { .v_type = VAR_UNKNOWN };
- if (msgpack_to_vim(unpacked.data, &tv) == FAIL) {
- EMSG2(_(e_invarg2), "Failed to convert msgpack string");
- goto f_msgpackparse_exit;
- }
- tv_list_append_owned_tv(ret_list, tv);
- }
- if (result == MSGPACK_UNPACK_CONTINUE) {
- if (rlret == OK) {
- EMSG2(_(e_invarg2), "Incomplete msgpack string");
- }
- break;
- }
- }
- if (rlret == OK) {
- break;
- }
- } while (true);
-
-f_msgpackparse_exit:
- msgpack_unpacked_destroy(&unpacked);
- msgpack_unpacker_free(unpacker);
- return;
-}
-
-/*
- * "nextnonblank()" function
- */
-static void f_nextnonblank(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- linenr_T lnum;
-
- for (lnum = tv_get_lnum(argvars);; lnum++) {
- if (lnum < 0 || lnum > curbuf->b_ml.ml_line_count) {
- lnum = 0;
- break;
- }
- if (*skipwhite(ml_get(lnum)) != NUL) {
- break;
- }
- }
- rettv->vval.v_number = lnum;
-}
-
-/*
- * "nr2char()" function
- */
-static void f_nr2char(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (argvars[1].v_type != VAR_UNKNOWN) {
- if (!tv_check_num(&argvars[1])) {
- return;
- }
- }
-
- bool error = false;
- const varnumber_T num = tv_get_number_chk(&argvars[0], &error);
- if (error) {
- return;
- }
- if (num < 0) {
- EMSG(_("E5070: Character number must not be less than zero"));
- return;
- }
- if (num > INT_MAX) {
- emsgf(_("E5071: Character number must not be greater than INT_MAX (%i)"),
- INT_MAX);
- return;
- }
-
- char buf[MB_MAXBYTES];
- const int len = utf_char2bytes((int)num, (char_u *)buf);
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = xmemdupz(buf, (size_t)len);
-}
-
-/*
- * "or(expr, expr)" function
- */
-static void f_or(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL)
- | tv_get_number_chk(&argvars[1], NULL);
-}
-
-/*
- * "pathshorten()" function
- */
-static void f_pathshorten(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_STRING;
- const char *const s = tv_get_string_chk(&argvars[0]);
- if (!s) {
- return;
- }
- rettv->vval.v_string = shorten_dir((char_u *)xstrdup(s));
-}
-
-/*
- * "pow()" function
- */
-static void f_pow(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- float_T fx;
- float_T fy;
-
- rettv->v_type = VAR_FLOAT;
- if (tv_get_float_chk(argvars, &fx) && tv_get_float_chk(&argvars[1], &fy)) {
- rettv->vval.v_float = pow(fx, fy);
- } else {
- rettv->vval.v_float = 0.0;
- }
-}
-
-/*
- * "prevnonblank()" function
- */
-static void f_prevnonblank(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- linenr_T lnum = tv_get_lnum(argvars);
- if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count) {
- lnum = 0;
- } else {
- while (lnum >= 1 && *skipwhite(ml_get(lnum)) == NUL) {
- lnum--;
- }
- }
- rettv->vval.v_number = lnum;
-}
-
-/*
- * "printf()" function
- */
-static void f_printf(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
- {
- int len;
- int saved_did_emsg = did_emsg;
-
- // Get the required length, allocate the buffer and do it for real.
- did_emsg = false;
- char buf[NUMBUFLEN];
- const char *fmt = tv_get_string_buf(&argvars[0], buf);
- len = vim_vsnprintf_typval(NULL, 0, fmt, dummy_ap, argvars + 1);
- if (!did_emsg) {
- char *s = xmalloc(len + 1);
- rettv->vval.v_string = (char_u *)s;
- (void)vim_vsnprintf_typval(s, len + 1, fmt, dummy_ap, argvars + 1);
- }
- did_emsg |= saved_did_emsg;
- }
-}
-
-// "pum_getpos()" function
-static void f_pum_getpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- tv_dict_alloc_ret(rettv);
- pum_set_event_info(rettv->vval.v_dict);
-}
-
-/*
- * "pumvisible()" function
- */
-static void f_pumvisible(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (pum_visible())
- rettv->vval.v_number = 1;
-}
-
-/*
- * "pyeval()" function
- */
-static void f_pyeval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- script_host_eval("python", argvars, rettv);
-}
-
-/*
- * "py3eval()" function
- */
-static void f_py3eval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- script_host_eval("python3", argvars, rettv);
-}
-
-// "pyxeval()" function
-static void f_pyxeval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- init_pyxversion();
- if (p_pyx == 2) {
- f_pyeval(argvars, rettv, NULL);
- } else {
- f_py3eval(argvars, rettv, NULL);
- }
-}
-
-/*
- * "range()" function
- */
-static void f_range(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- varnumber_T start;
- varnumber_T end;
- varnumber_T stride = 1;
- varnumber_T i;
- bool error = false;
-
- start = tv_get_number_chk(&argvars[0], &error);
- if (argvars[1].v_type == VAR_UNKNOWN) {
- end = start - 1;
- start = 0;
- } else {
- end = tv_get_number_chk(&argvars[1], &error);
- if (argvars[2].v_type != VAR_UNKNOWN) {
- stride = tv_get_number_chk(&argvars[2], &error);
- }
- }
-
- if (error) {
- return; // Type error; errmsg already given.
- }
- if (stride == 0) {
- EMSG(_("E726: Stride is zero"));
- } else if (stride > 0 ? end + 1 < start : end - 1 > start) {
- EMSG(_("E727: Start past end"));
- } else {
- tv_list_alloc_ret(rettv, (end - start) / stride);
- for (i = start; stride > 0 ? i <= end : i >= end; i += stride) {
- tv_list_append_number(rettv->vval.v_list, (varnumber_T)i);
- }
- }
-}
-
-/*
- * "readfile()" function
- */
-static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- bool binary = false;
- FILE *fd;
- char_u buf[(IOSIZE/256)*256]; /* rounded to avoid odd + 1 */
- int io_size = sizeof(buf);
- int readlen; /* size of last fread() */
- char_u *prev = NULL; /* previously read bytes, if any */
- long prevlen = 0; /* length of data in prev */
- long prevsize = 0; /* size of prev buffer */
- long maxline = MAXLNUM;
-
- if (argvars[1].v_type != VAR_UNKNOWN) {
- if (strcmp(tv_get_string(&argvars[1]), "b") == 0) {
- binary = true;
- }
- if (argvars[2].v_type != VAR_UNKNOWN) {
- maxline = tv_get_number(&argvars[2]);
- }
- }
-
- list_T *const l = tv_list_alloc_ret(rettv, kListLenUnknown);
-
- // Always open the file in binary mode, library functions have a mind of
- // their own about CR-LF conversion.
- const char *const fname = tv_get_string(&argvars[0]);
- if (*fname == NUL || (fd = os_fopen(fname, READBIN)) == NULL) {
- EMSG2(_(e_notopen), *fname == NUL ? _("<empty>") : fname);
- return;
- }
-
- while (maxline < 0 || tv_list_len(l) < maxline) {
- readlen = (int)fread(buf, 1, io_size, fd);
-
- // This for loop processes what was read, but is also entered at end
- // of file so that either:
- // - an incomplete line gets written
- // - a "binary" file gets an empty line at the end if it ends in a
- // newline.
- char_u *p; // Position in buf.
- char_u *start; // Start of current line.
- for (p = buf, start = buf;
- p < buf + readlen || (readlen <= 0 && (prevlen > 0 || binary));
- p++) {
- if (*p == '\n' || readlen <= 0) {
- char_u *s = NULL;
- size_t len = p - start;
-
- /* Finished a line. Remove CRs before NL. */
- if (readlen > 0 && !binary) {
- while (len > 0 && start[len - 1] == '\r')
- --len;
- /* removal may cross back to the "prev" string */
- if (len == 0)
- while (prevlen > 0 && prev[prevlen - 1] == '\r')
- --prevlen;
- }
- if (prevlen == 0) {
- assert(len < INT_MAX);
- s = vim_strnsave(start, (int)len);
- } else {
- /* Change "prev" buffer to be the right size. This way
- * the bytes are only copied once, and very long lines are
- * allocated only once. */
- s = xrealloc(prev, prevlen + len + 1);
- memcpy(s + prevlen, start, len);
- s[prevlen + len] = NUL;
- prev = NULL; /* the list will own the string */
- prevlen = prevsize = 0;
- }
-
- tv_list_append_owned_tv(l, (typval_T) {
- .v_type = VAR_STRING,
- .v_lock = VAR_UNLOCKED,
- .vval.v_string = s,
- });
-
- start = p + 1; // Step over newline.
- if (maxline < 0) {
- if (tv_list_len(l) > -maxline) {
- assert(tv_list_len(l) == 1 + (-maxline));
- tv_list_item_remove(l, tv_list_first(l));
- }
- } else if (tv_list_len(l) >= maxline) {
- assert(tv_list_len(l) == maxline);
- break;
- }
- if (readlen <= 0) {
- break;
- }
- } else if (*p == NUL) {
- *p = '\n';
- // Check for utf8 "bom"; U+FEFF is encoded as EF BB BF. Do this
- // when finding the BF and check the previous two bytes.
- } else if (*p == 0xbf && !binary) {
- // Find the two bytes before the 0xbf. If p is at buf, or buf + 1,
- // these may be in the "prev" string.
- char_u back1 = p >= buf + 1 ? p[-1]
- : prevlen >= 1 ? prev[prevlen - 1] : NUL;
- char_u back2 = p >= buf + 2 ? p[-2]
- : p == buf + 1 && prevlen >= 1 ? prev[prevlen - 1]
- : prevlen >= 2 ? prev[prevlen - 2] : NUL;
-
- if (back2 == 0xef && back1 == 0xbb) {
- char_u *dest = p - 2;
-
- /* Usually a BOM is at the beginning of a file, and so at
- * the beginning of a line; then we can just step over it.
- */
- if (start == dest)
- start = p + 1;
- else {
- /* have to shuffle buf to close gap */
- int adjust_prevlen = 0;
-
- if (dest < buf) { // -V782
- adjust_prevlen = (int)(buf - dest); // -V782
- // adjust_prevlen must be 1 or 2.
- dest = buf;
- }
- if (readlen > p - buf + 1)
- memmove(dest, p + 1, readlen - (p - buf) - 1);
- readlen -= 3 - adjust_prevlen;
- prevlen -= adjust_prevlen;
- p = dest - 1;
- }
- }
- }
- } /* for */
-
- if ((maxline >= 0 && tv_list_len(l) >= maxline) || readlen <= 0) {
- break;
- }
- if (start < p) {
- /* There's part of a line in buf, store it in "prev". */
- if (p - start + prevlen >= prevsize) {
-
- /* A common use case is ordinary text files and "prev" gets a
- * fragment of a line, so the first allocation is made
- * small, to avoid repeatedly 'allocing' large and
- * 'reallocing' small. */
- if (prevsize == 0)
- prevsize = (long)(p - start);
- else {
- long grow50pc = (prevsize * 3) / 2;
- long growmin = (long)((p - start) * 2 + prevlen);
- prevsize = grow50pc > growmin ? grow50pc : growmin;
- }
- prev = xrealloc(prev, prevsize);
- }
- /* Add the line part to end of "prev". */
- memmove(prev + prevlen, start, p - start);
- prevlen += (long)(p - start);
- }
- } /* while */
-
- xfree(prev);
- fclose(fd);
-}
-
-static void return_register(int regname, typval_T *rettv)
+void return_register(int regname, typval_T *rettv)
{
char_u buf[2] = { regname, 0 };
@@ -14220,810 +6792,7 @@ static void return_register(int regname, typval_T *rettv)
rettv->vval.v_string = vim_strsave(buf);
}
-// "reg_executing()" function
-static void f_reg_executing(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- return_register(reg_executing, rettv);
-}
-
-// "reg_recording()" function
-static void f_reg_recording(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- return_register(reg_recording, rettv);
-}
-
-/// list2proftime - convert a List to proftime_T
-///
-/// @param arg The input list, must be of type VAR_LIST and have
-/// exactly 2 items
-/// @param[out] tm The proftime_T representation of `arg`
-/// @return OK In case of success, FAIL in case of error
-static int list2proftime(typval_T *arg, proftime_T *tm) FUNC_ATTR_NONNULL_ALL
-{
- if (arg->v_type != VAR_LIST || tv_list_len(arg->vval.v_list) != 2) {
- return FAIL;
- }
-
- bool error = false;
- varnumber_T n1 = tv_list_find_nr(arg->vval.v_list, 0L, &error);
- varnumber_T n2 = tv_list_find_nr(arg->vval.v_list, 1L, &error);
- if (error) {
- return FAIL;
- }
-
- // in f_reltime() we split up the 64-bit proftime_T into two 32-bit
- // values, now we combine them again.
- union {
- struct { int32_t low, high; } split;
- proftime_T prof;
- } u = { .split.high = n1, .split.low = n2 };
-
- *tm = u.prof;
-
- return OK;
-}
-
-/// f_reltime - return an item that represents a time value
-///
-/// @param[out] rettv Without an argument it returns the current time. With
-/// one argument it returns the time passed since the argument.
-/// With two arguments it returns the time passed between
-/// the two arguments.
-static void f_reltime(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- proftime_T res;
- proftime_T start;
-
- if (argvars[0].v_type == VAR_UNKNOWN) {
- // no arguments: get current time.
- res = profile_start();
- } else if (argvars[1].v_type == VAR_UNKNOWN) {
- if (list2proftime(&argvars[0], &res) == FAIL) {
- return;
- }
- res = profile_end(res);
- } else {
- // two arguments: compute the difference.
- if (list2proftime(&argvars[0], &start) == FAIL
- || list2proftime(&argvars[1], &res) == FAIL) {
- return;
- }
- res = profile_sub(res, start);
- }
-
- // we have to store the 64-bit proftime_T inside of a list of int's
- // (varnumber_T is defined as int). For all our supported platforms, int's
- // are at least 32-bits wide. So we'll use two 32-bit values to store it.
- union {
- struct { int32_t low, high; } split;
- proftime_T prof;
- } u = { .prof = res };
-
- // statically assert that the union type conv will provide the correct
- // results, if varnumber_T or proftime_T change, the union cast will need
- // to be revised.
- STATIC_ASSERT(sizeof(u.prof) == sizeof(u) && sizeof(u.split) == sizeof(u),
- "type punning will produce incorrect results on this platform");
-
- tv_list_alloc_ret(rettv, 2);
- tv_list_append_number(rettv->vval.v_list, u.split.high);
- tv_list_append_number(rettv->vval.v_list, u.split.low);
-}
-
-/// "reltimestr()" function
-static void f_reltimestr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
- FUNC_ATTR_NONNULL_ALL
-{
- proftime_T tm;
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
- if (list2proftime(&argvars[0], &tm) == OK) {
- rettv->vval.v_string = (char_u *)xstrdup(profile_msg(tm));
- }
-}
-
-/*
- * "remove()" function
- */
-static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- list_T *l;
- listitem_T *item, *item2;
- listitem_T *li;
- long idx;
- long end;
- dict_T *d;
- dictitem_T *di;
- const char *const arg_errmsg = N_("remove() argument");
-
- if (argvars[0].v_type == VAR_DICT) {
- if (argvars[2].v_type != VAR_UNKNOWN) {
- EMSG2(_(e_toomanyarg), "remove()");
- } else if ((d = argvars[0].vval.v_dict) != NULL
- && !tv_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE)) {
- const char *key = tv_get_string_chk(&argvars[1]);
- if (key != NULL) {
- di = tv_dict_find(d, key, -1);
- if (di == NULL) {
- EMSG2(_(e_dictkey), key);
- } else if (!var_check_fixed(di->di_flags, arg_errmsg, TV_TRANSLATE)
- && !var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE)) {
- *rettv = di->di_tv;
- di->di_tv = TV_INITIAL_VALUE;
- tv_dict_item_remove(d, di);
- if (tv_dict_is_watched(d)) {
- tv_dict_watcher_notify(d, key, NULL, rettv);
- }
- }
- }
- }
- } else if (argvars[0].v_type != VAR_LIST) {
- EMSG2(_(e_listdictarg), "remove()");
- } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)),
- arg_errmsg, TV_TRANSLATE)) {
- bool error = false;
-
- idx = tv_get_number_chk(&argvars[1], &error);
- if (error) {
- // Type error: do nothing, errmsg already given.
- } else if ((item = tv_list_find(l, idx)) == NULL) {
- EMSGN(_(e_listidx), idx);
- } else {
- if (argvars[2].v_type == VAR_UNKNOWN) {
- // Remove one item, return its value.
- tv_list_drop_items(l, item, item);
- *rettv = *TV_LIST_ITEM_TV(item);
- xfree(item);
- } else {
- // Remove range of items, return list with values.
- end = tv_get_number_chk(&argvars[2], &error);
- if (error) {
- // Type error: do nothing.
- } else if ((item2 = tv_list_find(l, end)) == NULL) {
- EMSGN(_(e_listidx), end);
- } else {
- int cnt = 0;
-
- for (li = item; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) {
- cnt++;
- if (li == item2) {
- break;
- }
- }
- if (li == NULL) { // Didn't find "item2" after "item".
- EMSG(_(e_invrange));
- } else {
- tv_list_move_items(l, item, item2, tv_list_alloc_ret(rettv, cnt),
- cnt);
- }
- }
- }
- }
- }
-}
-
-/*
- * "rename({from}, {to})" function
- */
-static void f_rename(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (check_restricted() || check_secure()) {
- rettv->vval.v_number = -1;
- } else {
- char buf[NUMBUFLEN];
- rettv->vval.v_number = vim_rename(
- (const char_u *)tv_get_string(&argvars[0]),
- (const char_u *)tv_get_string_buf(&argvars[1], buf));
- }
-}
-
-/*
- * "repeat()" function
- */
-static void f_repeat(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- varnumber_T n = tv_get_number(&argvars[1]);
- if (argvars[0].v_type == VAR_LIST) {
- tv_list_alloc_ret(rettv, (n > 0) * n * tv_list_len(argvars[0].vval.v_list));
- while (n-- > 0) {
- tv_list_extend(rettv->vval.v_list, argvars[0].vval.v_list, NULL);
- }
- } else {
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
- if (n <= 0) {
- return;
- }
-
- const char *const p = tv_get_string(&argvars[0]);
-
- const size_t slen = strlen(p);
- if (slen == 0) {
- return;
- }
- const size_t len = slen * n;
- // Detect overflow.
- if (len / n != slen) {
- return;
- }
-
- char *const r = xmallocz(len);
- for (varnumber_T i = 0; i < n; i++) {
- memmove(r + i * slen, p, slen);
- }
-
- rettv->vval.v_string = (char_u *)r;
- }
-}
-
-/*
- * "resolve()" function
- */
-static void f_resolve(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_STRING;
- const char *fname = tv_get_string(&argvars[0]);
-#ifdef WIN32
- char *const v = os_resolve_shortcut(fname);
- rettv->vval.v_string = (char_u *)(v == NULL ? xstrdup(fname) : v);
-#else
-# ifdef HAVE_READLINK
- {
- bool is_relative_to_current = false;
- bool has_trailing_pathsep = false;
- int limit = 100;
-
- char *p = xstrdup(fname);
-
- if (p[0] == '.' && (vim_ispathsep(p[1])
- || (p[1] == '.' && (vim_ispathsep(p[2]))))) {
- is_relative_to_current = true;
- }
-
- ptrdiff_t len = (ptrdiff_t)strlen(p);
- if (len > 0 && after_pathsep(p, p + len)) {
- has_trailing_pathsep = true;
- p[len - 1] = NUL; // The trailing slash breaks readlink().
- }
-
- char *q = (char *)path_next_component(p);
- char *remain = NULL;
- if (*q != NUL) {
- // Separate the first path component in "p", and keep the
- // remainder (beginning with the path separator).
- remain = xstrdup(q - 1);
- q[-1] = NUL;
- }
-
- char *const buf = xmallocz(MAXPATHL);
-
- char *cpy;
- for (;; ) {
- for (;; ) {
- len = readlink(p, buf, MAXPATHL);
- if (len <= 0) {
- break;
- }
- buf[len] = NUL;
-
- if (limit-- == 0) {
- xfree(p);
- xfree(remain);
- EMSG(_("E655: Too many symbolic links (cycle?)"));
- rettv->vval.v_string = NULL;
- xfree(buf);
- return;
- }
-
- // Ensure that the result will have a trailing path separator
- // if the argument has one. */
- if (remain == NULL && has_trailing_pathsep) {
- add_pathsep(buf);
- }
-
- // Separate the first path component in the link value and
- // concatenate the remainders. */
- q = (char *)path_next_component(vim_ispathsep(*buf) ? buf + 1 : buf);
- if (*q != NUL) {
- cpy = remain;
- remain = (remain
- ? (char *)concat_str((char_u *)q - 1, (char_u *)remain)
- : xstrdup(q - 1));
- xfree(cpy);
- q[-1] = NUL;
- }
-
- q = (char *)path_tail((char_u *)p);
- if (q > p && *q == NUL) {
- // Ignore trailing path separator.
- q[-1] = NUL;
- q = (char *)path_tail((char_u *)p);
- }
- if (q > p && !path_is_absolute((const char_u *)buf)) {
- // Symlink is relative to directory of argument. Replace the
- // symlink with the resolved name in the same directory.
- const size_t p_len = strlen(p);
- const size_t buf_len = strlen(buf);
- p = xrealloc(p, p_len + buf_len + 1);
- memcpy(path_tail((char_u *)p), buf, buf_len + 1);
- } else {
- xfree(p);
- p = xstrdup(buf);
- }
- }
-
- if (remain == NULL) {
- break;
- }
-
- // Append the first path component of "remain" to "p".
- q = (char *)path_next_component(remain + 1);
- len = q - remain - (*q != NUL);
- const size_t p_len = strlen(p);
- cpy = xmallocz(p_len + len);
- memcpy(cpy, p, p_len + 1);
- xstrlcat(cpy + p_len, remain, len + 1);
- xfree(p);
- p = cpy;
-
- // Shorten "remain".
- if (*q != NUL) {
- STRMOVE(remain, q - 1);
- } else {
- XFREE_CLEAR(remain);
- }
- }
-
- // If the result is a relative path name, make it explicitly relative to
- // the current directory if and only if the argument had this form.
- if (!vim_ispathsep(*p)) {
- if (is_relative_to_current
- && *p != NUL
- && !(p[0] == '.'
- && (p[1] == NUL
- || vim_ispathsep(p[1])
- || (p[1] == '.'
- && (p[2] == NUL
- || vim_ispathsep(p[2])))))) {
- // Prepend "./".
- cpy = (char *)concat_str((const char_u *)"./", (const char_u *)p);
- xfree(p);
- p = cpy;
- } else if (!is_relative_to_current) {
- // Strip leading "./".
- q = p;
- while (q[0] == '.' && vim_ispathsep(q[1])) {
- q += 2;
- }
- if (q > p) {
- STRMOVE(p, p + 2);
- }
- }
- }
-
- // Ensure that the result will have no trailing path separator
- // if the argument had none. But keep "/" or "//".
- if (!has_trailing_pathsep) {
- q = p + strlen(p);
- if (after_pathsep(p, q)) {
- *path_tail_with_sep((char_u *)p) = NUL;
- }
- }
-
- rettv->vval.v_string = (char_u *)p;
- xfree(buf);
- }
-# else
- rettv->vval.v_string = (char_u *)xstrdup(p);
-# endif
-#endif
-
- simplify_filename(rettv->vval.v_string);
-}
-
-/*
- * "reverse({list})" function
- */
-static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- list_T *l;
- if (argvars[0].v_type != VAR_LIST) {
- EMSG2(_(e_listarg), "reverse()");
- } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)),
- N_("reverse() argument"), TV_TRANSLATE)) {
- tv_list_reverse(l);
- tv_list_set_ret(rettv, l);
- }
-}
-
-#define SP_NOMOVE 0x01 ///< don't move cursor
-#define SP_REPEAT 0x02 ///< repeat to find outer pair
-#define SP_RETCOUNT 0x04 ///< return matchcount
-#define SP_SETPCMARK 0x08 ///< set previous context mark
-#define SP_START 0x10 ///< accept match at start position
-#define SP_SUBPAT 0x20 ///< return nr of matching sub-pattern
-#define SP_END 0x40 ///< leave cursor at end of match
-#define SP_COLUMN 0x80 ///< start at cursor column
-
-/*
- * Get flags for a search function.
- * Possibly sets "p_ws".
- * Returns BACKWARD, FORWARD or zero (for an error).
- */
-static int get_search_arg(typval_T *varp, int *flagsp)
-{
- int dir = FORWARD;
- int mask;
-
- if (varp->v_type != VAR_UNKNOWN) {
- char nbuf[NUMBUFLEN];
- const char *flags = tv_get_string_buf_chk(varp, nbuf);
- if (flags == NULL) {
- return 0; // Type error; errmsg already given.
- }
- while (*flags != NUL) {
- switch (*flags) {
- case 'b': dir = BACKWARD; break;
- case 'w': p_ws = true; break;
- case 'W': p_ws = false; break;
- default: {
- mask = 0;
- if (flagsp != NULL) {
- switch (*flags) {
- case 'c': mask = SP_START; break;
- case 'e': mask = SP_END; break;
- case 'm': mask = SP_RETCOUNT; break;
- case 'n': mask = SP_NOMOVE; break;
- case 'p': mask = SP_SUBPAT; break;
- case 'r': mask = SP_REPEAT; break;
- case 's': mask = SP_SETPCMARK; break;
- case 'z': mask = SP_COLUMN; break;
- }
- }
- if (mask == 0) {
- emsgf(_(e_invarg2), flags);
- dir = 0;
- } else {
- *flagsp |= mask;
- }
- }
- }
- if (dir == 0) {
- break;
- }
- flags++;
- }
- }
- return dir;
-}
-
-// Shared by search() and searchpos() functions.
-static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp)
-{
- int flags;
- pos_T pos;
- pos_T save_cursor;
- bool save_p_ws = p_ws;
- int dir;
- int retval = 0; /* default: FAIL */
- long lnum_stop = 0;
- proftime_T tm;
- long time_limit = 0;
- int options = SEARCH_KEEP;
- int subpatnum;
- searchit_arg_T sia;
-
- const char *const pat = tv_get_string(&argvars[0]);
- dir = get_search_arg(&argvars[1], flagsp); // May set p_ws.
- if (dir == 0) {
- goto theend;
- }
- flags = *flagsp;
- if (flags & SP_START) {
- options |= SEARCH_START;
- }
- if (flags & SP_END) {
- options |= SEARCH_END;
- }
- if (flags & SP_COLUMN) {
- options |= SEARCH_COL;
- }
-
- /* Optional arguments: line number to stop searching and timeout. */
- if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN) {
- lnum_stop = tv_get_number_chk(&argvars[2], NULL);
- if (lnum_stop < 0) {
- goto theend;
- }
- if (argvars[3].v_type != VAR_UNKNOWN) {
- time_limit = tv_get_number_chk(&argvars[3], NULL);
- if (time_limit < 0) {
- goto theend;
- }
- }
- }
-
- /* Set the time limit, if there is one. */
- tm = profile_setlimit(time_limit);
-
- /*
- * This function does not accept SP_REPEAT and SP_RETCOUNT flags.
- * Check to make sure only those flags are set.
- * Also, Only the SP_NOMOVE or the SP_SETPCMARK flag can be set. Both
- * flags cannot be set. Check for that condition also.
- */
- if (((flags & (SP_REPEAT | SP_RETCOUNT)) != 0)
- || ((flags & SP_NOMOVE) && (flags & SP_SETPCMARK))) {
- EMSG2(_(e_invarg2), tv_get_string(&argvars[1]));
- goto theend;
- }
-
- pos = save_cursor = curwin->w_cursor;
- memset(&sia, 0, sizeof(sia));
- sia.sa_stop_lnum = (linenr_T)lnum_stop;
- sia.sa_tm = &tm;
- subpatnum = searchit(curwin, curbuf, &pos, NULL, dir, (char_u *)pat, 1,
- options, RE_SEARCH, &sia);
- if (subpatnum != FAIL) {
- if (flags & SP_SUBPAT)
- retval = subpatnum;
- else
- retval = pos.lnum;
- if (flags & SP_SETPCMARK)
- setpcmark();
- curwin->w_cursor = pos;
- if (match_pos != NULL) {
- /* Store the match cursor position */
- match_pos->lnum = pos.lnum;
- match_pos->col = pos.col + 1;
- }
- /* "/$" will put the cursor after the end of the line, may need to
- * correct that here */
- check_cursor();
- }
-
- /* If 'n' flag is used: restore cursor position. */
- if (flags & SP_NOMOVE)
- curwin->w_cursor = save_cursor;
- else
- curwin->w_set_curswant = TRUE;
-theend:
- p_ws = save_p_ws;
-
- return retval;
-}
-
-// "rpcnotify()" function
-static void f_rpcnotify(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = 0;
-
- if (check_restricted() || check_secure()) {
- return;
- }
-
- if (argvars[0].v_type != VAR_NUMBER || argvars[0].vval.v_number < 0) {
- EMSG2(_(e_invarg2), "Channel id must be a positive integer");
- return;
- }
-
- if (argvars[1].v_type != VAR_STRING) {
- EMSG2(_(e_invarg2), "Event type must be a string");
- return;
- }
-
- Array args = ARRAY_DICT_INIT;
-
- for (typval_T *tv = argvars + 2; tv->v_type != VAR_UNKNOWN; tv++) {
- ADD(args, vim_to_object(tv));
- }
-
- if (!rpc_send_event((uint64_t)argvars[0].vval.v_number,
- tv_get_string(&argvars[1]), args)) {
- EMSG2(_(e_invarg2), "Channel doesn't exist");
- return;
- }
-
- rettv->vval.v_number = 1;
-}
-
-// "rpcrequest()" function
-static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = 0;
- const int l_provider_call_nesting = provider_call_nesting;
-
- if (check_restricted() || check_secure()) {
- return;
- }
-
- if (argvars[0].v_type != VAR_NUMBER || argvars[0].vval.v_number <= 0) {
- EMSG2(_(e_invarg2), "Channel id must be a positive integer");
- return;
- }
-
- if (argvars[1].v_type != VAR_STRING) {
- EMSG2(_(e_invarg2), "Method name must be a string");
- return;
- }
-
- Array args = ARRAY_DICT_INIT;
-
- for (typval_T *tv = argvars + 2; tv->v_type != VAR_UNKNOWN; tv++) {
- ADD(args, vim_to_object(tv));
- }
-
- sctx_T save_current_sctx;
- uint8_t *save_sourcing_name, *save_autocmd_fname, *save_autocmd_match;
- linenr_T save_sourcing_lnum;
- int save_autocmd_bufnr;
- void *save_funccalp;
-
- if (l_provider_call_nesting) {
- // If this is called from a provider function, restore the scope
- // information of the caller.
- save_current_sctx = current_sctx;
- save_sourcing_name = sourcing_name;
- save_sourcing_lnum = sourcing_lnum;
- save_autocmd_fname = autocmd_fname;
- save_autocmd_match = autocmd_match;
- save_autocmd_bufnr = autocmd_bufnr;
- save_funccalp = save_funccal();
-
- current_sctx = provider_caller_scope.script_ctx;
- sourcing_name = provider_caller_scope.sourcing_name;
- sourcing_lnum = provider_caller_scope.sourcing_lnum;
- autocmd_fname = provider_caller_scope.autocmd_fname;
- autocmd_match = provider_caller_scope.autocmd_match;
- autocmd_bufnr = provider_caller_scope.autocmd_bufnr;
- restore_funccal(provider_caller_scope.funccalp);
- }
-
-
- Error err = ERROR_INIT;
-
- uint64_t chan_id = (uint64_t)argvars[0].vval.v_number;
- const char *method = tv_get_string(&argvars[1]);
-
- Object result = rpc_send_call(chan_id, method, args, &err);
-
- if (l_provider_call_nesting) {
- current_sctx = save_current_sctx;
- sourcing_name = save_sourcing_name;
- sourcing_lnum = save_sourcing_lnum;
- autocmd_fname = save_autocmd_fname;
- autocmd_match = save_autocmd_match;
- autocmd_bufnr = save_autocmd_bufnr;
- restore_funccal(save_funccalp);
- }
-
- if (ERROR_SET(&err)) {
- const char *name = NULL;
- Channel *chan = find_channel(chan_id);
- if (chan) {
- name = rpc_client_name(chan);
- }
- msg_ext_set_kind("rpc_error");
- if (name) {
- emsgf_multiline("Error invoking '%s' on channel %"PRIu64" (%s):\n%s",
- method, chan_id, name, err.msg);
- } else {
- emsgf_multiline("Error invoking '%s' on channel %"PRIu64":\n%s",
- method, chan_id, err.msg);
- }
-
- goto end;
- }
-
- if (!object_to_vim(result, rettv, &err)) {
- EMSG2(_("Error converting the call result: %s"), err.msg);
- }
-
-end:
- api_free_object(result);
- api_clear_error(&err);
-}
-
-// "rpcstart()" function (DEPRECATED)
-static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = 0;
-
- if (check_restricted() || check_secure()) {
- return;
- }
-
- if (argvars[0].v_type != VAR_STRING
- || (argvars[1].v_type != VAR_LIST && argvars[1].v_type != VAR_UNKNOWN)) {
- // Wrong argument types
- EMSG(_(e_invarg));
- return;
- }
-
- list_T *args = NULL;
- int argsl = 0;
- if (argvars[1].v_type == VAR_LIST) {
- args = argvars[1].vval.v_list;
- argsl = tv_list_len(args);
- // Assert that all list items are strings
- int i = 0;
- TV_LIST_ITER_CONST(args, arg, {
- if (TV_LIST_ITEM_TV(arg)->v_type != VAR_STRING) {
- emsgf(_("E5010: List item %d of the second argument is not a string"),
- i);
- return;
- }
- i++;
- });
- }
-
- if (argvars[0].vval.v_string == NULL || argvars[0].vval.v_string[0] == NUL) {
- EMSG(_(e_api_spawn_failed));
- return;
- }
-
- // Allocate extra memory for the argument vector and the NULL pointer
- int argvl = argsl + 2;
- char **argv = xmalloc(sizeof(char_u *) * argvl);
-
- // Copy program name
- argv[0] = xstrdup((char *)argvars[0].vval.v_string);
-
- int i = 1;
- // Copy arguments to the vector
- if (argsl > 0) {
- TV_LIST_ITER_CONST(args, arg, {
- argv[i++] = xstrdup(tv_get_string(TV_LIST_ITEM_TV(arg)));
- });
- }
-
- // The last item of argv must be NULL
- argv[i] = NULL;
-
- Channel *chan = channel_job_start(argv, CALLBACK_READER_INIT,
- CALLBACK_READER_INIT, CALLBACK_NONE,
- false, true, false, NULL, 0, 0, NULL, NULL,
- &rettv->vval.v_number);
- if (chan) {
- channel_create_event(chan, NULL);
- }
-}
-
-// "rpcstop()" function
-static void f_rpcstop(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = 0;
-
- if (check_restricted() || check_secure()) {
- return;
- }
-
- if (argvars[0].v_type != VAR_NUMBER) {
- // Wrong argument types
- EMSG(_(e_invarg));
- return;
- }
-
- // if called with a job, stop it, else closes the channel
- uint64_t id = argvars[0].vval.v_number;
- if (find_job(id, false)) {
- f_jobstop(argvars, rettv, NULL);
- } else {
- const char *error;
- rettv->vval.v_number = channel_close(argvars[0].vval.v_number,
- kChannelPartRpc, &error);
- if (!rettv->vval.v_number) {
- EMSG(error);
- }
- }
-}
-
-static void screenchar_adjust_grid(ScreenGrid **grid, int *row, int *col)
+void screenchar_adjust_grid(ScreenGrid **grid, int *row, int *col)
{
// TODO(bfredl): this is a hack for legacy tests which use screenchar()
// to check printed messages on the screen (but not floats etc
@@ -15038,501 +6807,9 @@ static void screenchar_adjust_grid(ScreenGrid **grid, int *row, int *col)
}
}
-/*
- * "screenattr()" function
- */
-static void f_screenattr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int c;
-
- int row = (int)tv_get_number_chk(&argvars[0], NULL) - 1;
- int col = (int)tv_get_number_chk(&argvars[1], NULL) - 1;
- if (row < 0 || row >= default_grid.Rows
- || col < 0 || col >= default_grid.Columns) {
- c = -1;
- } else {
- ScreenGrid *grid = &default_grid;
- screenchar_adjust_grid(&grid, &row, &col);
- c = grid->attrs[grid->line_offset[row] + col];
- }
- rettv->vval.v_number = c;
-}
-
-/*
- * "screenchar()" function
- */
-static void f_screenchar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int c;
-
- int row = tv_get_number_chk(&argvars[0], NULL) - 1;
- int col = tv_get_number_chk(&argvars[1], NULL) - 1;
- if (row < 0 || row >= default_grid.Rows
- || col < 0 || col >= default_grid.Columns) {
- c = -1;
- } else {
- ScreenGrid *grid = &default_grid;
- screenchar_adjust_grid(&grid, &row, &col);
- c = utf_ptr2char(grid->chars[grid->line_offset[row] + col]);
- }
- rettv->vval.v_number = c;
-}
-
-/*
- * "screencol()" function
- *
- * First column is 1 to be consistent with virtcol().
- */
-static void f_screencol(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = ui_current_col() + 1;
-}
-
-/// "screenpos({winid}, {lnum}, {col})" function
-static void f_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- pos_T pos;
- int row = 0;
- int scol = 0, ccol = 0, ecol = 0;
-
- tv_dict_alloc_ret(rettv);
- dict_T *dict = rettv->vval.v_dict;
-
- win_T *wp = find_win_by_nr_or_id(&argvars[0]);
- if (wp == NULL) {
- return;
- }
-
- pos.lnum = tv_get_number(&argvars[1]);
- pos.col = tv_get_number(&argvars[2]) - 1;
- pos.coladd = 0;
- textpos2screenpos(wp, &pos, &row, &scol, &ccol, &ecol, false);
-
- tv_dict_add_nr(dict, S_LEN("row"), row);
- tv_dict_add_nr(dict, S_LEN("col"), scol);
- tv_dict_add_nr(dict, S_LEN("curscol"), ccol);
- tv_dict_add_nr(dict, S_LEN("endcol"), ecol);
-}
-
-/*
- * "screenrow()" function
- */
-static void f_screenrow(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = ui_current_row() + 1;
-}
-
-/*
- * "search()" function
- */
-static void f_search(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int flags = 0;
-
- rettv->vval.v_number = search_cmn(argvars, NULL, &flags);
-}
-
-/*
- * "searchdecl()" function
- */
-static void f_searchdecl(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int locally = 1;
- int thisblock = 0;
- bool error = false;
-
- rettv->vval.v_number = 1; /* default: FAIL */
-
- const char *const name = tv_get_string_chk(&argvars[0]);
- if (argvars[1].v_type != VAR_UNKNOWN) {
- locally = tv_get_number_chk(&argvars[1], &error) == 0;
- if (!error && argvars[2].v_type != VAR_UNKNOWN) {
- thisblock = tv_get_number_chk(&argvars[2], &error) != 0;
- }
- }
- if (!error && name != NULL) {
- rettv->vval.v_number = find_decl((char_u *)name, strlen(name), locally,
- thisblock, SEARCH_KEEP) == FAIL;
- }
-}
-
-/*
- * Used by searchpair() and searchpairpos()
- */
-static int searchpair_cmn(typval_T *argvars, pos_T *match_pos)
-{
- bool save_p_ws = p_ws;
- int dir;
- int flags = 0;
- int retval = 0; // default: FAIL
- long lnum_stop = 0;
- long time_limit = 0;
-
- // Get the three pattern arguments: start, middle, end. Will result in an
- // error if not a valid argument.
- char nbuf1[NUMBUFLEN];
- char nbuf2[NUMBUFLEN];
- const char *spat = tv_get_string_chk(&argvars[0]);
- const char *mpat = tv_get_string_buf_chk(&argvars[1], nbuf1);
- const char *epat = tv_get_string_buf_chk(&argvars[2], nbuf2);
- if (spat == NULL || mpat == NULL || epat == NULL) {
- goto theend; // Type error.
- }
-
- // Handle the optional fourth argument: flags.
- dir = get_search_arg(&argvars[3], &flags); // may set p_ws.
- if (dir == 0) {
- goto theend;
- }
-
- // Don't accept SP_END or SP_SUBPAT.
- // Only one of the SP_NOMOVE or SP_SETPCMARK flags can be set.
- if ((flags & (SP_END | SP_SUBPAT)) != 0
- || ((flags & SP_NOMOVE) && (flags & SP_SETPCMARK))) {
- EMSG2(_(e_invarg2), tv_get_string(&argvars[3]));
- goto theend;
- }
-
- // Using 'r' implies 'W', otherwise it doesn't work.
- if (flags & SP_REPEAT) {
- p_ws = false;
- }
-
- // Optional fifth argument: skip expression.
- const typval_T *skip;
- if (argvars[3].v_type == VAR_UNKNOWN
- || argvars[4].v_type == VAR_UNKNOWN) {
- skip = NULL;
- } else {
- skip = &argvars[4];
- if (skip->v_type != VAR_FUNC
- && skip->v_type != VAR_PARTIAL
- && skip->v_type != VAR_STRING) {
- emsgf(_(e_invarg2), tv_get_string(&argvars[4]));
- goto theend; // Type error.
- }
- if (argvars[5].v_type != VAR_UNKNOWN) {
- lnum_stop = tv_get_number_chk(&argvars[5], NULL);
- if (lnum_stop < 0) {
- emsgf(_(e_invarg2), tv_get_string(&argvars[5]));
- goto theend;
- }
- if (argvars[6].v_type != VAR_UNKNOWN) {
- time_limit = tv_get_number_chk(&argvars[6], NULL);
- if (time_limit < 0) {
- emsgf(_(e_invarg2), tv_get_string(&argvars[6]));
- goto theend;
- }
- }
- }
- }
-
- retval = do_searchpair(
- (char_u *)spat, (char_u *)mpat, (char_u *)epat, dir, skip,
- flags, match_pos, lnum_stop, time_limit);
-
-theend:
- p_ws = save_p_ws;
-
- return retval;
-}
-
-/*
- * "searchpair()" function
- */
-static void f_searchpair(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = searchpair_cmn(argvars, NULL);
-}
-
-/*
- * "searchpairpos()" function
- */
-static void f_searchpairpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- pos_T match_pos;
- int lnum = 0;
- int col = 0;
-
- tv_list_alloc_ret(rettv, 2);
-
- if (searchpair_cmn(argvars, &match_pos) > 0) {
- lnum = match_pos.lnum;
- col = match_pos.col;
- }
-
- tv_list_append_number(rettv->vval.v_list, (varnumber_T)lnum);
- tv_list_append_number(rettv->vval.v_list, (varnumber_T)col);
-}
-
-/*
- * Search for a start/middle/end thing.
- * Used by searchpair(), see its documentation for the details.
- * Returns 0 or -1 for no match,
- */
-long
-do_searchpair(
- char_u *spat, // start pattern
- char_u *mpat, // middle pattern
- char_u *epat, // end pattern
- int dir, // BACKWARD or FORWARD
- const typval_T *skip, // skip expression
- int flags, // SP_SETPCMARK and other SP_ values
- pos_T *match_pos,
- linenr_T lnum_stop, // stop at this line if not zero
- long time_limit // stop after this many msec
-)
-{
- char_u *save_cpo;
- char_u *pat, *pat2 = NULL, *pat3 = NULL;
- long retval = 0;
- pos_T pos;
- pos_T firstpos;
- pos_T foundpos;
- pos_T save_cursor;
- pos_T save_pos;
- int n;
- int nest = 1;
- bool use_skip = false;
- int options = SEARCH_KEEP;
- proftime_T tm;
- size_t pat2_len;
- size_t pat3_len;
-
- /* Make 'cpoptions' empty, the 'l' flag should not be used here. */
- save_cpo = p_cpo;
- p_cpo = empty_option;
-
- /* Set the time limit, if there is one. */
- tm = profile_setlimit(time_limit);
-
- // Make two search patterns: start/end (pat2, for in nested pairs) and
- // start/middle/end (pat3, for the top pair).
- pat2_len = STRLEN(spat) + STRLEN(epat) + 17;
- pat2 = xmalloc(pat2_len);
- pat3_len = STRLEN(spat) + STRLEN(mpat) + STRLEN(epat) + 25;
- pat3 = xmalloc(pat3_len);
- snprintf((char *)pat2, pat2_len, "\\m\\(%s\\m\\)\\|\\(%s\\m\\)", spat, epat);
- if (*mpat == NUL) {
- STRCPY(pat3, pat2);
- } else {
- snprintf((char *)pat3, pat3_len,
- "\\m\\(%s\\m\\)\\|\\(%s\\m\\)\\|\\(%s\\m\\)", spat, epat, mpat);
- }
- if (flags & SP_START) {
- options |= SEARCH_START;
- }
-
- if (skip != NULL) {
- // Empty string means to not use the skip expression.
- if (skip->v_type == VAR_STRING || skip->v_type == VAR_FUNC) {
- use_skip = skip->vval.v_string != NULL && *skip->vval.v_string != NUL;
- }
- }
-
- save_cursor = curwin->w_cursor;
- pos = curwin->w_cursor;
- clearpos(&firstpos);
- clearpos(&foundpos);
- pat = pat3;
- for (;; ) {
- searchit_arg_T sia;
- memset(&sia, 0, sizeof(sia));
- sia.sa_stop_lnum = lnum_stop;
- sia.sa_tm = &tm;
-
- n = searchit(curwin, curbuf, &pos, NULL, dir, pat, 1L,
- options, RE_SEARCH, &sia);
- if (n == FAIL || (firstpos.lnum != 0 && equalpos(pos, firstpos))) {
- // didn't find it or found the first match again: FAIL
- break;
- }
-
- if (firstpos.lnum == 0)
- firstpos = pos;
- if (equalpos(pos, foundpos)) {
- /* Found the same position again. Can happen with a pattern that
- * has "\zs" at the end and searching backwards. Advance one
- * character and try again. */
- if (dir == BACKWARD)
- decl(&pos);
- else
- incl(&pos);
- }
- foundpos = pos;
-
- /* clear the start flag to avoid getting stuck here */
- options &= ~SEARCH_START;
-
- // If the skip pattern matches, ignore this match.
- if (use_skip) {
- save_pos = curwin->w_cursor;
- curwin->w_cursor = pos;
- bool err = false;
- const bool r = eval_expr_to_bool(skip, &err);
- curwin->w_cursor = save_pos;
- if (err) {
- /* Evaluating {skip} caused an error, break here. */
- curwin->w_cursor = save_cursor;
- retval = -1;
- break;
- }
- if (r)
- continue;
- }
-
- if ((dir == BACKWARD && n == 3) || (dir == FORWARD && n == 2)) {
- /* Found end when searching backwards or start when searching
- * forward: nested pair. */
- ++nest;
- pat = pat2; /* nested, don't search for middle */
- } else {
- /* Found end when searching forward or start when searching
- * backward: end of (nested) pair; or found middle in outer pair. */
- if (--nest == 1)
- pat = pat3; /* outer level, search for middle */
- }
-
- if (nest == 0) {
- /* Found the match: return matchcount or line number. */
- if (flags & SP_RETCOUNT)
- ++retval;
- else
- retval = pos.lnum;
- if (flags & SP_SETPCMARK)
- setpcmark();
- curwin->w_cursor = pos;
- if (!(flags & SP_REPEAT))
- break;
- nest = 1; /* search for next unmatched */
- }
- }
-
- if (match_pos != NULL) {
- /* Store the match cursor position */
- match_pos->lnum = curwin->w_cursor.lnum;
- match_pos->col = curwin->w_cursor.col + 1;
- }
-
- /* If 'n' flag is used or search failed: restore cursor position. */
- if ((flags & SP_NOMOVE) || retval == 0)
- curwin->w_cursor = save_cursor;
-
- xfree(pat2);
- xfree(pat3);
- if (p_cpo == empty_option)
- p_cpo = save_cpo;
- else
- /* Darn, evaluating the {skip} expression changed the value. */
- free_string_option(save_cpo);
-
- return retval;
-}
-
-/*
- * "searchpos()" function
- */
-static void f_searchpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- pos_T match_pos;
- int flags = 0;
-
- const int n = search_cmn(argvars, &match_pos, &flags);
-
- tv_list_alloc_ret(rettv, 2 + (!!(flags & SP_SUBPAT)));
-
- const int lnum = (n > 0 ? match_pos.lnum : 0);
- const int col = (n > 0 ? match_pos.col : 0);
-
- tv_list_append_number(rettv->vval.v_list, (varnumber_T)lnum);
- tv_list_append_number(rettv->vval.v_list, (varnumber_T)col);
- if (flags & SP_SUBPAT) {
- tv_list_append_number(rettv->vval.v_list, (varnumber_T)n);
- }
-}
-
-/// "serverlist()" function
-static void f_serverlist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- size_t n;
- char **addrs = server_address_list(&n);
-
- // Copy addrs into a linked list.
- list_T *const l = tv_list_alloc_ret(rettv, n);
- for (size_t i = 0; i < n; i++) {
- tv_list_append_allocated_string(l, addrs[i]);
- }
- xfree(addrs);
-}
-
-/// "serverstart()" function
-static void f_serverstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL; // Address of the new server
-
- if (check_restricted() || check_secure()) {
- return;
- }
-
- char *address;
- // If the user supplied an address, use it, otherwise use a temp.
- if (argvars[0].v_type != VAR_UNKNOWN) {
- if (argvars[0].v_type != VAR_STRING) {
- EMSG(_(e_invarg));
- return;
- } else {
- address = xstrdup(tv_get_string(argvars));
- }
- } else {
- address = server_address_new();
- }
-
- int result = server_start(address);
- xfree(address);
-
- if (result != 0) {
- EMSG2("Failed to start server: %s",
- result > 0 ? "Unknown system error" : uv_strerror(result));
- return;
- }
-
- // Since it's possible server_start adjusted the given {address} (e.g.,
- // "localhost:" will now have a port), return the final value to the user.
- size_t n;
- char **addrs = server_address_list(&n);
- rettv->vval.v_string = (char_u *)addrs[n - 1];
-
- n--;
- for (size_t i = 0; i < n; i++) {
- xfree(addrs[i]);
- }
- xfree(addrs);
-}
-
-/// "serverstop()" function
-static void f_serverstop(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (check_restricted() || check_secure()) {
- return;
- }
-
- if (argvars[0].v_type != VAR_STRING) {
- EMSG(_(e_invarg));
- return;
- }
-
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = 0;
- if (argvars[0].vval.v_string) {
- bool rv = server_stop((char *)argvars[0].vval.v_string);
- rettv->vval.v_number = (rv ? 1 : 0);
- }
-}
-
/// Set line or list of lines in buffer "buf".
-static void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append,
- const typval_T *lines, typval_T *rettv)
+void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append,
+ const typval_T *lines, typval_T *rettv)
FUNC_ATTR_NONNULL_ARG(4, 5)
{
linenr_T lnum = lnum_arg + (append ? 1 : 0);
@@ -15646,632 +6923,11 @@ static void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append,
}
}
-/// "setbufline()" function
-static void f_setbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- linenr_T lnum;
- buf_T *buf;
-
- buf = tv_get_buf(&argvars[0], false);
- if (buf == NULL) {
- rettv->vval.v_number = 1; // FAIL
- } else {
- lnum = tv_get_lnum_buf(&argvars[1], buf);
- set_buffer_lines(buf, lnum, false, &argvars[2], rettv);
- }
-}
-
-/*
- * "setbufvar()" function
- */
-static void f_setbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (check_restricted()
- || check_secure()
- || !tv_check_str_or_nr(&argvars[0])) {
- return;
- }
- const char *varname = tv_get_string_chk(&argvars[1]);
- buf_T *const buf = tv_get_buf(&argvars[0], false);
- typval_T *varp = &argvars[2];
-
- if (buf != NULL && varname != NULL) {
- if (*varname == '&') {
- long numval;
- bool error = false;
- aco_save_T aco;
-
- // set curbuf to be our buf, temporarily
- aucmd_prepbuf(&aco, buf);
-
- varname++;
- numval = tv_get_number_chk(varp, &error);
- char nbuf[NUMBUFLEN];
- const char *const strval = tv_get_string_buf_chk(varp, nbuf);
- if (!error && strval != NULL) {
- set_option_value(varname, numval, strval, OPT_LOCAL);
- }
-
- // reset notion of buffer
- aucmd_restbuf(&aco);
- } else {
- buf_T *save_curbuf = curbuf;
-
- const size_t varname_len = STRLEN(varname);
- char *const bufvarname = xmalloc(varname_len + 3);
- curbuf = buf;
- memcpy(bufvarname, "b:", 2);
- memcpy(bufvarname + 2, varname, varname_len + 1);
- set_var(bufvarname, varname_len + 2, varp, true);
- xfree(bufvarname);
- curbuf = save_curbuf;
- }
- }
-}
-
-static void f_setcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- dict_T *d;
- dictitem_T *di;
-
- if (argvars[0].v_type != VAR_DICT) {
- EMSG(_(e_dictreq));
- return;
- }
-
- if ((d = argvars[0].vval.v_dict) != NULL) {
- char_u *const csearch = (char_u *)tv_dict_get_string(d, "char", false);
- if (csearch != NULL) {
- if (enc_utf8) {
- int pcc[MAX_MCO];
- int c = utfc_ptr2char(csearch, pcc);
- set_last_csearch(c, csearch, utfc_ptr2len(csearch));
- }
- else
- set_last_csearch(PTR2CHAR(csearch),
- csearch, utfc_ptr2len(csearch));
- }
-
- di = tv_dict_find(d, S_LEN("forward"));
- if (di != NULL) {
- set_csearch_direction(tv_get_number(&di->di_tv) ? FORWARD : BACKWARD);
- }
-
- di = tv_dict_find(d, S_LEN("until"));
- if (di != NULL) {
- set_csearch_until(!!tv_get_number(&di->di_tv));
- }
- }
-}
-
-/*
- * "setcmdpos()" function
- */
-static void f_setcmdpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const int pos = (int)tv_get_number(&argvars[0]) - 1;
-
- if (pos >= 0) {
- rettv->vval.v_number = set_cmdline_pos(pos);
- }
-}
-
-/// "setenv()" function
-static void f_setenv(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char namebuf[NUMBUFLEN];
- char valbuf[NUMBUFLEN];
- const char *name = tv_get_string_buf(&argvars[0], namebuf);
-
- if (argvars[1].v_type == VAR_SPECIAL
- && argvars[1].vval.v_special == kSpecialVarNull) {
- os_unsetenv(name);
- } else {
- os_setenv(name, tv_get_string_buf(&argvars[1], valbuf), 1);
- }
-}
-
-/// "setfperm({fname}, {mode})" function
-static void f_setfperm(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = 0;
-
- const char *const fname = tv_get_string_chk(&argvars[0]);
- if (fname == NULL) {
- return;
- }
-
- char modebuf[NUMBUFLEN];
- const char *const mode_str = tv_get_string_buf_chk(&argvars[1], modebuf);
- if (mode_str == NULL) {
- return;
- }
- if (strlen(mode_str) != 9) {
- EMSG2(_(e_invarg2), mode_str);
- return;
- }
-
- int mask = 1;
- int mode = 0;
- for (int i = 8; i >= 0; i--) {
- if (mode_str[i] != '-') {
- mode |= mask;
- }
- mask = mask << 1;
- }
- rettv->vval.v_number = os_setperm(fname, mode) == OK;
-}
-
-/*
- * "setline()" function
- */
-static void f_setline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- linenr_T lnum = tv_get_lnum(&argvars[0]);
- set_buffer_lines(curbuf, lnum, false, &argvars[1], rettv);
-}
-
-/// Create quickfix/location list from VimL values
-///
-/// Used by `setqflist()` and `setloclist()` functions. Accepts invalid
-/// list_arg, action_arg and what_arg arguments in which case errors out,
-/// including VAR_UNKNOWN parameters.
-///
-/// @param[in,out] wp Window to create location list for. May be NULL in
-/// which case quickfix list will be created.
-/// @param[in] list_arg Quickfix list contents.
-/// @param[in] action_arg Action to perform: append to an existing list,
-/// replace its content or create a new one.
-/// @param[in] title_arg New list title. Defaults to caller function name.
-/// @param[out] rettv Return value: 0 in case of success, -1 otherwise.
-static void set_qf_ll_list(win_T *wp, typval_T *args, typval_T *rettv)
- FUNC_ATTR_NONNULL_ARG(2, 3)
-{
- static char *e_invact = N_("E927: Invalid action: '%s'");
- const char *title = NULL;
- int action = ' ';
- static int recursive = 0;
- rettv->vval.v_number = -1;
- dict_T *d = NULL;
-
- typval_T *list_arg = &args[0];
- if (list_arg->v_type != VAR_LIST) {
- EMSG(_(e_listreq));
- return;
- } else if (recursive != 0) {
- EMSG(_(e_au_recursive));
- return;
- }
-
- typval_T *action_arg = &args[1];
- if (action_arg->v_type == VAR_UNKNOWN) {
- // Option argument was not given.
- goto skip_args;
- } else if (action_arg->v_type != VAR_STRING) {
- EMSG(_(e_stringreq));
- return;
- }
- const char *const act = tv_get_string_chk(action_arg);
- if ((*act == 'a' || *act == 'r' || *act == ' ' || *act == 'f')
- && act[1] == NUL) {
- action = *act;
- } else {
- EMSG2(_(e_invact), act);
- return;
- }
-
- typval_T *title_arg = &args[2];
- if (title_arg->v_type == VAR_UNKNOWN) {
- // Option argument was not given.
- goto skip_args;
- } else if (title_arg->v_type == VAR_STRING) {
- title = tv_get_string_chk(title_arg);
- if (!title) {
- // Type error. Error already printed by tv_get_string_chk().
- return;
- }
- } else if (title_arg->v_type == VAR_DICT) {
- d = title_arg->vval.v_dict;
- } else {
- EMSG(_(e_dictreq));
- return;
- }
-
-skip_args:
- if (!title) {
- title = (wp ? ":setloclist()" : ":setqflist()");
- }
-
- recursive++;
- list_T *const l = list_arg->vval.v_list;
- if (set_errorlist(wp, l, action, (char_u *)title, d) == OK) {
- rettv->vval.v_number = 0;
- }
- recursive--;
-}
-
-/*
- * "setloclist()" function
- */
-static void f_setloclist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- win_T *win;
-
- rettv->vval.v_number = -1;
-
- win = find_win_by_nr_or_id(&argvars[0]);
- if (win != NULL) {
- set_qf_ll_list(win, &argvars[1], rettv);
- }
-}
-
-/*
- * "setmatches()" function
- */
-static void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- dict_T *d;
- list_T *s = NULL;
-
- rettv->vval.v_number = -1;
- if (argvars[0].v_type != VAR_LIST) {
- EMSG(_(e_listreq));
- 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) {
- emsgf(_("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)) {
- emsgf(_("E474: List item %d is missing one of the required keys"),
- li_idx);
- return;
- }
- li_idx++;
- });
-
- clear_matches(curwin);
- 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(curwin, group,
- tv_dict_get_string(d, "pattern", false),
- priority, id, NULL, conceal) != id) {
- match_add_failed = true;
- }
- } else {
- if (match_add(curwin, 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;
- }
-}
-
-/*
- * "setpos()" function
- */
-static void f_setpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- pos_T pos;
- int fnum;
- colnr_T curswant = -1;
-
- rettv->vval.v_number = -1;
- const char *const name = tv_get_string_chk(argvars);
- if (name != NULL) {
- if (list2fpos(&argvars[1], &pos, &fnum, &curswant) == OK) {
- if (pos.col != MAXCOL && --pos.col < 0) {
- pos.col = 0;
- }
- if (name[0] == '.' && name[1] == NUL) {
- // set cursor; "fnum" is ignored
- curwin->w_cursor = pos;
- if (curswant >= 0) {
- curwin->w_curswant = curswant - 1;
- curwin->w_set_curswant = false;
- }
- check_cursor();
- rettv->vval.v_number = 0;
- } else if (name[0] == '\'' && name[1] != NUL && name[2] == NUL) {
- // set mark
- if (setmark_pos((uint8_t)name[1], &pos, fnum) == OK) {
- rettv->vval.v_number = 0;
- }
- } else {
- EMSG(_(e_invarg));
- }
- }
- }
-}
-
-/*
- * "setqflist()" function
- */
-static void f_setqflist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- set_qf_ll_list(NULL, argvars, rettv);
-}
-
-/*
- * "setreg()" function
- */
-static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int regname;
- bool append = false;
- MotionType yank_type;
- long block_len;
-
- block_len = -1;
- yank_type = kMTUnknown;
-
- rettv->vval.v_number = 1; // FAIL is default.
-
- const char *const strregname = tv_get_string_chk(argvars);
- if (strregname == NULL) {
- return; // Type error; errmsg already given.
- }
- regname = (uint8_t)(*strregname);
- if (regname == 0 || regname == '@') {
- regname = '"';
- }
-
- bool set_unnamed = false;
- if (argvars[2].v_type != VAR_UNKNOWN) {
- const char *stropt = tv_get_string_chk(&argvars[2]);
- if (stropt == NULL) {
- return; // Type error.
- }
- for (; *stropt != NUL; stropt++) {
- switch (*stropt) {
- case 'a': case 'A': { // append
- append = true;
- break;
- }
- case 'v': case 'c': { // character-wise selection
- yank_type = kMTCharWise;
- break;
- }
- case 'V': case 'l': { // line-wise selection
- yank_type = kMTLineWise;
- break;
- }
- case 'b': case Ctrl_V: { // block-wise selection
- yank_type = kMTBlockWise;
- if (ascii_isdigit(stropt[1])) {
- stropt++;
- block_len = getdigits_long((char_u **)&stropt, true, 0) - 1;
- stropt--;
- }
- break;
- }
- case 'u': case '"': { // unnamed register
- set_unnamed = true;
- break;
- }
- }
- }
- }
-
- if (argvars[1].v_type == VAR_LIST) {
- list_T *ll = argvars[1].vval.v_list;
- // If the list is NULL handle like an empty list.
- const int len = tv_list_len(ll);
-
- // First half: use for pointers to result lines; second half: use for
- // pointers to allocated copies.
- char **lstval = xmalloc(sizeof(char *) * ((len + 1) * 2));
- const char **curval = (const char **)lstval;
- char **allocval = lstval + len + 2;
- char **curallocval = allocval;
-
- TV_LIST_ITER_CONST(ll, li, {
- char buf[NUMBUFLEN];
- *curval = tv_get_string_buf_chk(TV_LIST_ITEM_TV(li), buf);
- if (*curval == NULL) {
- goto free_lstval;
- }
- if (*curval == buf) {
- // Need to make a copy,
- // next tv_get_string_buf_chk() will overwrite the string.
- *curallocval = xstrdup(*curval);
- *curval = *curallocval;
- curallocval++;
- }
- curval++;
- });
- *curval++ = NULL;
-
- write_reg_contents_lst(regname, (char_u **)lstval, append, yank_type,
- block_len);
-
-free_lstval:
- while (curallocval > allocval) {
- xfree(*--curallocval);
- }
- xfree(lstval);
- } else {
- const char *strval = tv_get_string_chk(&argvars[1]);
- if (strval == NULL) {
- return;
- }
- write_reg_contents_ex(regname, (const char_u *)strval, STRLEN(strval),
- append, yank_type, block_len);
- }
- rettv->vval.v_number = 0;
-
- if (set_unnamed) {
- // Discard the result. We already handle the error case.
- if (op_reg_set_previous(regname)) { }
- }
-}
-
-/*
- * "settabvar()" function
- */
-static void f_settabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = 0;
-
- if (check_restricted() || check_secure()) {
- return;
- }
-
- tabpage_T *const tp = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL));
- const char *const varname = tv_get_string_chk(&argvars[1]);
- typval_T *const varp = &argvars[2];
-
- if (varname != NULL && tp != NULL) {
- tabpage_T *const save_curtab = curtab;
- goto_tabpage_tp(tp, false, false);
-
- const size_t varname_len = strlen(varname);
- char *const tabvarname = xmalloc(varname_len + 3);
- memcpy(tabvarname, "t:", 2);
- memcpy(tabvarname + 2, varname, varname_len + 1);
- set_var(tabvarname, varname_len + 2, varp, true);
- xfree(tabvarname);
-
- // Restore current tabpage.
- if (valid_tabpage(save_curtab)) {
- goto_tabpage_tp(save_curtab, false, false);
- }
- }
-}
-
-/*
- * "settabwinvar()" function
- */
-static void f_settabwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- setwinvar(argvars, rettv, 1);
-}
-
-// "settagstack()" function
-static void f_settagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- static char *e_invact2 = N_("E962: Invalid action: '%s'");
- win_T *wp;
- dict_T *d;
- int action = 'r';
-
- rettv->vval.v_number = -1;
-
- // first argument: window number or id
- wp = find_win_by_nr_or_id(&argvars[0]);
- if (wp == NULL) {
- return;
- }
-
- // second argument: dict with items to set in the tag stack
- if (argvars[1].v_type != VAR_DICT) {
- EMSG(_(e_dictreq));
- return;
- }
- d = argvars[1].vval.v_dict;
- if (d == NULL) {
- return;
- }
-
- // third argument: action - 'a' for append and 'r' for replace.
- // default is to replace the stack.
- if (argvars[2].v_type == VAR_UNKNOWN) {
- action = 'r';
- } else if (argvars[2].v_type == VAR_STRING) {
- const char *actstr;
- actstr = tv_get_string_chk(&argvars[2]);
- if (actstr == NULL) {
- return;
- }
- if ((*actstr == 'r' || *actstr == 'a') && actstr[1] == NUL) {
- action = *actstr;
- } else {
- EMSG2(_(e_invact2), actstr);
- return;
- }
- } else {
- EMSG(_(e_stringreq));
- return;
- }
-
- if (set_tagstack(wp, d, action) == OK) {
- rettv->vval.v_number = 0;
- } else {
- EMSG(_(e_listreq));
- }
-}
-
-/*
- * "setwinvar()" function
- */
-static void f_setwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- setwinvar(argvars, rettv, 0);
-}
-
/*
* "setwinvar()" and "settabwinvar()" functions
*/
-static void setwinvar(typval_T *argvars, typval_T *rettv, int off)
+void setwinvar(typval_T *argvars, typval_T *rettv, int off)
{
if (check_secure()) {
return;
@@ -16319,990 +6975,8 @@ static void setwinvar(typval_T *argvars, typval_T *rettv, int off)
}
}
-/// f_sha256 - sha256({string}) function
-static void f_sha256(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *p = tv_get_string(&argvars[0]);
- const char *hash = sha256_bytes((const uint8_t *)p, strlen(p) , NULL, 0);
-
- // make a copy of the hash (sha256_bytes returns a static buffer)
- rettv->vval.v_string = (char_u *)xstrdup(hash);
- rettv->v_type = VAR_STRING;
-}
-
-/*
- * "shellescape({string})" function
- */
-static void f_shellescape(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const bool do_special = non_zero_arg(&argvars[1]);
-
- rettv->vval.v_string = vim_strsave_shellescape(
- (const char_u *)tv_get_string(&argvars[0]), do_special, do_special);
- rettv->v_type = VAR_STRING;
-}
-
-/*
- * shiftwidth() function
- */
-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;
- dict_T *dict;
- char *icon = NULL;
- char *linehl = NULL;
- char *text = NULL;
- char *texthl = NULL;
- char *numhl = NULL;
-
- rettv->vval.v_number = -1;
-
- name = tv_get_string_chk(&argvars[0]);
- if (name == NULL) {
- return;
- }
-
- if (argvars[1].v_type != VAR_UNKNOWN) {
- if (argvars[1].v_type != VAR_DICT) {
- EMSG(_(e_dictreq));
- return;
- }
-
- // sign attributes
- dict = argvars[1].vval.v_dict;
- if (tv_dict_find(dict, "icon", -1) != NULL) {
- icon = tv_dict_get_string(dict, "icon", true);
- }
- if (tv_dict_find(dict, "linehl", -1) != NULL) {
- linehl = tv_dict_get_string(dict, "linehl", true);
- }
- if (tv_dict_find(dict, "text", -1) != NULL) {
- text = tv_dict_get_string(dict, "text", true);
- }
- if (tv_dict_find(dict, "texthl", -1) != NULL) {
- texthl = tv_dict_get_string(dict, "texthl", true);
- }
- if (tv_dict_find(dict, "numhl", -1) != NULL) {
- numhl = tv_dict_get_string(dict, "numhl", true);
- }
- }
-
- if (sign_define_by_name((char_u *)name, (char_u *)icon, (char_u *)linehl,
- (char_u *)text, (char_u *)texthl, (char_u *)numhl)
- == OK) {
- rettv->vval.v_number = 0;
- }
-
- xfree(icon);
- xfree(linehl);
- xfree(text);
- xfree(texthl);
-}
-
-/// "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 identifer
- 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)
-{
- int sign_id;
- char_u *group = NULL;
- const char *sign_name;
- buf_T *buf;
- dict_T *dict;
- dictitem_T *di;
- linenr_T lnum = 0;
- int prio = SIGN_DEF_PRIO;
- bool notanum = false;
-
- rettv->vval.v_number = -1;
-
- // Sign identifer
- 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 *group_chk = tv_get_string_chk(&argvars[1]);
- if (group_chk == NULL) {
- return;
- }
- if (group_chk[0] == '\0') {
- group = NULL; // global sign group
- } else {
- group = vim_strsave((const char_u *)group_chk);
- }
-
- // Sign name
- sign_name = tv_get_string_chk(&argvars[2]);
- if (sign_name == NULL) {
- goto cleanup;
- }
-
- // Buffer to place the sign
- buf = get_buf_arg(&argvars[3]);
- if (buf == NULL) {
- goto cleanup;
- }
-
- if (argvars[4].v_type != VAR_UNKNOWN) {
- if (argvars[4].v_type != VAR_DICT
- || ((dict = argvars[4].vval.v_dict) == NULL)) {
- EMSG(_(e_dictreq));
- goto cleanup;
- }
-
- // Line number where the sign is to be placed
- if ((di = tv_dict_find(dict, "lnum", -1)) != NULL) {
- lnum = (linenr_T)tv_get_number_chk(&di->di_tv, &notanum);
- if (notanum) {
- goto cleanup;
- }
- (void)lnum;
- lnum = tv_get_lnum(&di->di_tv);
- }
- if ((di = tv_dict_find(dict, "priority", -1)) != NULL) {
- // Sign priority
- prio = (int)tv_get_number_chk(&di->di_tv, &notanum);
- if (notanum) {
- goto cleanup;
- }
- }
- }
-
- if (sign_place(&sign_id, group, (const char_u *)sign_name, buf, lnum, prio)
- == OK) {
- rettv->vval.v_number = sign_id;
- }
-
-cleanup:
- xfree(group);
-}
-
-/// "sign_undefine()" function
-static void f_sign_undefine(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *name;
-
- 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;
- dictitem_T *di;
- int sign_id = 0;
- buf_T *buf = NULL;
- char_u *group = NULL;
-
- rettv->vval.v_number = -1;
-
- if (argvars[0].v_type != VAR_STRING) {
- EMSG(_(e_invarg));
- return;
- }
-
- const char *group_chk = tv_get_string(&argvars[0]);
- if (group_chk[0] == '\0') {
- group = NULL; // global sign group
- } else {
- group = vim_strsave((const char_u *)group_chk);
- }
-
- if (argvars[1].v_type != VAR_UNKNOWN) {
- if (argvars[1].v_type != VAR_DICT) {
- EMSG(_(e_dictreq));
- goto cleanup;
- }
- dict = argvars[1].vval.v_dict;
-
- if ((di = tv_dict_find(dict, "buffer", -1)) != NULL) {
- buf = get_buf_arg(&di->di_tv);
- if (buf == NULL) {
- goto cleanup;
- }
- }
- if (tv_dict_find(dict, "id", -1) != NULL) {
- sign_id = tv_dict_get_number(dict, "id");
- }
- }
-
- if (buf == NULL) {
- // Delete the sign in all the buffers
- FOR_ALL_BUFFERS(cbuf) {
- if (sign_unplace(sign_id, group, cbuf, 0) == OK) {
- rettv->vval.v_number = 0;
- }
- }
- } else {
- if (sign_unplace(sign_id, group, buf, 0) == OK) {
- rettv->vval.v_number = 0;
- }
- }
-
-cleanup:
- xfree(group);
-}
-
-/*
- * "simplify()" function
- */
-static void f_simplify(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *const p = tv_get_string(&argvars[0]);
- rettv->vval.v_string = (char_u *)xstrdup(p);
- simplify_filename(rettv->vval.v_string); // Simplify in place.
- rettv->v_type = VAR_STRING;
-}
-
-/// "sockconnect()" function
-static void f_sockconnect(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (argvars[0].v_type != VAR_STRING || argvars[1].v_type != VAR_STRING) {
- EMSG(_(e_invarg));
- return;
- }
- if (argvars[2].v_type != VAR_DICT && argvars[2].v_type != VAR_UNKNOWN) {
- // Wrong argument types
- EMSG2(_(e_invarg2), "expected dictionary");
- return;
- }
-
- const char *mode = tv_get_string(&argvars[0]);
- const char *address = tv_get_string(&argvars[1]);
-
- bool tcp;
- if (strcmp(mode, "tcp") == 0) {
- tcp = true;
- } else if (strcmp(mode, "pipe") == 0) {
- tcp = false;
- } else {
- EMSG2(_(e_invarg2), "invalid mode");
- return;
- }
-
- bool rpc = false;
- CallbackReader on_data = CALLBACK_READER_INIT;
- if (argvars[2].v_type == VAR_DICT) {
- dict_T *opts = argvars[2].vval.v_dict;
- rpc = tv_dict_get_number(opts, "rpc") != 0;
-
- if (!tv_dict_get_callback(opts, S_LEN("on_data"), &on_data.cb)) {
- return;
- }
- on_data.buffered = tv_dict_get_number(opts, "data_buffered");
- if (on_data.buffered && on_data.cb.type == kCallbackNone) {
- on_data.self = opts;
- }
- }
-
- const char *error = NULL;
- uint64_t id = channel_connect(tcp, address, rpc, on_data, 50, &error);
-
- if (error) {
- EMSG2(_("connection failed: %s"), error);
- }
-
- rettv->vval.v_number = (varnumber_T)id;
- rettv->v_type = VAR_NUMBER;
-}
-
-/// struct storing information about current sort
-typedef struct {
- int item_compare_ic;
- bool item_compare_numeric;
- bool item_compare_numbers;
- bool item_compare_float;
- const char *item_compare_func;
- partial_T *item_compare_partial;
- dict_T *item_compare_selfdict;
- bool item_compare_func_err;
-} sortinfo_T;
-static sortinfo_T *sortinfo = NULL;
-
-#define ITEM_COMPARE_FAIL 999
-
-/*
- * Compare functions for f_sort() and f_uniq() below.
- */
-static int item_compare(const void *s1, const void *s2, bool keep_zero)
-{
- ListSortItem *const si1 = (ListSortItem *)s1;
- ListSortItem *const si2 = (ListSortItem *)s2;
-
- typval_T *const tv1 = TV_LIST_ITEM_TV(si1->item);
- typval_T *const tv2 = TV_LIST_ITEM_TV(si2->item);
-
- int res;
-
- if (sortinfo->item_compare_numbers) {
- const varnumber_T v1 = tv_get_number(tv1);
- const varnumber_T v2 = tv_get_number(tv2);
-
- res = v1 == v2 ? 0 : v1 > v2 ? 1 : -1;
- goto item_compare_end;
- }
-
- if (sortinfo->item_compare_float) {
- const float_T v1 = tv_get_float(tv1);
- const float_T v2 = tv_get_float(tv2);
-
- res = v1 == v2 ? 0 : v1 > v2 ? 1 : -1;
- goto item_compare_end;
- }
-
- char *tofree1 = NULL;
- char *tofree2 = NULL;
- char *p1;
- char *p2;
-
- // encode_tv2string() puts quotes around a string and allocates memory. Don't
- // do that for string variables. Use a single quote when comparing with
- // a non-string to do what the docs promise.
- if (tv1->v_type == VAR_STRING) {
- if (tv2->v_type != VAR_STRING || sortinfo->item_compare_numeric) {
- p1 = "'";
- } else {
- p1 = (char *)tv1->vval.v_string;
- }
- } else {
- tofree1 = p1 = encode_tv2string(tv1, NULL);
- }
- if (tv2->v_type == VAR_STRING) {
- if (tv1->v_type != VAR_STRING || sortinfo->item_compare_numeric) {
- p2 = "'";
- } else {
- p2 = (char *)tv2->vval.v_string;
- }
- } else {
- tofree2 = p2 = encode_tv2string(tv2, NULL);
- }
- if (p1 == NULL) {
- p1 = "";
- }
- if (p2 == NULL) {
- p2 = "";
- }
- if (!sortinfo->item_compare_numeric) {
- if (sortinfo->item_compare_ic) {
- res = STRICMP(p1, p2);
- } else {
- res = STRCMP(p1, p2);
- }
- } else {
- double n1, n2;
- n1 = strtod(p1, &p1);
- n2 = strtod(p2, &p2);
- res = n1 == n2 ? 0 : n1 > n2 ? 1 : -1;
- }
-
- xfree(tofree1);
- xfree(tofree2);
-
-item_compare_end:
- // When the result would be zero, compare the item indexes. Makes the
- // sort stable.
- if (res == 0 && !keep_zero) {
- // WARNING: When using uniq si1 and si2 are actually listitem_T **, no
- // indexes are there.
- res = si1->idx > si2->idx ? 1 : -1;
- }
- return res;
-}
-
-static int item_compare_keeping_zero(const void *s1, const void *s2)
-{
- return item_compare(s1, s2, true);
-}
-
-static int item_compare_not_keeping_zero(const void *s1, const void *s2)
-{
- return item_compare(s1, s2, false);
-}
-
-static int item_compare2(const void *s1, const void *s2, bool keep_zero)
-{
- ListSortItem *si1, *si2;
- int res;
- typval_T rettv;
- typval_T argv[3];
- int dummy;
- const char *func_name;
- partial_T *partial = sortinfo->item_compare_partial;
-
- // shortcut after failure in previous call; compare all items equal
- if (sortinfo->item_compare_func_err) {
- return 0;
- }
-
- si1 = (ListSortItem *)s1;
- si2 = (ListSortItem *)s2;
-
- if (partial == NULL) {
- func_name = sortinfo->item_compare_func;
- } else {
- func_name = (const char *)partial_name(partial);
- }
-
- // Copy the values. This is needed to be able to set v_lock to VAR_FIXED
- // in the copy without changing the original list items.
- tv_copy(TV_LIST_ITEM_TV(si1->item), &argv[0]);
- tv_copy(TV_LIST_ITEM_TV(si2->item), &argv[1]);
-
- rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this
- res = call_func((const char_u *)func_name,
- (int)STRLEN(func_name),
- &rettv, 2, argv, NULL, 0L, 0L, &dummy, true,
- partial, sortinfo->item_compare_selfdict);
- tv_clear(&argv[0]);
- tv_clear(&argv[1]);
-
- if (res == FAIL) {
- res = ITEM_COMPARE_FAIL;
- } else {
- res = tv_get_number_chk(&rettv, &sortinfo->item_compare_func_err);
- }
- if (sortinfo->item_compare_func_err) {
- res = ITEM_COMPARE_FAIL; // return value has wrong type
- }
- tv_clear(&rettv);
-
- // When the result would be zero, compare the pointers themselves. Makes
- // the sort stable.
- if (res == 0 && !keep_zero) {
- // WARNING: When using uniq si1 and si2 are actually listitem_T **, no
- // indexes are there.
- res = si1->idx > si2->idx ? 1 : -1;
- }
-
- return res;
-}
-
-static int item_compare2_keeping_zero(const void *s1, const void *s2)
-{
- return item_compare2(s1, s2, true);
-}
-
-static int item_compare2_not_keeping_zero(const void *s1, const void *s2)
-{
- return item_compare2(s1, s2, false);
-}
-
-/*
- * "sort({list})" function
- */
-static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort)
-{
- ListSortItem *ptrs;
- long len;
- long i;
-
- // Pointer to current info struct used in compare function. Save and restore
- // the current one for nested calls.
- sortinfo_T info;
- sortinfo_T *old_sortinfo = sortinfo;
- sortinfo = &info;
-
- const char *const arg_errmsg = (sort
- ? N_("sort() argument")
- : N_("uniq() argument"));
-
- if (argvars[0].v_type != VAR_LIST) {
- EMSG2(_(e_listarg), sort ? "sort()" : "uniq()");
- } else {
- list_T *const l = argvars[0].vval.v_list;
- if (tv_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE)) {
- goto theend;
- }
- tv_list_set_ret(rettv, l);
-
- len = tv_list_len(l);
- if (len <= 1) {
- goto theend; // short list sorts pretty quickly
- }
-
- info.item_compare_ic = false;
- info.item_compare_numeric = false;
- info.item_compare_numbers = false;
- info.item_compare_float = false;
- info.item_compare_func = NULL;
- info.item_compare_partial = NULL;
- info.item_compare_selfdict = NULL;
-
- if (argvars[1].v_type != VAR_UNKNOWN) {
- /* optional second argument: {func} */
- if (argvars[1].v_type == VAR_FUNC) {
- info.item_compare_func = (const char *)argvars[1].vval.v_string;
- } else if (argvars[1].v_type == VAR_PARTIAL) {
- info.item_compare_partial = argvars[1].vval.v_partial;
- } else {
- bool error = false;
-
- i = tv_get_number_chk(&argvars[1], &error);
- if (error) {
- goto theend; // type error; errmsg already given
- }
- if (i == 1) {
- info.item_compare_ic = true;
- } else if (argvars[1].v_type != VAR_NUMBER) {
- info.item_compare_func = tv_get_string(&argvars[1]);
- } else if (i != 0) {
- EMSG(_(e_invarg));
- goto theend;
- }
- if (info.item_compare_func != NULL) {
- if (*info.item_compare_func == NUL) {
- // empty string means default sort
- info.item_compare_func = NULL;
- } else if (strcmp(info.item_compare_func, "n") == 0) {
- info.item_compare_func = NULL;
- info.item_compare_numeric = true;
- } else if (strcmp(info.item_compare_func, "N") == 0) {
- info.item_compare_func = NULL;
- info.item_compare_numbers = true;
- } else if (strcmp(info.item_compare_func, "f") == 0) {
- info.item_compare_func = NULL;
- info.item_compare_float = true;
- } else if (strcmp(info.item_compare_func, "i") == 0) {
- info.item_compare_func = NULL;
- info.item_compare_ic = true;
- }
- }
- }
-
- if (argvars[2].v_type != VAR_UNKNOWN) {
- // optional third argument: {dict}
- if (argvars[2].v_type != VAR_DICT) {
- EMSG(_(e_dictreq));
- goto theend;
- }
- info.item_compare_selfdict = argvars[2].vval.v_dict;
- }
- }
-
- // Make an array with each entry pointing to an item in the List.
- ptrs = xmalloc((size_t)(len * sizeof(ListSortItem)));
-
- if (sort) {
- info.item_compare_func_err = false;
- tv_list_item_sort(l, ptrs,
- ((info.item_compare_func == NULL
- && info.item_compare_partial == NULL)
- ? item_compare_not_keeping_zero
- : item_compare2_not_keeping_zero),
- &info.item_compare_func_err);
- if (info.item_compare_func_err) {
- EMSG(_("E702: Sort compare function failed"));
- }
- } else {
- ListSorter item_compare_func_ptr;
-
- // f_uniq(): ptrs will be a stack of items to remove.
- info.item_compare_func_err = false;
- if (info.item_compare_func != NULL
- || info.item_compare_partial != NULL) {
- item_compare_func_ptr = item_compare2_keeping_zero;
- } else {
- item_compare_func_ptr = item_compare_keeping_zero;
- }
-
- int idx = 0;
- for (listitem_T *li = TV_LIST_ITEM_NEXT(l, tv_list_first(l))
- ; li != NULL;) {
- listitem_T *const prev_li = TV_LIST_ITEM_PREV(l, li);
- if (item_compare_func_ptr(&prev_li, &li) == 0) {
- if (info.item_compare_func_err) { // -V547
- EMSG(_("E882: Uniq compare function failed"));
- break;
- }
- li = tv_list_item_remove(l, li);
- } else {
- idx++;
- li = TV_LIST_ITEM_NEXT(l, li);
- }
- }
- }
-
- xfree(ptrs);
- }
-
-theend:
- sortinfo = old_sortinfo;
-}
-
-/// "sort"({list})" function
-static void f_sort(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- do_sort_uniq(argvars, rettv, true);
-}
-
-/// "stdioopen()" function
-static void f_stdioopen(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (argvars[0].v_type != VAR_DICT) {
- EMSG(_(e_invarg));
- return;
- }
-
-
- bool rpc = false;
- CallbackReader on_stdin = CALLBACK_READER_INIT;
- dict_T *opts = argvars[0].vval.v_dict;
- rpc = tv_dict_get_number(opts, "rpc") != 0;
-
- if (!tv_dict_get_callback(opts, S_LEN("on_stdin"), &on_stdin.cb)) {
- return;
- }
- on_stdin.buffered = tv_dict_get_number(opts, "stdin_buffered");
- if (on_stdin.buffered && on_stdin.cb.type == kCallbackNone) {
- on_stdin.self = opts;
- }
-
- const char *error;
- uint64_t id = channel_from_stdio(rpc, on_stdin, &error);
- if (!id) {
- EMSG2(e_stdiochan2, error);
- }
-
-
- rettv->vval.v_number = (varnumber_T)id;
- rettv->v_type = VAR_NUMBER;
-}
-
-/// "uniq({list})" function
-static void f_uniq(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- do_sort_uniq(argvars, rettv, false);
-}
-
-// "reltimefloat()" function
-static void f_reltimefloat(typval_T *argvars , typval_T *rettv, FunPtr fptr)
- FUNC_ATTR_NONNULL_ALL
-{
- proftime_T tm;
-
- rettv->v_type = VAR_FLOAT;
- rettv->vval.v_float = 0;
- if (list2proftime(&argvars[0], &tm) == OK) {
- rettv->vval.v_float = (float_T)profile_signed(tm) / 1000000000.0;
- }
-}
-
-/*
- * "soundfold({word})" function
- */
-static void f_soundfold(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_STRING;
- const char *const s = tv_get_string(&argvars[0]);
- rettv->vval.v_string = (char_u *)eval_soundfold(s);
-}
-
-/*
- * "spellbadword()" function
- */
-static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *word = "";
- hlf_T attr = HLF_COUNT;
- size_t len = 0;
-
- if (argvars[0].v_type == VAR_UNKNOWN) {
- // Find the start and length of the badly spelled word.
- len = spell_move_to(curwin, FORWARD, true, true, &attr);
- if (len != 0) {
- word = (char *)get_cursor_pos_ptr();
- curwin->w_set_curswant = true;
- }
- } else if (curwin->w_p_spell && *curbuf->b_s.b_p_spl != NUL) {
- const char *str = tv_get_string_chk(&argvars[0]);
- int capcol = -1;
-
- if (str != NULL) {
- // Check the argument for spelling.
- while (*str != NUL) {
- len = spell_check(curwin, (char_u *)str, &attr, &capcol, false);
- if (attr != HLF_COUNT) {
- word = str;
- break;
- }
- str += len;
- capcol -= len;
- len = 0;
- }
- }
- }
-
- assert(len <= INT_MAX);
- tv_list_alloc_ret(rettv, 2);
- tv_list_append_string(rettv->vval.v_list, word, len);
- tv_list_append_string(rettv->vval.v_list,
- (attr == HLF_SPB ? "bad"
- : attr == HLF_SPR ? "rare"
- : attr == HLF_SPL ? "local"
- : attr == HLF_SPC ? "caps"
- : NULL), -1);
-}
-
-/*
- * "spellsuggest()" function
- */
-static void f_spellsuggest(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- bool typeerr = false;
- int maxcount;
- garray_T ga = GA_EMPTY_INIT_VALUE;
- bool need_capital = false;
-
- if (curwin->w_p_spell && *curwin->w_s->b_p_spl != NUL) {
- const char *const str = tv_get_string(&argvars[0]);
- if (argvars[1].v_type != VAR_UNKNOWN) {
- maxcount = tv_get_number_chk(&argvars[1], &typeerr);
- if (maxcount <= 0) {
- goto f_spellsuggest_return;
- }
- if (argvars[2].v_type != VAR_UNKNOWN) {
- need_capital = tv_get_number_chk(&argvars[2], &typeerr);
- if (typeerr) {
- goto f_spellsuggest_return;
- }
- }
- } else {
- maxcount = 25;
- }
-
- spell_suggest_list(&ga, (char_u *)str, maxcount, need_capital, false);
- }
-
-f_spellsuggest_return:
- tv_list_alloc_ret(rettv, (ptrdiff_t)ga.ga_len);
- for (int i = 0; i < ga.ga_len; i++) {
- char *const p = ((char **)ga.ga_data)[i];
- tv_list_append_allocated_string(rettv->vval.v_list, p);
- }
- ga_clear(&ga);
-}
-
-static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char_u *save_cpo;
- int match;
- colnr_T col = 0;
- bool keepempty = false;
- bool typeerr = false;
-
- /* Make 'cpoptions' empty, the 'l' flag should not be used here. */
- save_cpo = p_cpo;
- p_cpo = (char_u *)"";
-
- const char *str = tv_get_string(&argvars[0]);
- const char *pat = NULL;
- char patbuf[NUMBUFLEN];
- if (argvars[1].v_type != VAR_UNKNOWN) {
- pat = tv_get_string_buf_chk(&argvars[1], patbuf);
- if (pat == NULL) {
- typeerr = true;
- }
- if (argvars[2].v_type != VAR_UNKNOWN) {
- keepempty = (bool)tv_get_number_chk(&argvars[2], &typeerr);
- }
- }
- if (pat == NULL || *pat == NUL) {
- pat = "[\\x01- ]\\+";
- }
-
- tv_list_alloc_ret(rettv, kListLenMayKnow);
-
- if (typeerr) {
- return;
- }
-
- regmatch_T regmatch = {
- .regprog = vim_regcomp((char_u *)pat, RE_MAGIC + RE_STRING),
- .startp = { NULL },
- .endp = { NULL },
- .rm_ic = false,
- };
- if (regmatch.regprog != NULL) {
- while (*str != NUL || keepempty) {
- if (*str == NUL) {
- match = false; // Empty item at the end.
- } else {
- match = vim_regexec_nl(&regmatch, (char_u *)str, col);
- }
- const char *end;
- if (match) {
- end = (const char *)regmatch.startp[0];
- } else {
- end = str + strlen(str);
- }
- if (keepempty || end > str || (tv_list_len(rettv->vval.v_list) > 0
- && *str != NUL
- && match
- && end < (const char *)regmatch.endp[0])) {
- tv_list_append_string(rettv->vval.v_list, str, end - str);
- }
- if (!match) {
- break;
- }
- // Advance to just after the match.
- if (regmatch.endp[0] > (char_u *)str) {
- col = 0;
- } else {
- // Don't get stuck at the same match.
- col = (*mb_ptr2len)(regmatch.endp[0]);
- }
- str = (const char *)regmatch.endp[0];
- }
-
- vim_regfree(regmatch.regprog);
- }
-
- p_cpo = save_cpo;
-}
-
/// "stdpath()" helper for list results
-static void get_xdg_var_list(const XDGVarType xdg, typval_T *rettv)
+void get_xdg_var_list(const XDGVarType xdg, typval_T *rettv)
FUNC_ATTR_NONNULL_ALL
{
const void *iter = NULL;
@@ -17328,691 +7002,6 @@ static void get_xdg_var_list(const XDGVarType xdg, typval_T *rettv)
xfree(dirs);
}
-/// "stdpath(type)" function
-static void f_stdpath(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
-
- const char *const p = tv_get_string_chk(&argvars[0]);
- if (p == NULL) {
- return; // Type error; errmsg already given.
- }
-
- if (strequal(p, "config")) {
- rettv->vval.v_string = (char_u *)get_xdg_home(kXDGConfigHome);
- } else if (strequal(p, "data")) {
- rettv->vval.v_string = (char_u *)get_xdg_home(kXDGDataHome);
- } else if (strequal(p, "cache")) {
- rettv->vval.v_string = (char_u *)get_xdg_home(kXDGCacheHome);
- } else if (strequal(p, "config_dirs")) {
- get_xdg_var_list(kXDGConfigDirs, rettv);
- } else if (strequal(p, "data_dirs")) {
- get_xdg_var_list(kXDGDataDirs, rettv);
- } else {
- EMSG2(_("E6100: \"%s\" is not a valid stdpath"), p);
- }
-}
-
-/*
- * "str2float()" function
- */
-static void f_str2float(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char_u *p = skipwhite((const char_u *)tv_get_string(&argvars[0]));
- bool isneg = (*p == '-');
-
- if (*p == '+' || *p == '-') {
- p = skipwhite(p + 1);
- }
- (void)string2float((char *)p, &rettv->vval.v_float);
- if (isneg) {
- rettv->vval.v_float *= -1;
- }
- rettv->v_type = VAR_FLOAT;
-}
-
-// "str2nr()" function
-static void f_str2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int base = 10;
- varnumber_T n;
- int what;
-
- if (argvars[1].v_type != VAR_UNKNOWN) {
- base = tv_get_number(&argvars[1]);
- if (base != 2 && base != 8 && base != 10 && base != 16) {
- EMSG(_(e_invarg));
- return;
- }
- }
-
- char_u *p = skipwhite((const char_u *)tv_get_string(&argvars[0]));
- bool isneg = (*p == '-');
- if (*p == '+' || *p == '-') {
- p = skipwhite(p + 1);
- }
- switch (base) {
- case 2: {
- what = STR2NR_BIN | STR2NR_FORCE;
- break;
- }
- case 8: {
- what = STR2NR_OCT | STR2NR_FORCE;
- break;
- }
- case 16: {
- what = STR2NR_HEX | STR2NR_FORCE;
- break;
- }
- default: {
- what = 0;
- }
- }
- vim_str2nr(p, NULL, NULL, what, &n, NULL, 0);
- if (isneg) {
- rettv->vval.v_number = -n;
- } else {
- rettv->vval.v_number = n;
- }
-}
-
-/*
- * "strftime({format}[, {time}])" function
- */
-static void f_strftime(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- time_t seconds;
-
- rettv->v_type = VAR_STRING;
-
- char *p = (char *)tv_get_string(&argvars[0]);
- if (argvars[1].v_type == VAR_UNKNOWN) {
- seconds = time(NULL);
- } else {
- seconds = (time_t)tv_get_number(&argvars[1]);
- }
-
- struct tm curtime;
- struct tm *curtime_ptr = os_localtime_r(&seconds, &curtime);
- /* MSVC returns NULL for an invalid value of seconds. */
- if (curtime_ptr == NULL)
- rettv->vval.v_string = vim_strsave((char_u *)_("(Invalid)"));
- else {
- vimconv_T conv;
- char_u *enc;
-
- conv.vc_type = CONV_NONE;
- enc = enc_locale();
- convert_setup(&conv, p_enc, enc);
- if (conv.vc_type != CONV_NONE) {
- p = (char *)string_convert(&conv, (char_u *)p, NULL);
- }
- char result_buf[256];
- if (p != NULL) {
- (void)strftime(result_buf, sizeof(result_buf), p, curtime_ptr);
- } else {
- result_buf[0] = NUL;
- }
-
- if (conv.vc_type != CONV_NONE) {
- xfree(p);
- }
- convert_setup(&conv, enc, p_enc);
- if (conv.vc_type != CONV_NONE) {
- rettv->vval.v_string = string_convert(&conv, (char_u *)result_buf, NULL);
- } else {
- rettv->vval.v_string = (char_u *)xstrdup(result_buf);
- }
-
- // Release conversion descriptors.
- convert_setup(&conv, NULL, NULL);
- xfree(enc);
- }
-}
-
-// "strgetchar()" function
-static void f_strgetchar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = -1;
-
- const char *const str = tv_get_string_chk(&argvars[0]);
- if (str == NULL) {
- return;
- }
- bool error = false;
- varnumber_T charidx = tv_get_number_chk(&argvars[1], &error);
- if (error) {
- return;
- }
-
- const size_t len = STRLEN(str);
- size_t byteidx = 0;
-
- while (charidx >= 0 && byteidx < len) {
- if (charidx == 0) {
- rettv->vval.v_number = utf_ptr2char((const char_u *)str + byteidx);
- break;
- }
- charidx--;
- byteidx += MB_CPTR2LEN((const char_u *)str + byteidx);
- }
-}
-
-/*
- * "stridx()" function
- */
-static void f_stridx(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = -1;
-
- char buf[NUMBUFLEN];
- const char *const needle = tv_get_string_chk(&argvars[1]);
- const char *haystack = tv_get_string_buf_chk(&argvars[0], buf);
- const char *const haystack_start = haystack;
- if (needle == NULL || haystack == NULL) {
- return; // Type error; errmsg already given.
- }
-
- if (argvars[2].v_type != VAR_UNKNOWN) {
- bool error = false;
-
- const ptrdiff_t start_idx = (ptrdiff_t)tv_get_number_chk(&argvars[2],
- &error);
- if (error || start_idx >= (ptrdiff_t)strlen(haystack)) {
- return;
- }
- if (start_idx >= 0) {
- haystack += start_idx;
- }
- }
-
- const char *pos = strstr(haystack, needle);
- if (pos != NULL) {
- rettv->vval.v_number = (varnumber_T)(pos - haystack_start);
- }
-}
-
-/*
- * "string()" function
- */
-static void f_string(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = (char_u *) encode_tv2string(&argvars[0], NULL);
-}
-
-/*
- * "strlen()" function
- */
-static void f_strlen(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = (varnumber_T)strlen(tv_get_string(&argvars[0]));
-}
-
-/*
- * "strchars()" function
- */
-static void f_strchars(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *s = tv_get_string(&argvars[0]);
- int skipcc = 0;
- varnumber_T len = 0;
- int (*func_mb_ptr2char_adv)(const char_u **pp);
-
- if (argvars[1].v_type != VAR_UNKNOWN) {
- skipcc = tv_get_number_chk(&argvars[1], NULL);
- }
- if (skipcc < 0 || skipcc > 1) {
- EMSG(_(e_invarg));
- } else {
- func_mb_ptr2char_adv = skipcc ? mb_ptr2char_adv : mb_cptr2char_adv;
- while (*s != NUL) {
- func_mb_ptr2char_adv((const char_u **)&s);
- len++;
- }
- rettv->vval.v_number = len;
- }
-}
-
-/*
- * "strdisplaywidth()" function
- */
-static void f_strdisplaywidth(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *const s = tv_get_string(&argvars[0]);
- int col = 0;
-
- if (argvars[1].v_type != VAR_UNKNOWN) {
- col = tv_get_number(&argvars[1]);
- }
-
- rettv->vval.v_number = (varnumber_T)(linetabsize_col(col, (char_u *)s) - col);
-}
-
-/*
- * "strwidth()" function
- */
-static void f_strwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *const s = tv_get_string(&argvars[0]);
-
- rettv->vval.v_number = (varnumber_T)mb_string2cells((const char_u *)s);
-}
-
-// "strcharpart()" function
-static void f_strcharpart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *const p = tv_get_string(&argvars[0]);
- const size_t slen = STRLEN(p);
-
- int nbyte = 0;
- bool error = false;
- varnumber_T nchar = tv_get_number_chk(&argvars[1], &error);
- if (!error) {
- if (nchar > 0) {
- while (nchar > 0 && (size_t)nbyte < slen) {
- nbyte += MB_CPTR2LEN((const char_u *)p + nbyte);
- nchar--;
- }
- } else {
- nbyte = nchar;
- }
- }
- int len = 0;
- if (argvars[2].v_type != VAR_UNKNOWN) {
- int charlen = tv_get_number(&argvars[2]);
- while (charlen > 0 && nbyte + len < (int)slen) {
- int off = nbyte + len;
-
- if (off < 0) {
- len += 1;
- } else {
- len += (size_t)MB_CPTR2LEN((const char_u *)p + off);
- }
- charlen--;
- }
- } else {
- len = slen - nbyte; // default: all bytes that are available.
- }
-
- // Only return the overlap between the specified part and the actual
- // string.
- if (nbyte < 0) {
- len += nbyte;
- nbyte = 0;
- } else if ((size_t)nbyte > slen) {
- nbyte = slen;
- }
- if (len < 0) {
- len = 0;
- } else if (nbyte + len > (int)slen) {
- len = slen - nbyte;
- }
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = (char_u *)xstrndup(p + nbyte, (size_t)len);
-}
-
-/*
- * "strpart()" function
- */
-static void f_strpart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- bool error = false;
-
- const char *const p = tv_get_string(&argvars[0]);
- const size_t slen = strlen(p);
-
- varnumber_T n = tv_get_number_chk(&argvars[1], &error);
- varnumber_T len;
- if (error) {
- len = 0;
- } else if (argvars[2].v_type != VAR_UNKNOWN) {
- len = tv_get_number(&argvars[2]);
- } else {
- len = slen - n; // Default len: all bytes that are available.
- }
-
- // Only return the overlap between the specified part and the actual
- // string.
- if (n < 0) {
- len += n;
- n = 0;
- } else if (n > (varnumber_T)slen) {
- n = slen;
- }
- if (len < 0) {
- len = 0;
- } else if (n + len > (varnumber_T)slen) {
- len = slen - n;
- }
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = (char_u *)xmemdupz(p + n, (size_t)len);
-}
-
-/*
- * "strridx()" function
- */
-static void f_strridx(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char buf[NUMBUFLEN];
- const char *const needle = tv_get_string_chk(&argvars[1]);
- const char *const haystack = tv_get_string_buf_chk(&argvars[0], buf);
-
- rettv->vval.v_number = -1;
- if (needle == NULL || haystack == NULL) {
- return; // Type error; errmsg already given.
- }
-
- const size_t haystack_len = STRLEN(haystack);
- ptrdiff_t end_idx;
- if (argvars[2].v_type != VAR_UNKNOWN) {
- // Third argument: upper limit for index.
- end_idx = (ptrdiff_t)tv_get_number_chk(&argvars[2], NULL);
- if (end_idx < 0) {
- return; // Can never find a match.
- }
- } else {
- end_idx = (ptrdiff_t)haystack_len;
- }
-
- const char *lastmatch = NULL;
- if (*needle == NUL) {
- // Empty string matches past the end.
- lastmatch = haystack + end_idx;
- } else {
- for (const char *rest = haystack; *rest != NUL; rest++) {
- rest = strstr(rest, needle);
- if (rest == NULL || rest > haystack + end_idx) {
- break;
- }
- lastmatch = rest;
- }
- }
-
- if (lastmatch != NULL) {
- rettv->vval.v_number = (varnumber_T)(lastmatch - haystack);
- }
-}
-
-/*
- * "strtrans()" function
- */
-static void f_strtrans(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = (char_u *)transstr(tv_get_string(&argvars[0]));
-}
-
-/*
- * "submatch()" function
- */
-static void f_submatch(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- bool error = false;
- int no = (int)tv_get_number_chk(&argvars[0], &error);
- if (error) {
- return;
- }
-
- if (no < 0 || no >= NSUBEXP) {
- emsgf(_("E935: invalid submatch number: %d"), no);
- return;
- }
- int retList = 0;
-
- if (argvars[1].v_type != VAR_UNKNOWN) {
- retList = tv_get_number_chk(&argvars[1], &error);
- if (error) {
- return;
- }
- }
-
- if (retList == 0) {
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = reg_submatch(no);
- } else {
- rettv->v_type = VAR_LIST;
- rettv->vval.v_list = reg_submatch_list(no);
- }
-}
-
-/*
- * "substitute()" function
- */
-static void f_substitute(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char patbuf[NUMBUFLEN];
- char subbuf[NUMBUFLEN];
- char flagsbuf[NUMBUFLEN];
-
- const char *const str = tv_get_string_chk(&argvars[0]);
- const char *const pat = tv_get_string_buf_chk(&argvars[1], patbuf);
- const char *sub = NULL;
- const char *const flg = tv_get_string_buf_chk(&argvars[3], flagsbuf);
-
- typval_T *expr = NULL;
- if (tv_is_func(argvars[2])) {
- expr = &argvars[2];
- } else {
- sub = tv_get_string_buf_chk(&argvars[2], subbuf);
- }
-
- rettv->v_type = VAR_STRING;
- if (str == NULL || pat == NULL || (sub == NULL && expr == NULL)
- || flg == NULL) {
- rettv->vval.v_string = NULL;
- } else {
- rettv->vval.v_string = do_string_sub((char_u *)str, (char_u *)pat,
- (char_u *)sub, expr, (char_u *)flg);
- }
-}
-
-/// "swapinfo(swap_filename)" function
-static void f_swapinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- tv_dict_alloc_ret(rettv);
- get_b0_dict(tv_get_string(argvars), rettv->vval.v_dict);
-}
-
-/// "swapname(expr)" function
-static void f_swapname(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_STRING;
- buf_T *buf = tv_get_buf(&argvars[0], false);
- if (buf == NULL
- || buf->b_ml.ml_mfp == NULL
- || buf->b_ml.ml_mfp->mf_fname == NULL) {
- rettv->vval.v_string = NULL;
- } else {
- rettv->vval.v_string = vim_strsave(buf->b_ml.ml_mfp->mf_fname);
- }
-}
-
-/// "synID(lnum, col, trans)" function
-static void f_synID(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- // -1 on type error (both)
- const linenr_T lnum = tv_get_lnum(argvars);
- const colnr_T col = (colnr_T)tv_get_number(&argvars[1]) - 1;
-
- bool transerr = false;
- const int trans = tv_get_number_chk(&argvars[2], &transerr);
-
- int id = 0;
- if (!transerr && lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count
- && col >= 0 && (size_t)col < STRLEN(ml_get(lnum))) {
- id = syn_get_id(curwin, lnum, col, trans, NULL, false);
- }
-
- rettv->vval.v_number = id;
-}
-
-/*
- * "synIDattr(id, what [, mode])" function
- */
-static void f_synIDattr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const int id = (int)tv_get_number(&argvars[0]);
- const char *const what = tv_get_string(&argvars[1]);
- int modec;
- if (argvars[2].v_type != VAR_UNKNOWN) {
- char modebuf[NUMBUFLEN];
- const char *const mode = tv_get_string_buf(&argvars[2], modebuf);
- modec = TOLOWER_ASC(mode[0]);
- if (modec != 'c' && modec != 'g') {
- modec = 0; // Replace invalid with current.
- }
- } else if (ui_rgb_attached()) {
- modec = 'g';
- } else {
- modec = 'c';
- }
-
-
- const char *p = NULL;
- switch (TOLOWER_ASC(what[0])) {
- case 'b': {
- if (TOLOWER_ASC(what[1]) == 'g') { // bg[#]
- p = highlight_color(id, what, modec);
- } else { // bold
- p = highlight_has_attr(id, HL_BOLD, modec);
- }
- break;
- }
- case 'f': { // fg[#] or font
- p = highlight_color(id, what, modec);
- break;
- }
- case 'i': {
- if (TOLOWER_ASC(what[1]) == 'n') { // inverse
- p = highlight_has_attr(id, HL_INVERSE, modec);
- } else { // italic
- p = highlight_has_attr(id, HL_ITALIC, modec);
- }
- break;
- }
- case 'n': { // name
- p = get_highlight_name_ext(NULL, id - 1, false);
- break;
- }
- case 'r': { // reverse
- p = highlight_has_attr(id, HL_INVERSE, modec);
- break;
- }
- case 's': {
- if (TOLOWER_ASC(what[1]) == 'p') { // sp[#]
- p = highlight_color(id, what, modec);
- } else if (TOLOWER_ASC(what[1]) == 't'
- && TOLOWER_ASC(what[2]) == 'r') { // strikethrough
- p = highlight_has_attr(id, HL_STRIKETHROUGH, modec);
- } else { // standout
- p = highlight_has_attr(id, HL_STANDOUT, modec);
- }
- break;
- }
- case 'u': {
- if (STRLEN(what) <= 5 || TOLOWER_ASC(what[5]) != 'c') { // underline
- p = highlight_has_attr(id, HL_UNDERLINE, modec);
- } else { // undercurl
- p = highlight_has_attr(id, HL_UNDERCURL, modec);
- }
- break;
- }
- }
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = (char_u *)(p == NULL ? p : xstrdup(p));
-}
-
-/*
- * "synIDtrans(id)" function
- */
-static void f_synIDtrans(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int id = tv_get_number(&argvars[0]);
-
- if (id > 0) {
- id = syn_get_final_id(id);
- } else {
- id = 0;
- }
-
- rettv->vval.v_number = id;
-}
-
-/*
- * "synconcealed(lnum, col)" function
- */
-static void f_synconcealed(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int syntax_flags = 0;
- int cchar;
- int matchid = 0;
- char_u str[NUMBUFLEN];
-
- tv_list_set_ret(rettv, NULL);
-
- // -1 on type error (both)
- const linenr_T lnum = tv_get_lnum(argvars);
- const colnr_T col = (colnr_T)tv_get_number(&argvars[1]) - 1;
-
- memset(str, NUL, sizeof(str));
-
- if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count && col >= 0
- && (size_t)col <= STRLEN(ml_get(lnum)) && curwin->w_p_cole > 0) {
- (void)syn_get_id(curwin, lnum, col, false, NULL, false);
- syntax_flags = get_syntax_info(&matchid);
-
- // get the conceal character
- if ((syntax_flags & HL_CONCEAL) && curwin->w_p_cole < 3) {
- cchar = syn_get_sub_char();
- if (cchar == NUL && curwin->w_p_cole == 1) {
- cchar = (curwin->w_p_lcs_chars.conceal == NUL)
- ? ' '
- : curwin->w_p_lcs_chars.conceal;
- }
- if (cchar != NUL) {
- utf_char2bytes(cchar, str);
- }
- }
- }
-
- tv_list_alloc_ret(rettv, 3);
- tv_list_append_number(rettv->vval.v_list, (syntax_flags & HL_CONCEAL) != 0);
- // -1 to auto-determine strlen
- tv_list_append_string(rettv->vval.v_list, (const char *)str, -1);
- tv_list_append_number(rettv->vval.v_list, matchid);
-}
-
-/*
- * "synstack(lnum, col)" function
- */
-static void f_synstack(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- tv_list_set_ret(rettv, NULL);
-
- // -1 on type error (both)
- const linenr_T lnum = tv_get_lnum(argvars);
- const colnr_T col = (colnr_T)tv_get_number(&argvars[1]) - 1;
-
- if (lnum >= 1
- && lnum <= curbuf->b_ml.ml_line_count
- && col >= 0
- && (size_t)col <= STRLEN(ml_get(lnum))) {
- tv_list_alloc_ret(rettv, kListLenMayKnow);
- (void)syn_get_id(curwin, lnum, col, false, NULL, true);
-
- int id;
- int i = 0;
- while ((id = syn_get_stack_item(i++)) >= 0) {
- tv_list_append_number(rettv->vval.v_list, id);
- }
- }
-}
-
static list_T *string_to_list(const char *str, size_t len, const bool keepempty)
{
if (!keepempty && str[len - 1] == NL) {
@@ -18024,8 +7013,8 @@ static list_T *string_to_list(const char *str, size_t len, const bool keepempty)
}
// os_system wrapper. Handles 'verbose', :profile, and v:shell_error.
-static void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv,
- bool retlist)
+void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv,
+ bool retlist)
{
proftime_T wait_time;
bool profiling = do_profiling == PROF_YES;
@@ -18123,324 +7112,19 @@ static void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv,
}
}
-/// f_system - the VimL system() function
-static void f_system(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- get_system_output_as_rettv(argvars, rettv, false);
-}
-
-static void f_systemlist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- get_system_output_as_rettv(argvars, rettv, true);
-}
-
-
-/*
- * "tabpagebuflist()" function
- */
-static void f_tabpagebuflist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- win_T *wp = NULL;
-
- if (argvars[0].v_type == VAR_UNKNOWN) {
- wp = firstwin;
- } else {
- tabpage_T *const tp = find_tabpage((int)tv_get_number(&argvars[0]));
- if (tp != NULL) {
- wp = (tp == curtab) ? firstwin : tp->tp_firstwin;
- }
- }
- if (wp != NULL) {
- tv_list_alloc_ret(rettv, kListLenMayKnow);
- while (wp != NULL) {
- tv_list_append_number(rettv->vval.v_list, wp->w_buffer->b_fnum);
- wp = wp->w_next;
- }
- }
-}
-
-/*
- * "tabpagenr()" function
- */
-static void f_tabpagenr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int nr = 1;
-
- if (argvars[0].v_type != VAR_UNKNOWN) {
- const char *const arg = tv_get_string_chk(&argvars[0]);
- nr = 0;
- if (arg != NULL) {
- if (strcmp(arg, "$") == 0) {
- nr = tabpage_index(NULL) - 1;
- } else if (strcmp(arg, "#") == 0) {
- nr = valid_tabpage(lastused_tabpage)
- ? tabpage_index(lastused_tabpage)
- : nr;
- } else {
- EMSG2(_(e_invexpr2), arg);
- }
- }
- } else {
- nr = tabpage_index(curtab);
- }
- rettv->vval.v_number = nr;
-}
-
-
-
-/*
- * Common code for tabpagewinnr() and winnr().
- */
-static int get_winnr(tabpage_T *tp, typval_T *argvar)
-{
- win_T *twin;
- int nr = 1;
- win_T *wp;
-
- twin = (tp == curtab) ? curwin : tp->tp_curwin;
- if (argvar->v_type != VAR_UNKNOWN) {
- bool invalid_arg = false;
- const char *const arg = tv_get_string_chk(argvar);
- if (arg == NULL) {
- nr = 0; // Type error; errmsg already given.
- } else if (strcmp(arg, "$") == 0) {
- twin = (tp == curtab) ? lastwin : tp->tp_lastwin;
- } else if (strcmp(arg, "#") == 0) {
- twin = (tp == curtab) ? prevwin : tp->tp_prevwin;
- if (twin == NULL) {
- nr = 0;
- }
- } else {
- // Extract the window count (if specified). e.g. winnr('3j')
- char_u *endp;
- long count = strtol((char *)arg, (char **)&endp, 10);
- if (count <= 0) {
- // if count is not specified, default to 1
- count = 1;
- }
- if (endp != NULL && *endp != '\0') {
- if (strequal((char *)endp, "j")) {
- twin = win_vert_neighbor(tp, twin, false, count);
- } else if (strequal((char *)endp, "k")) {
- twin = win_vert_neighbor(tp, twin, true, count);
- } else if (strequal((char *)endp, "h")) {
- twin = win_horz_neighbor(tp, twin, true, count);
- } else if (strequal((char *)endp, "l")) {
- twin = win_horz_neighbor(tp, twin, false, count);
- } else {
- invalid_arg = true;
- }
- } else {
- invalid_arg = true;
- }
- }
-
- if (invalid_arg) {
- EMSG2(_(e_invexpr2), arg);
- nr = 0;
- }
- }
-
- if (nr > 0)
- for (wp = (tp == curtab) ? firstwin : tp->tp_firstwin;
- wp != twin; wp = wp->w_next) {
- if (wp == NULL) {
- /* didn't find it in this tabpage */
- nr = 0;
- break;
- }
- ++nr;
- }
- return nr;
-}
-
-/*
- * "tabpagewinnr()" function
- */
-static void f_tabpagewinnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int nr = 1;
- tabpage_T *const tp = find_tabpage((int)tv_get_number(&argvars[0]));
- if (tp == NULL) {
- nr = 0;
- } else {
- nr = get_winnr(tp, &argvars[1]);
- }
- rettv->vval.v_number = nr;
-}
-
-/*
- * "tagfiles()" function
- */
-static void f_tagfiles(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char *fname;
- tagname_T tn;
-
- tv_list_alloc_ret(rettv, kListLenUnknown);
- fname = xmalloc(MAXPATHL);
-
- bool first = true;
- while (get_tagfname(&tn, first, (char_u *)fname) == OK) {
- tv_list_append_string(rettv->vval.v_list, fname, -1);
- first = false;
- }
-
- tagname_free(&tn);
- xfree(fname);
-}
-
-/*
- * "taglist()" function
- */
-static void f_taglist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *const tag_pattern = tv_get_string(&argvars[0]);
-
- rettv->vval.v_number = false;
- if (*tag_pattern == NUL) {
- return;
- }
-
- const char *fname = NULL;
- if (argvars[1].v_type != VAR_UNKNOWN) {
- fname = tv_get_string(&argvars[1]);
- }
- (void)get_tags(tv_list_alloc_ret(rettv, kListLenUnknown),
- (char_u *)tag_pattern, (char_u *)fname);
-}
-
-/*
- * "tempname()" function
- */
-static void f_tempname(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = vim_tempname();
-}
-
-// "termopen(cmd[, cwd])" function
-static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (check_restricted() || check_secure()) {
- return;
- }
-
- if (curbuf->b_changed) {
- EMSG(_("Can only call this function in an unmodified buffer"));
- return;
- }
-
- const char *cmd;
- bool executable = true;
- char **argv = tv_to_argv(&argvars[0], &cmd, &executable);
- if (!argv) {
- rettv->vval.v_number = executable ? 0 : -1;
- return; // Did error message in tv_to_argv.
- }
-
- if (argvars[1].v_type != VAR_DICT && argvars[1].v_type != VAR_UNKNOWN) {
- // Wrong argument type
- EMSG2(_(e_invarg2), "expected dictionary");
- shell_free_argv(argv);
- return;
- }
-
- CallbackReader on_stdout = CALLBACK_READER_INIT,
- on_stderr = CALLBACK_READER_INIT;
- Callback on_exit = CALLBACK_NONE;
- dict_T *job_opts = NULL;
- const char *cwd = ".";
- if (argvars[1].v_type == VAR_DICT) {
- job_opts = argvars[1].vval.v_dict;
-
- const char *const new_cwd = tv_dict_get_string(job_opts, "cwd", false);
- if (new_cwd && *new_cwd != NUL) {
- cwd = new_cwd;
- // The new cwd must be a directory.
- if (!os_isdir_executable((const char *)cwd)) {
- EMSG2(_(e_invarg2), "expected valid directory");
- shell_free_argv(argv);
- return;
- }
- }
-
- if (!common_job_callbacks(job_opts, &on_stdout, &on_stderr, &on_exit)) {
- shell_free_argv(argv);
- return;
- }
- }
-
- uint16_t term_width = MAX(0, curwin->w_width_inner - win_col_off(curwin));
- Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit,
- true, false, false, cwd,
- term_width, curwin->w_height_inner,
- xstrdup("xterm-256color"), NULL,
- &rettv->vval.v_number);
- if (rettv->vval.v_number <= 0) {
- return;
- }
-
- int pid = chan->stream.pty.process.pid;
-
- // "./…" => "/home/foo/…"
- vim_FullName(cwd, (char *)NameBuff, sizeof(NameBuff), false);
- // "/home/foo/…" => "~/…"
- size_t len = home_replace(NULL, NameBuff, IObuff, sizeof(IObuff), true);
- // Trim slash.
- if (IObuff[len - 1] == '\\' || IObuff[len - 1] == '/') {
- IObuff[len - 1] = '\0';
- }
-
- // Terminal URI: "term://$CWD//$PID:$CMD"
- snprintf((char *)NameBuff, sizeof(NameBuff), "term://%s//%d:%s",
- (char *)IObuff, pid, cmd);
- // at this point the buffer has no terminal instance associated yet, so unset
- // the 'swapfile' option to ensure no swap file will be created
- curbuf->b_p_swf = false;
- (void)setfname(curbuf, NameBuff, NULL, true);
- // Save the job id and pid in b:terminal_job_{id,pid}
- Error err = ERROR_INIT;
- // deprecated: use 'channel' buffer option
- dict_set_var(curbuf->b_vars, cstr_as_string("terminal_job_id"),
- INTEGER_OBJ(chan->id), false, false, &err);
- api_clear_error(&err);
- dict_set_var(curbuf->b_vars, cstr_as_string("terminal_job_pid"),
- INTEGER_OBJ(pid), false, false, &err);
- api_clear_error(&err);
-
- channel_terminal_open(chan);
- channel_create_event(chan, NULL);
-}
-
-// "test_garbagecollect_now()" function
-static void f_test_garbagecollect_now(typval_T *argvars,
- typval_T *rettv, FunPtr fptr)
-{
- // This is dangerous, any Lists and Dicts used internally may be freed
- // while still in use.
- garbage_collect(true);
-}
-
-// "test_write_list_log()" function
-static void f_test_write_list_log(typval_T *const argvars,
- typval_T *const rettv,
- FunPtr fptr)
-{
- const char *const fname = tv_get_string_chk(&argvars[0]);
- if (fname == NULL) {
- return;
- }
- list_write_log(fname);
-}
-
bool callback_from_typval(Callback *const callback, typval_T *const arg)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
+ int r = OK;
+
if (arg->v_type == VAR_PARTIAL && arg->vval.v_partial != NULL) {
callback->data.partial = arg->vval.v_partial;
callback->data.partial->pt_refcount++;
callback->type = kCallbackPartial;
+ } else if (arg->v_type == VAR_STRING
+ && arg->vval.v_string != NULL
+ && ascii_isdigit(*arg->vval.v_string)) {
+ r = FAIL;
} else if (arg->v_type == VAR_FUNC || arg->v_type == VAR_STRING) {
char_u *name = arg->vval.v_string;
func_ref(name);
@@ -18449,6 +7133,10 @@ bool callback_from_typval(Callback *const callback, typval_T *const arg)
} else if (arg->v_type == VAR_NUMBER && arg->vval.v_number == 0) {
callback->type = kCallbackNone;
} else {
+ r = FAIL;
+ }
+
+ if (r == FAIL) {
EMSG(_("E921: Invalid callback argument"));
return false;
}
@@ -18526,7 +7214,12 @@ static bool set_ref_in_callback_reader(CallbackReader *reader, int copyID,
return false;
}
-static void add_timer_info(typval_T *rettv, timer_T *timer)
+timer_T *find_timer_by_nr(varnumber_T xx)
+{
+ return pmap_get(uint64_t)(timers, xx);
+}
+
+void add_timer_info(typval_T *rettv, timer_T *timer)
{
list_T *list = rettv->vval.v_list;
dict_T *dict = tv_dict_alloc();
@@ -18555,8 +7248,9 @@ static void add_timer_info(typval_T *rettv, timer_T *timer)
}
}
-static void add_timer_info_all(typval_T *rettv)
+void add_timer_info_all(typval_T *rettv)
{
+ tv_list_alloc_ret(rettv, timers->table->n_occupied);
timer_T *timer;
map_foreach_value(timers, timer, {
if (!timer->stopped) {
@@ -18565,121 +7259,8 @@ static void add_timer_info_all(typval_T *rettv)
})
}
-/// "timer_info([timer])" function
-static void f_timer_info(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- tv_list_alloc_ret(rettv, (argvars[0].v_type != VAR_UNKNOWN
- ? 1
- : timers->table->n_occupied));
- if (argvars[0].v_type != VAR_UNKNOWN) {
- if (argvars[0].v_type != VAR_NUMBER) {
- EMSG(_(e_number_exp));
- return;
- }
- timer_T *timer = pmap_get(uint64_t)(timers, tv_get_number(&argvars[0]));
- if (timer != NULL && !timer->stopped) {
- add_timer_info(rettv, timer);
- }
- } else {
- add_timer_info_all(rettv);
- }
-}
-
-/// "timer_pause(timer, paused)" function
-static void f_timer_pause(typval_T *argvars, typval_T *unused, FunPtr fptr)
-{
- if (argvars[0].v_type != VAR_NUMBER) {
- EMSG(_(e_number_exp));
- return;
- }
- int paused = (bool)tv_get_number(&argvars[1]);
- timer_T *timer = pmap_get(uint64_t)(timers, tv_get_number(&argvars[0]));
- if (timer != NULL) {
- if (!timer->paused && paused) {
- time_watcher_stop(&timer->tw);
- } else if (timer->paused && !paused) {
- time_watcher_start(&timer->tw, timer_due_cb, timer->timeout,
- timer->timeout);
- }
- timer->paused = paused;
- }
-}
-
-/// "timer_start(timeout, callback, opts)" function
-static void f_timer_start(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const long timeout = tv_get_number(&argvars[0]);
- timer_T *timer;
- int repeat = 1;
- dict_T *dict;
-
- rettv->vval.v_number = -1;
-
- if (argvars[2].v_type != VAR_UNKNOWN) {
- if (argvars[2].v_type != VAR_DICT
- || (dict = argvars[2].vval.v_dict) == NULL) {
- EMSG2(_(e_invarg2), tv_get_string(&argvars[2]));
- return;
- }
- dictitem_T *const di = tv_dict_find(dict, S_LEN("repeat"));
- if (di != NULL) {
- repeat = tv_get_number(&di->di_tv);
- if (repeat == 0) {
- repeat = 1;
- }
- }
- }
-
- Callback callback;
- if (!callback_from_typval(&callback, &argvars[1])) {
- return;
- }
-
- timer = xmalloc(sizeof *timer);
- timer->refcount = 1;
- timer->stopped = false;
- timer->paused = false;
- timer->emsg_count = 0;
- timer->repeat_count = repeat;
- timer->timeout = timeout;
- timer->timer_id = last_timer_id++;
- timer->callback = callback;
-
- time_watcher_init(&main_loop, &timer->tw, timer);
- timer->tw.events = multiqueue_new_child(main_loop.events);
- // if main loop is blocked, don't queue up multiple events
- timer->tw.blockable = true;
- time_watcher_start(&timer->tw, timer_due_cb, timeout, timeout);
-
- pmap_put(uint64_t)(timers, timer->timer_id, timer);
- rettv->vval.v_number = timer->timer_id;
-}
-
-
-// "timer_stop(timerid)" function
-static void f_timer_stop(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (argvars[0].v_type != VAR_NUMBER) {
- EMSG(_(e_number_exp));
- return;
- }
-
- timer_T *timer = pmap_get(uint64_t)(timers, tv_get_number(&argvars[0]));
-
- if (timer == NULL) {
- return;
- }
-
- timer_stop(timer);
-}
-
-static void f_timer_stopall(typval_T *argvars, typval_T *unused, FunPtr fptr)
-{
- timer_stop_all();
-}
-
// invoked on the main loop
-static void timer_due_cb(TimeWatcher *tw, void *data)
+void timer_due_cb(TimeWatcher *tw, void *data)
{
timer_T *timer = (timer_T *)data;
int save_did_emsg = did_emsg;
@@ -18731,7 +7312,31 @@ static void timer_due_cb(TimeWatcher *tw, void *data)
timer_decref(timer);
}
-static void timer_stop(timer_T *timer)
+uint64_t timer_start(const long timeout,
+ const int repeat_count,
+ const Callback *const callback)
+{
+ timer_T *timer = xmalloc(sizeof *timer);
+ timer->refcount = 1;
+ timer->stopped = false;
+ timer->paused = false;
+ timer->emsg_count = 0;
+ timer->repeat_count = repeat_count;
+ timer->timeout = timeout;
+ timer->timer_id = last_timer_id++;
+ timer->callback = *callback;
+
+ time_watcher_init(&main_loop, &timer->tw, timer);
+ timer->tw.events = multiqueue_new_child(main_loop.events);
+ // if main loop is blocked, don't queue up multiple events
+ timer->tw.blockable = true;
+ time_watcher_start(&timer->tw, timer_due_cb, timeout, timeout);
+
+ pmap_put(uint64_t)(timers, timer->timer_id, timer);
+ return timer->timer_id;
+}
+
+void timer_stop(timer_T *timer)
{
if (timer->stopped) {
// avoid double free
@@ -18760,7 +7365,7 @@ static void timer_decref(timer_T *timer)
}
}
-static void timer_stop_all(void)
+void timer_stop_all(void)
{
timer_T *timer;
map_foreach_value(timers, timer, {
@@ -18773,527 +7378,6 @@ void timer_teardown(void)
timer_stop_all();
}
-/*
- * "tolower(string)" function
- */
-static void f_tolower(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = (char_u *)strcase_save(tv_get_string(&argvars[0]),
- false);
-}
-
-/*
- * "toupper(string)" function
- */
-static void f_toupper(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = (char_u *)strcase_save(tv_get_string(&argvars[0]),
- true);
-}
-
-/*
- * "tr(string, fromstr, tostr)" function
- */
-static void f_tr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char buf[NUMBUFLEN];
- char buf2[NUMBUFLEN];
-
- const char *in_str = tv_get_string(&argvars[0]);
- const char *fromstr = tv_get_string_buf_chk(&argvars[1], buf);
- const char *tostr = tv_get_string_buf_chk(&argvars[2], buf2);
-
- // Default return value: empty string.
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
- if (fromstr == NULL || tostr == NULL) {
- return; // Type error; errmsg already given.
- }
- garray_T ga;
- ga_init(&ga, (int)sizeof(char), 80);
-
- if (!has_mbyte) {
- // Not multi-byte: fromstr and tostr must be the same length.
- if (strlen(fromstr) != strlen(tostr)) {
- goto error;
- }
- }
-
- // fromstr and tostr have to contain the same number of chars.
- bool first = true;
- while (*in_str != NUL) {
- if (has_mbyte) {
- const char *cpstr = in_str;
- const int inlen = (*mb_ptr2len)((const char_u *)in_str);
- int cplen = inlen;
- int idx = 0;
- int fromlen;
- for (const char *p = fromstr; *p != NUL; p += fromlen) {
- fromlen = (*mb_ptr2len)((const char_u *)p);
- if (fromlen == inlen && STRNCMP(in_str, p, inlen) == 0) {
- int tolen;
- for (p = tostr; *p != NUL; p += tolen) {
- tolen = (*mb_ptr2len)((const char_u *)p);
- if (idx-- == 0) {
- cplen = tolen;
- cpstr = (char *)p;
- break;
- }
- }
- if (*p == NUL) { // tostr is shorter than fromstr.
- goto error;
- }
- break;
- }
- idx++;
- }
-
- if (first && cpstr == in_str) {
- // Check that fromstr and tostr have the same number of
- // (multi-byte) characters. Done only once when a character
- // of in_str doesn't appear in fromstr.
- first = false;
- int tolen;
- for (const char *p = tostr; *p != NUL; p += tolen) {
- tolen = (*mb_ptr2len)((const char_u *)p);
- idx--;
- }
- if (idx != 0) {
- goto error;
- }
- }
-
- ga_grow(&ga, cplen);
- memmove((char *)ga.ga_data + ga.ga_len, cpstr, (size_t)cplen);
- ga.ga_len += cplen;
-
- in_str += inlen;
- } else {
- // When not using multi-byte chars we can do it faster.
- const char *const p = strchr(fromstr, *in_str);
- if (p != NULL) {
- ga_append(&ga, tostr[p - fromstr]);
- } else {
- ga_append(&ga, *in_str);
- }
- in_str++;
- }
- }
-
- // add a terminating NUL
- ga_append(&ga, NUL);
-
- rettv->vval.v_string = ga.ga_data;
- return;
-error:
- EMSG2(_(e_invarg2), fromstr);
- ga_clear(&ga);
- return;
-}
-
-// "trim({expr})" function
-static void f_trim(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char buf1[NUMBUFLEN];
- char buf2[NUMBUFLEN];
- const char_u *head = (const char_u *)tv_get_string_buf_chk(&argvars[0], buf1);
- const char_u *mask = NULL;
- const char_u *tail;
- const char_u *prev;
- const char_u *p;
- int c1;
-
- rettv->v_type = VAR_STRING;
- if (head == NULL) {
- rettv->vval.v_string = NULL;
- return;
- }
-
- if (argvars[1].v_type == VAR_STRING) {
- mask = (const char_u *)tv_get_string_buf_chk(&argvars[1], buf2);
- }
-
- while (*head != NUL) {
- c1 = PTR2CHAR(head);
- if (mask == NULL) {
- if (c1 > ' ' && c1 != 0xa0) {
- break;
- }
- } else {
- for (p = mask; *p != NUL; MB_PTR_ADV(p)) {
- if (c1 == PTR2CHAR(p)) {
- break;
- }
- }
- if (*p == NUL) {
- break;
- }
- }
- MB_PTR_ADV(head);
- }
-
- for (tail = head + STRLEN(head); tail > head; tail = prev) {
- prev = tail;
- MB_PTR_BACK(head, prev);
- c1 = PTR2CHAR(prev);
- if (mask == NULL) {
- if (c1 > ' ' && c1 != 0xa0) {
- break;
- }
- } else {
- for (p = mask; *p != NUL; MB_PTR_ADV(p)) {
- if (c1 == PTR2CHAR(p)) {
- break;
- }
- }
- if (*p == NUL) {
- break;
- }
- }
- }
- rettv->vval.v_string = vim_strnsave(head, (int)(tail - head));
-}
-
-/*
- * "type(expr)" function
- */
-static void f_type(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int n = -1;
-
- switch (argvars[0].v_type) {
- case VAR_NUMBER: n = VAR_TYPE_NUMBER; break;
- case VAR_STRING: n = VAR_TYPE_STRING; break;
- case VAR_PARTIAL:
- case VAR_FUNC: n = VAR_TYPE_FUNC; break;
- case VAR_LIST: n = VAR_TYPE_LIST; break;
- case VAR_DICT: n = VAR_TYPE_DICT; break;
- case VAR_FLOAT: n = VAR_TYPE_FLOAT; break;
- case VAR_SPECIAL: {
- switch (argvars[0].vval.v_special) {
- case kSpecialVarTrue:
- case kSpecialVarFalse: {
- n = VAR_TYPE_BOOL;
- break;
- }
- case kSpecialVarNull: {
- n = 7;
- break;
- }
- }
- break;
- }
- case VAR_UNKNOWN: {
- internal_error("f_type(UNKNOWN)");
- break;
- }
- }
- rettv->vval.v_number = n;
-}
-
-/*
- * "undofile(name)" function
- */
-static void f_undofile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->v_type = VAR_STRING;
- const char *const fname = tv_get_string(&argvars[0]);
-
- if (*fname == NUL) {
- // If there is no file name there will be no undo file.
- rettv->vval.v_string = NULL;
- } else {
- char *ffname = FullName_save(fname, true);
-
- if (ffname != NULL) {
- rettv->vval.v_string = (char_u *)u_get_undo_file_name(ffname, false);
- }
- xfree(ffname);
- }
-}
-
-/*
- * "undotree()" function
- */
-static void f_undotree(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- tv_dict_alloc_ret(rettv);
-
- dict_T *dict = rettv->vval.v_dict;
-
- tv_dict_add_nr(dict, S_LEN("synced"), (varnumber_T)curbuf->b_u_synced);
- tv_dict_add_nr(dict, S_LEN("seq_last"), (varnumber_T)curbuf->b_u_seq_last);
- tv_dict_add_nr(dict, S_LEN("save_last"),
- (varnumber_T)curbuf->b_u_save_nr_last);
- tv_dict_add_nr(dict, S_LEN("seq_cur"), (varnumber_T)curbuf->b_u_seq_cur);
- tv_dict_add_nr(dict, S_LEN("time_cur"), (varnumber_T)curbuf->b_u_time_cur);
- tv_dict_add_nr(dict, S_LEN("save_cur"), (varnumber_T)curbuf->b_u_save_nr_cur);
-
- tv_dict_add_list(dict, S_LEN("entries"), u_eval_tree(curbuf->b_u_oldhead));
-}
-
-/*
- * "values(dict)" function
- */
-static void f_values(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- dict_list(argvars, rettv, 1);
-}
-
-/*
- * "virtcol(string)" function
- */
-static void f_virtcol(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- colnr_T vcol = 0;
- pos_T *fp;
- int fnum = curbuf->b_fnum;
-
- fp = var2fpos(&argvars[0], FALSE, &fnum);
- if (fp != NULL && fp->lnum <= curbuf->b_ml.ml_line_count
- && fnum == curbuf->b_fnum) {
- // Limit the column to a valid value, getvvcol() doesn't check.
- if (fp->col < 0) {
- fp->col = 0;
- } else {
- const size_t len = STRLEN(ml_get(fp->lnum));
- if (fp->col > (colnr_T)len) {
- fp->col = (colnr_T)len;
- }
- }
- getvvcol(curwin, fp, NULL, NULL, &vcol);
- ++vcol;
- }
-
- rettv->vval.v_number = vcol;
-}
-
-/*
- * "visualmode()" function
- */
-static void f_visualmode(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- char_u str[2];
-
- rettv->v_type = VAR_STRING;
- str[0] = curbuf->b_visual_mode_eval;
- str[1] = NUL;
- rettv->vval.v_string = vim_strsave(str);
-
- /* A non-zero number or non-empty string argument: reset mode. */
- if (non_zero_arg(&argvars[0]))
- curbuf->b_visual_mode_eval = NUL;
-}
-
-/*
- * "wildmenumode()" function
- */
-static void f_wildmenumode(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- if (wild_menu_showing || ((State & CMDLINE) && pum_visible())) {
- rettv->vval.v_number = 1;
- }
-}
-
-/// "win_findbuf()" function
-static void f_win_findbuf(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- tv_list_alloc_ret(rettv, kListLenMayKnow);
- win_findbuf(argvars, rettv->vval.v_list);
-}
-
-/// "win_getid()" function
-static void f_win_getid(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = win_getid(argvars);
-}
-
-/// "win_gotoid()" function
-static void f_win_gotoid(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = win_gotoid(argvars);
-}
-
-/// "win_id2tabwin()" function
-static void f_win_id2tabwin(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- win_id2tabwin(argvars, rettv);
-}
-
-/// "win_id2win()" function
-static void f_win_id2win(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = win_id2win(argvars);
-}
-
-/// "winbufnr(nr)" function
-static void f_winbufnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- win_T *wp = find_win_by_nr_or_id(&argvars[0]);
- if (wp == NULL) {
- rettv->vval.v_number = -1;
- } else {
- rettv->vval.v_number = wp->w_buffer->b_fnum;
- }
-}
-
-/*
- * "wincol()" function
- */
-static void f_wincol(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- validate_cursor();
- rettv->vval.v_number = curwin->w_wcol + 1;
-}
-
-/// "winheight(nr)" function
-static void f_winheight(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- win_T *wp = find_win_by_nr_or_id(&argvars[0]);
- if (wp == NULL) {
- rettv->vval.v_number = -1;
- } else {
- rettv->vval.v_number = wp->w_height;
- }
-}
-
-// "winlayout()" function
-static void f_winlayout(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- tabpage_T *tp;
-
- tv_list_alloc_ret(rettv, 2);
-
- if (argvars[0].v_type == VAR_UNKNOWN) {
- tp = curtab;
- } else {
- tp = find_tabpage((int)tv_get_number(&argvars[0]));
- if (tp == NULL) {
- return;
- }
- }
-
- get_framelayout(tp->tp_topframe, rettv->vval.v_list, true);
-}
-
-/*
- * "winline()" function
- */
-static void f_winline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- validate_cursor();
- rettv->vval.v_number = curwin->w_wrow + 1;
-}
-
-/*
- * "winnr()" function
- */
-static void f_winnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int nr = 1;
-
- nr = get_winnr(curtab, &argvars[0]);
- rettv->vval.v_number = nr;
-}
-
-/*
- * "winrestcmd()" function
- */
-static void f_winrestcmd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int winnr = 1;
- garray_T ga;
- char_u buf[50];
-
- ga_init(&ga, (int)sizeof(char), 70);
- FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- sprintf((char *)buf, "%dresize %d|", winnr, wp->w_height);
- ga_concat(&ga, buf);
- sprintf((char *)buf, "vert %dresize %d|", winnr, wp->w_width);
- ga_concat(&ga, buf);
- ++winnr;
- }
- ga_append(&ga, NUL);
-
- rettv->vval.v_string = ga.ga_data;
- rettv->v_type = VAR_STRING;
-}
-
-/*
- * "winrestview()" function
- */
-static void f_winrestview(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- dict_T *dict;
-
- if (argvars[0].v_type != VAR_DICT
- || (dict = argvars[0].vval.v_dict) == NULL) {
- EMSG(_(e_invarg));
- } else {
- dictitem_T *di;
- if ((di = tv_dict_find(dict, S_LEN("lnum"))) != NULL) {
- curwin->w_cursor.lnum = tv_get_number(&di->di_tv);
- }
- if ((di = tv_dict_find(dict, S_LEN("col"))) != NULL) {
- curwin->w_cursor.col = tv_get_number(&di->di_tv);
- }
- if ((di = tv_dict_find(dict, S_LEN("coladd"))) != NULL) {
- curwin->w_cursor.coladd = tv_get_number(&di->di_tv);
- }
- if ((di = tv_dict_find(dict, S_LEN("curswant"))) != NULL) {
- curwin->w_curswant = tv_get_number(&di->di_tv);
- curwin->w_set_curswant = false;
- }
- if ((di = tv_dict_find(dict, S_LEN("topline"))) != NULL) {
- set_topline(curwin, tv_get_number(&di->di_tv));
- }
- if ((di = tv_dict_find(dict, S_LEN("topfill"))) != NULL) {
- curwin->w_topfill = tv_get_number(&di->di_tv);
- }
- if ((di = tv_dict_find(dict, S_LEN("leftcol"))) != NULL) {
- curwin->w_leftcol = tv_get_number(&di->di_tv);
- }
- if ((di = tv_dict_find(dict, S_LEN("skipcol"))) != NULL) {
- curwin->w_skipcol = tv_get_number(&di->di_tv);
- }
-
- check_cursor();
- win_new_height(curwin, curwin->w_height);
- win_new_width(curwin, curwin->w_width);
- changed_window_setting();
-
- if (curwin->w_topline <= 0)
- curwin->w_topline = 1;
- if (curwin->w_topline > curbuf->b_ml.ml_line_count)
- curwin->w_topline = curbuf->b_ml.ml_line_count;
- check_topfill(curwin, true);
- }
-}
-
-/*
- * "winsaveview()" function
- */
-static void f_winsaveview(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- dict_T *dict;
-
- tv_dict_alloc_ret(rettv);
- dict = rettv->vval.v_dict;
-
- tv_dict_add_nr(dict, S_LEN("lnum"), (varnumber_T)curwin->w_cursor.lnum);
- tv_dict_add_nr(dict, S_LEN("col"), (varnumber_T)curwin->w_cursor.col);
- tv_dict_add_nr(dict, S_LEN("coladd"), (varnumber_T)curwin->w_cursor.coladd);
- update_curswant();
- tv_dict_add_nr(dict, S_LEN("curswant"), (varnumber_T)curwin->w_curswant);
-
- tv_dict_add_nr(dict, S_LEN("topline"), (varnumber_T)curwin->w_topline);
- tv_dict_add_nr(dict, S_LEN("topfill"), (varnumber_T)curwin->w_topfill);
- tv_dict_add_nr(dict, S_LEN("leftcol"), (varnumber_T)curwin->w_leftcol);
- tv_dict_add_nr(dict, S_LEN("skipcol"), (varnumber_T)curwin->w_skipcol);
-}
-
/// Write "list" of strings to file "fd".
///
/// @param fp File to write to.
@@ -19301,8 +7385,8 @@ static void f_winsaveview(typval_T *argvars, typval_T *rettv, FunPtr fptr)
/// @param[in] binary Whether to write in binary mode.
///
/// @return true in case of success, false otherwise.
-static bool write_list(FileDescriptor *const fp, const list_T *const list,
- const bool binary)
+bool write_list(FileDescriptor *const fp, const list_T *const list,
+ const bool binary)
FUNC_ATTR_NONNULL_ARG(1)
{
int error = 0;
@@ -19360,7 +7444,7 @@ write_list_error:
/// @param[in] endnl If true, the output will end in a newline (if a list).
/// @returns an allocated string if `tv` represents a VimL string, list, or
/// number; NULL otherwise.
-static char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl)
+char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl)
FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL
{
*len = 0;
@@ -19439,101 +7523,6 @@ static char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl)
return ret;
}
-/// "winwidth(nr)" function
-static void f_winwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- win_T *wp = find_win_by_nr_or_id(&argvars[0]);
- if (wp == NULL) {
- rettv->vval.v_number = -1;
- } else {
- rettv->vval.v_number = wp->w_width;
- }
-}
-
-/// "wordcount()" function
-static void f_wordcount(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- tv_dict_alloc_ret(rettv);
- cursor_pos_info(rettv->vval.v_dict);
-}
-
-/// "writefile()" function
-static void f_writefile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = -1;
-
- if (check_restricted() || check_secure()) {
- return;
- }
-
- if (argvars[0].v_type != VAR_LIST) {
- EMSG2(_(e_listarg), "writefile()");
- return;
- }
- const list_T *const list = argvars[0].vval.v_list;
- TV_LIST_ITER_CONST(list, li, {
- if (!tv_check_str_or_nr(TV_LIST_ITEM_TV(li))) {
- return;
- }
- });
-
- bool binary = false;
- bool append = false;
- bool do_fsync = !!p_fs;
- if (argvars[2].v_type != VAR_UNKNOWN) {
- const char *const flags = tv_get_string_chk(&argvars[2]);
- if (flags == NULL) {
- return;
- }
- for (const char *p = flags; *p; p++) {
- switch (*p) {
- case 'b': { binary = true; break; }
- case 'a': { append = true; break; }
- case 's': { do_fsync = true; break; }
- case 'S': { do_fsync = false; break; }
- default: {
- // Using %s, p and not %c, *p to preserve multibyte characters
- emsgf(_("E5060: Unknown flag: %s"), p);
- return;
- }
- }
- }
- }
-
- char buf[NUMBUFLEN];
- const char *const fname = tv_get_string_buf_chk(&argvars[1], buf);
- if (fname == NULL) {
- return;
- }
- FileDescriptor fp;
- int error;
- if (*fname == NUL) {
- EMSG(_("E482: Can't open file with an empty name"));
- } else if ((error = file_open(&fp, fname,
- ((append ? kFileAppend : kFileTruncate)
- | kFileCreate), 0666)) != 0) {
- emsgf(_("E482: Can't open file %s for writing: %s"),
- fname, os_strerror(error));
- } else {
- if (write_list(&fp, list, binary)) {
- rettv->vval.v_number = 0;
- }
- if ((error = file_close(&fp, do_fsync)) != 0) {
- emsgf(_("E80: Error when closing file %s: %s"),
- fname, os_strerror(error));
- }
- }
-}
-/*
- * "xor(expr, expr)" function
- */
-static void f_xor(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL)
- ^ tv_get_number_chk(&argvars[1], NULL);
-}
-
-
/// Translate a VimL object into a position
///
/// Accepts VAR_LIST and VAR_STRING objects. Does not give an error for invalid
@@ -19626,19 +7615,19 @@ pos_T *var2fpos(const typval_T *const tv, const int dollar_lnum,
if (name[0] == 'w' && dollar_lnum) {
pos.col = 0;
- if (name[1] == '0') { /* "w0": first visible line */
+ if (name[1] == '0') { // "w0": first visible line
update_topline();
// In silent Ex mode topline is zero, but that's not a valid line
// number; use one instead.
pos.lnum = curwin->w_topline > 0 ? curwin->w_topline : 1;
return &pos;
- } else if (name[1] == '$') { /* "w$": last visible line */
+ } else if (name[1] == '$') { // "w$": last visible line
validate_botline();
// In silent Ex mode botline is zero, return zero then.
pos.lnum = curwin->w_botline > 0 ? curwin->w_botline - 1 : 0;
return &pos;
}
- } else if (name[0] == '$') { /* last column or line */
+ } else if (name[0] == '$') { // last column or line
if (dollar_lnum) {
pos.lnum = curbuf->b_ml.ml_line_count;
pos.col = 0;
@@ -19735,7 +7724,7 @@ static int get_env_len(const char_u **arg)
// Get the length of the name of a function or internal variable.
// "arg" is advanced to the first non-white character after the name.
// Return 0 if something is wrong.
-static int get_id_len(const char **const arg)
+int get_id_len(const char **const arg)
{
int len;
@@ -19771,14 +7760,14 @@ static int get_id_len(const char **const arg)
* If the name contains 'magic' {}'s, expand them and return the
* expanded name in an allocated string via 'alias' - caller must free.
*/
-static int get_name_len(const char **const arg,
- char **alias,
- int evaluate,
- int verbose)
+int get_name_len(const char **const arg,
+ char **alias,
+ int evaluate,
+ int verbose)
{
int len;
- *alias = NULL; /* default to no alias */
+ *alias = NULL; // default to no alias
if ((*arg)[0] == (char)K_SPECIAL && (*arg)[1] == (char)KS_EXTRA
&& (*arg)[2] == (char)KE_SNR) {
@@ -19788,7 +7777,7 @@ static int get_name_len(const char **const arg,
}
len = eval_fname_script(*arg);
if (len > 0) {
- /* literal "<SID>", "s:" or "<SNR>" */
+ // literal "<SID>", "s:" or "<SNR>"
*arg += len;
}
@@ -19836,8 +7825,8 @@ static int get_name_len(const char **const arg,
// "flags" can have FNE_INCL_BR and FNE_CHECK_START.
// Return a pointer to just after the name. Equal to "arg" if there is no
// valid name.
-static const char_u *find_name_end(const char_u *arg, const char_u **expr_start,
- const char_u **expr_end, int flags)
+const char_u *find_name_end(const char_u *arg, const char_u **expr_start,
+ const char_u **expr_end, int flags)
{
int mb_nest = 0;
int br_nest = 0;
@@ -19951,7 +7940,7 @@ static char_u *make_expanded_name(const char_u *in_start, char_u *expr_start,
}
xfree(temp_result);
- *in_end = c1; /* put char back for error messages */
+ *in_end = c1; // put char back for error messages
*expr_start = '{';
*expr_end = '}';
@@ -19960,7 +7949,7 @@ static char_u *make_expanded_name(const char_u *in_start, char_u *expr_start,
(const char_u **)&expr_start,
(const char_u **)&expr_end, 0);
if (expr_start != NULL) {
- /* Further expansion! */
+ // Further expansion!
temp_result = make_expanded_name(retval, expr_start,
expr_end, temp_result);
xfree(retval);
@@ -19975,7 +7964,7 @@ static char_u *make_expanded_name(const char_u *in_start, char_u *expr_start,
* Return TRUE if character "c" can be used in a variable or function name.
* Does not include '{' or '}' for magic braces.
*/
-static int eval_isnamec(int c)
+int eval_isnamec(int c)
{
return ASCII_ISALNUM(c) || c == '_' || c == ':' || c == AUTOLOAD_CHAR;
}
@@ -19984,7 +7973,7 @@ static int eval_isnamec(int c)
* Return TRUE if character "c" can be used as the first character in a
* variable or function name (excluding '{' and '}').
*/
-static int eval_isnamec1(int c)
+int eval_isnamec1(int c)
{
return ASCII_ISALPHA(c) || c == '_';
}
@@ -20174,10 +8163,7 @@ char_u *v_throwpoint(char_u *oldval)
*/
char_u *set_cmdarg(exarg_T *eap, char_u *oldarg)
{
- char_u *oldval;
- char_u *newval;
-
- oldval = vimvars[VV_CMDARG].vv_str;
+ char_u *oldval = vimvars[VV_CMDARG].vv_str;
if (eap == NULL) {
xfree(oldval);
vimvars[VV_CMDARG].vv_str = oldarg;
@@ -20193,14 +8179,18 @@ char_u *set_cmdarg(exarg_T *eap, char_u *oldarg)
if (eap->read_edit)
len += 7;
- if (eap->force_ff != 0)
- len += STRLEN(eap->cmd + eap->force_ff) + 6;
- if (eap->force_enc != 0)
+ if (eap->force_ff != 0) {
+ len += 10; // " ++ff=unix"
+ }
+ if (eap->force_enc != 0) {
len += STRLEN(eap->cmd + eap->force_enc) + 7;
- if (eap->bad_char != 0)
- len += 7 + 4; /* " ++bad=" + "keep" or "drop" */
+ }
+ if (eap->bad_char != 0) {
+ len += 7 + 4; // " ++bad=" + "keep" or "drop"
+ }
- newval = xmalloc(len + 1);
+ const size_t newval_len = len + 1;
+ char_u *newval = xmalloc(newval_len);
if (eap->force_bin == FORCE_BIN)
sprintf((char *)newval, " ++bin");
@@ -20212,18 +8202,23 @@ char_u *set_cmdarg(exarg_T *eap, char_u *oldarg)
if (eap->read_edit)
STRCAT(newval, " ++edit");
- if (eap->force_ff != 0)
- sprintf((char *)newval + STRLEN(newval), " ++ff=%s",
- eap->cmd + eap->force_ff);
- if (eap->force_enc != 0)
- sprintf((char *)newval + STRLEN(newval), " ++enc=%s",
- eap->cmd + eap->force_enc);
- if (eap->bad_char == BAD_KEEP)
+ if (eap->force_ff != 0) {
+ snprintf((char *)newval + STRLEN(newval), newval_len, " ++ff=%s",
+ eap->force_ff == 'u' ? "unix" :
+ eap->force_ff == 'd' ? "dos" : "mac");
+ }
+ if (eap->force_enc != 0) {
+ snprintf((char *)newval + STRLEN(newval), newval_len, " ++enc=%s",
+ eap->cmd + eap->force_enc);
+ }
+ if (eap->bad_char == BAD_KEEP) {
STRCPY(newval + STRLEN(newval), " ++bad=keep");
- else if (eap->bad_char == BAD_DROP)
+ } else if (eap->bad_char == BAD_DROP) {
STRCPY(newval + STRLEN(newval), " ++bad=drop");
- else if (eap->bad_char != 0)
- sprintf((char *)newval + STRLEN(newval), " ++bad=%c", eap->bad_char);
+ } else if (eap->bad_char != 0) {
+ snprintf((char *)newval + STRLEN(newval), newval_len, " ++bad=%c",
+ eap->bad_char);
+ }
vimvars[VV_CMDARG].vv_str = newval;
return oldval;
}
@@ -20232,7 +8227,7 @@ char_u *set_cmdarg(exarg_T *eap, char_u *oldarg)
* Get the value of internal variable "name".
* Return OK or FAIL.
*/
-static int get_var_tv(
+int get_var_tv(
const char *name,
int len, // length of "name"
typval_T *rettv, // NULL when only checking existence
@@ -20266,7 +8261,7 @@ static int get_var_tv(
}
/// Check if variable "name[len]" is a local variable or an argument.
-/// If so, "*eval_lavars_used" is set to TRUE.
+/// If so, "*eval_lavars_used" is set to true.
static void check_vars(const char *name, size_t len)
{
if (eval_lavars_used == NULL) {
@@ -20284,13 +8279,19 @@ static void check_vars(const char *name, size_t len)
}
/// check if special v:lua value for calling lua functions
+bool is_luafunc(partial_T *partial)
+{
+ return partial == vvlua_partial;
+}
+
+/// check if special v:lua value for calling lua functions
static bool tv_is_luafunc(typval_T *tv)
{
- return tv->v_type == VAR_PARTIAL && tv->vval.v_partial == vvlua_partial;
+ return tv->v_type == VAR_PARTIAL && is_luafunc(tv->vval.v_partial);
}
/// check the function name after "v:lua."
-static int check_luafunc_name(const char *str, bool paren)
+int check_luafunc_name(const char *str, bool paren)
{
const char *p = str;
while (ASCII_ISALNUM(*p) || *p == '_' || *p == '.') {
@@ -20306,12 +8307,12 @@ static int check_luafunc_name(const char *str, bool paren)
/// Handle expr[expr], expr[expr:expr] subscript and .name lookup.
/// Also handle function call with Funcref variable: func(expr)
/// Can all be combined: dict.func(expr)[idx]['func'](expr)
-static int
+int
handle_subscript(
const char **const arg,
typval_T *rettv,
- int evaluate, /* do more than finding the end */
- int verbose /* give error messages */
+ int evaluate, // do more than finding the end
+ int verbose // give error messages
)
{
int ret = OK;
@@ -20410,7 +8411,7 @@ handle_subscript(
return ret;
}
-void set_selfdict(typval_T *rettv, dict_T *selfdict)
+void set_selfdict(typval_T *const rettv, dict_T *const selfdict)
{
// Don't do this when "dict.Func" is already a partial that was bound
// explicitly (pt_auto is false).
@@ -20418,61 +8419,28 @@ void set_selfdict(typval_T *rettv, dict_T *selfdict)
&& rettv->vval.v_partial->pt_dict != NULL) {
return;
}
- char_u *fname;
- char_u *tofree = NULL;
- ufunc_T *fp;
- char_u fname_buf[FLEN_FIXED + 1];
- int error;
+ make_partial(selfdict, rettv);
+}
- if (rettv->v_type == VAR_PARTIAL && rettv->vval.v_partial->pt_func != NULL) {
- fp = rettv->vval.v_partial->pt_func;
- } else {
- fname = rettv->v_type == VAR_FUNC || rettv->v_type == VAR_STRING
- ? rettv->vval.v_string
- : rettv->vval.v_partial->pt_name;
- // Translate "s:func" to the stored function name.
- fname = fname_trans_sid(fname, fname_buf, &tofree, &error);
- fp = find_func(fname);
- xfree(tofree);
- }
+// Turn a typeval into a string. Similar to tv_get_string_buf() but uses
+// string() on Dict, List, etc.
+static const char *tv_stringify(typval_T *varp, char *buf)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (varp->v_type == VAR_LIST
+ || varp->v_type == VAR_DICT
+ || varp->v_type == VAR_FUNC
+ || varp->v_type == VAR_PARTIAL
+ || varp->v_type == VAR_FLOAT) {
+ typval_T tmp;
- // Turn "dict.Func" into a partial for "Func" with "dict".
- if (fp != NULL && (fp->uf_flags & FC_DICT)) {
- partial_T *pt = (partial_T *)xcalloc(1, sizeof(partial_T));
- pt->pt_refcount = 1;
- pt->pt_dict = selfdict;
- (selfdict->dv_refcount)++;
- pt->pt_auto = true;
- if (rettv->v_type == VAR_FUNC || rettv->v_type == VAR_STRING) {
- // Just a function: Take over the function name and use selfdict.
- pt->pt_name = rettv->vval.v_string;
- } else {
- partial_T *ret_pt = rettv->vval.v_partial;
- int i;
-
- // Partial: copy the function name, use selfdict and copy
- // args. Can't take over name or args, the partial might
- // be referenced elsewhere.
- if (ret_pt->pt_name != NULL) {
- pt->pt_name = vim_strsave(ret_pt->pt_name);
- func_ref(pt->pt_name);
- } else {
- pt->pt_func = ret_pt->pt_func;
- func_ptr_ref(pt->pt_func);
- }
- if (ret_pt->pt_argc > 0) {
- size_t arg_size = sizeof(typval_T) * ret_pt->pt_argc;
- pt->pt_argv = (typval_T *)xmalloc(arg_size);
- pt->pt_argc = ret_pt->pt_argc;
- for (i = 0; i < pt->pt_argc; i++) {
- tv_copy(&ret_pt->pt_argv[i], &pt->pt_argv[i]);
- }
- }
- partial_unref(ret_pt);
- }
- rettv->v_type = VAR_PARTIAL;
- rettv->vval.v_partial = pt;
+ f_string(varp, &tmp, NULL);
+ const char *const res = tv_get_string_buf(&tmp, buf);
+ tv_clear(varp);
+ *varp = tmp;
+ return res;
}
+ return tv_get_string_buf(varp, buf);
}
// Find variable "name" in the list of variables.
@@ -20480,8 +8448,8 @@ void set_selfdict(typval_T *rettv, dict_T *selfdict)
// Careful: "a:0" variables don't have a name.
// When "htp" is not NULL we are writing to the variable, set "htp" to the
// hashtab_T used.
-static dictitem_T *find_var(const char *const name, const size_t name_len,
- hashtab_T **htp, int no_autoload)
+dictitem_T *find_var(const char *const name, const size_t name_len,
+ hashtab_T **htp, int no_autoload)
{
const char *varname;
hashtab_T *const ht = find_var_ht(name, name_len, &varname);
@@ -20503,7 +8471,8 @@ static dictitem_T *find_var(const char *const name, const size_t name_len,
return find_var_in_scoped_ht(name, name_len, no_autoload || htp != NULL);
}
-/// Find variable in hashtab
+/// Find variable in hashtab.
+/// When "varname" is empty returns curwin/curtab/etc vars dictionary.
///
/// @param[in] ht Hashtab to find variable in.
/// @param[in] htname Hashtab name (first character).
@@ -20514,11 +8483,11 @@ static dictitem_T *find_var(const char *const name, const size_t name_len,
///
/// @return pointer to the dictionary item with the found variable or NULL if it
/// was not found.
-static dictitem_T *find_var_in_ht(hashtab_T *const ht,
- int htname,
- const char *const varname,
- const size_t varname_len,
- int no_autoload)
+dictitem_T *find_var_in_ht(hashtab_T *const ht,
+ int htname,
+ const char *const varname,
+ const size_t varname_len,
+ int no_autoload)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
hashitem_T *hi;
@@ -20532,10 +8501,8 @@ static dictitem_T *find_var_in_ht(hashtab_T *const ht,
case 'b': return (dictitem_T *)&curbuf->b_bufvar;
case 'w': return (dictitem_T *)&curwin->w_winvar;
case 't': return (dictitem_T *)&curtab->tp_winvar;
- case 'l': return (current_funccal == NULL
- ? NULL : (dictitem_T *)&current_funccal->l_vars_var);
- case 'a': return (current_funccal == NULL
- ? NULL : (dictitem_T *)&get_funccal()->l_avars_var);
+ case 'l': return get_funccal_local_var();
+ case 'a': return get_funccal_args_var();
}
return NULL;
}
@@ -20561,45 +8528,6 @@ static dictitem_T *find_var_in_ht(hashtab_T *const ht,
return TV_DICT_HI2DI(hi);
}
-// Get function call environment based on backtrace debug level
-static funccall_T *get_funccal(void)
-{
- funccall_T *funccal = current_funccal;
- if (debug_backtrace_level > 0) {
- for (int i = 0; i < debug_backtrace_level; i++) {
- funccall_T *temp_funccal = funccal->caller;
- if (temp_funccal) {
- funccal = temp_funccal;
- } else {
- // backtrace level overflow. reset to max
- debug_backtrace_level = i;
- }
- }
- }
-
- return funccal;
-}
-
-/// Return the hashtable used for argument in the current funccal.
-/// Return NULL if there is no current funccal.
-static hashtab_T *get_funccal_args_ht(void)
-{
- if (current_funccal == NULL) {
- return NULL;
- }
- return &get_funccal()->l_avars.dv_hashtab;
-}
-
-/// Return the hashtable used for local variables in the current funccal.
-/// Return NULL if there is no current funccal.
-static hashtab_T *get_funccal_local_ht(void)
-{
- if (current_funccal == NULL) {
- return NULL;
- }
- return &get_funccal()->l_vars.dv_hashtab;
-}
-
/// Finds the dict (g:, l:, s:, …) and hashtable used for a variable.
///
/// @param[in] name Variable name, possibly with scope prefix.
@@ -20613,6 +8541,7 @@ static hashtab_T *find_var_ht_dict(const char *name, const size_t name_len,
const char **varname, dict_T **d)
{
hashitem_T *hi;
+ funccall_T *funccal = get_funccal();
*d = NULL;
if (name_len == 0) {
@@ -20632,16 +8561,16 @@ static hashtab_T *find_var_ht_dict(const char *name, const size_t name_len,
return &compat_hashtab;
}
- if (current_funccal == NULL) {
+ if (funccal == NULL) { // global variable
*d = &globvardict;
- } else {
- *d = &get_funccal()->l_vars; // l: variable
+ } else { // l: variable
+ *d = &funccal->l_vars;
}
goto end;
}
*varname = name + 2;
- if (*name == 'g') { // global variable
+ if (*name == 'g') { // global variable
*d = &globvardict;
} else if (name_len > 2
&& (memchr(name + 2, ':', name_len - 2) != NULL
@@ -20658,10 +8587,10 @@ static hashtab_T *find_var_ht_dict(const char *name, const size_t name_len,
*d = curtab->tp_vars;
} else if (*name == 'v') { // v: variable
*d = &vimvardict;
- } else if (*name == 'a' && current_funccal != NULL) { // function argument
- *d = &get_funccal()->l_avars;
- } else if (*name == 'l' && current_funccal != NULL) { // local variable
- *d = &get_funccal()->l_vars;
+ } else if (*name == 'a' && funccal != NULL) { // function argument
+ *d = &funccal->l_avars;
+ } else if (*name == 'l' && funccal != NULL) { // local variable
+ *d = &funccal->l_vars;
} else if (*name == 's' // script variable
&& current_sctx.sc_sid > 0
&& current_sctx.sc_sid <= ga_scripts.ga_len) {
@@ -20680,8 +8609,8 @@ end:
/// prefix.
///
/// @return Scope hashtab, NULL if name is not valid.
-static hashtab_T *find_var_ht(const char *name, const size_t name_len,
- const char **varname)
+hashtab_T *find_var_ht(const char *name, const size_t name_len,
+ const char **varname)
{
dict_T *d;
return find_var_ht_dict(name, name_len, varname, &d);
@@ -20776,7 +8705,7 @@ void vars_clear(hashtab_T *ht)
/*
* Like vars_clear(), but only free the value if "free_val" is TRUE.
*/
-static void vars_clear_ext(hashtab_T *ht, int free_val)
+void vars_clear_ext(hashtab_T *ht, int free_val)
{
int todo;
hashitem_T *hi;
@@ -20878,8 +8807,8 @@ static void list_one_var_a(const char *prefix, const char *name,
/// @param[in] name_len Length of the variable name.
/// @param tv Variable value.
/// @param[in] copy True if value in tv is to be copied.
-static void set_var(const char *name, const size_t name_len, typval_T *const tv,
- const bool copy)
+void set_var(const char *name, const size_t name_len, typval_T *const tv,
+ const bool copy)
FUNC_ATTR_NONNULL_ALL
{
set_var_const(name, name_len, tv, copy, false);
@@ -21048,7 +8977,7 @@ bool var_check_ro(const int flags, const char *name,
{
const char *error_message = NULL;
if (flags & DI_FLAGS_RO) {
- error_message = N_(e_readonlyvar);
+ error_message = _(e_readonlyvar);
} else if ((flags & DI_FLAGS_RO_SBX) && sandbox) {
error_message = N_("E794: Cannot set variable in the sandbox: \"%.*s\"");
}
@@ -21086,8 +9015,8 @@ bool var_check_ro(const int flags, const char *name,
/// gettext.
///
/// @return True if variable is fixed, false otherwise.
-static bool var_check_fixed(const int flags, const char *name,
- size_t name_len)
+bool var_check_fixed(const int flags, const char *name,
+ size_t name_len)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
if (flags & DI_FLAGS_FIX) {
@@ -21229,10 +9158,10 @@ int var_item_copy(const vimconv_T *const conv,
case VAR_DICT:
to->v_type = VAR_DICT;
to->v_lock = 0;
- if (from->vval.v_dict == NULL)
+ if (from->vval.v_dict == NULL) {
to->vval.v_dict = NULL;
- else if (copyID != 0 && from->vval.v_dict->dv_copyID == copyID) {
- /* use the copy made earlier */
+ } else if (copyID != 0 && from->vval.v_dict->dv_copyID == copyID) {
+ // use the copy made earlier
to->vval.v_dict = from->vval.v_dict->dv_copydict;
++to->vval.v_dict->dv_refcount;
} else {
@@ -21330,13 +9259,7 @@ void ex_echo(exarg_T *eap)
*/
void ex_echohl(exarg_T *eap)
{
- int id;
-
- id = syn_name2id(eap->arg);
- if (id == 0)
- echo_attr = 0;
- else
- echo_attr = syn_id2attr(id);
+ echo_attr = syn_name2attr(eap->arg);
}
/*
@@ -21365,7 +9288,10 @@ void ex_execute(exarg_T *eap)
}
if (!eap->skip) {
- const char *const argstr = tv_get_string(&rettv);
+ char buf[NUMBUFLEN];
+ const char *const argstr = eap->cmdidx == CMD_execute
+ ? tv_get_string_buf(&rettv, buf)
+ : tv_stringify(&rettv, buf);
const size_t len = strlen(argstr);
ga_grow(&ga, len + 2);
if (!GA_EMPTY(&ga)) {
@@ -21392,7 +9318,7 @@ void ex_execute(exarg_T *eap)
MSG_ATTR(ga.ga_data, echo_attr);
ui_flush();
} else if (eap->cmdidx == CMD_echoerr) {
- /* We don't want to abort following commands, restore did_emsg. */
+ // We don't want to abort following commands, restore did_emsg.
save_did_emsg = did_emsg;
msg_ext_set_kind("echoerr");
EMSG((char_u *)ga.ga_data);
@@ -21447,1123 +9373,8 @@ static const char *find_option_end(const char **const arg, int *const opt_flags)
return p;
}
-/*
- * ":function"
- */
-void ex_function(exarg_T *eap)
-{
- char_u *theline;
- char_u *line_to_free = NULL;
- int c;
- int saved_did_emsg;
- int saved_wait_return = need_wait_return;
- char_u *name = NULL;
- char_u *p;
- char_u *arg;
- char_u *line_arg = NULL;
- garray_T newargs;
- garray_T newlines;
- int varargs = false;
- int flags = 0;
- ufunc_T *fp;
- bool overwrite = false;
- int indent;
- int nesting;
- dictitem_T *v;
- funcdict_T fudi;
- static int func_nr = 0; /* number for nameless function */
- int paren;
- hashtab_T *ht;
- int todo;
- hashitem_T *hi;
- linenr_T sourcing_lnum_off;
- linenr_T sourcing_lnum_top;
- bool is_heredoc = false;
- char_u *skip_until = NULL;
- char_u *heredoc_trimmed = NULL;
- bool show_block = false;
- bool do_concat = true;
-
- /*
- * ":function" without argument: list functions.
- */
- if (ends_excmd(*eap->arg)) {
- if (!eap->skip) {
- todo = (int)func_hashtab.ht_used;
- for (hi = func_hashtab.ht_array; todo > 0 && !got_int; ++hi) {
- if (!HASHITEM_EMPTY(hi)) {
- --todo;
- fp = HI2UF(hi);
- if (message_filtered(fp->uf_name)) {
- continue;
- }
- if (!func_name_refcount(fp->uf_name)) {
- list_func_head(fp, false, false);
- }
- }
- }
- }
- eap->nextcmd = check_nextcmd(eap->arg);
- return;
- }
-
- /*
- * ":function /pat": list functions matching pattern.
- */
- if (*eap->arg == '/') {
- p = skip_regexp(eap->arg + 1, '/', TRUE, NULL);
- if (!eap->skip) {
- regmatch_T regmatch;
-
- c = *p;
- *p = NUL;
- regmatch.regprog = vim_regcomp(eap->arg + 1, RE_MAGIC);
- *p = c;
- if (regmatch.regprog != NULL) {
- regmatch.rm_ic = p_ic;
-
- todo = (int)func_hashtab.ht_used;
- for (hi = func_hashtab.ht_array; todo > 0 && !got_int; ++hi) {
- if (!HASHITEM_EMPTY(hi)) {
- --todo;
- fp = HI2UF(hi);
- if (!isdigit(*fp->uf_name)
- && vim_regexec(&regmatch, fp->uf_name, 0))
- list_func_head(fp, false, false);
- }
- }
- vim_regfree(regmatch.regprog);
- }
- }
- if (*p == '/')
- ++p;
- eap->nextcmd = check_nextcmd(p);
- return;
- }
-
- // Get the function name. There are these situations:
- // func function name
- // "name" == func, "fudi.fd_dict" == NULL
- // dict.func new dictionary entry
- // "name" == NULL, "fudi.fd_dict" set,
- // "fudi.fd_di" == NULL, "fudi.fd_newkey" == func
- // dict.func existing dict entry with a Funcref
- // "name" == func, "fudi.fd_dict" set,
- // "fudi.fd_di" set, "fudi.fd_newkey" == NULL
- // dict.func existing dict entry that's not a Funcref
- // "name" == NULL, "fudi.fd_dict" set,
- // "fudi.fd_di" set, "fudi.fd_newkey" == NULL
- // s:func script-local function name
- // g:func global function name, same as "func"
- p = eap->arg;
- name = trans_function_name(&p, eap->skip, TFN_NO_AUTOLOAD, &fudi, NULL);
- paren = (vim_strchr(p, '(') != NULL);
- if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip) {
- /*
- * Return on an invalid expression in braces, unless the expression
- * evaluation has been cancelled due to an aborting error, an
- * interrupt, or an exception.
- */
- if (!aborting()) {
- if (fudi.fd_newkey != NULL) {
- EMSG2(_(e_dictkey), fudi.fd_newkey);
- }
- xfree(fudi.fd_newkey);
- return;
- } else
- eap->skip = TRUE;
- }
-
- /* An error in a function call during evaluation of an expression in magic
- * braces should not cause the function not to be defined. */
- saved_did_emsg = did_emsg;
- did_emsg = FALSE;
-
- //
- // ":function func" with only function name: list function.
- // If bang is given:
- // - include "!" in function head
- // - exclude line numbers from function body
- //
- if (!paren) {
- if (!ends_excmd(*skipwhite(p))) {
- EMSG(_(e_trailing));
- goto ret_free;
- }
- eap->nextcmd = check_nextcmd(p);
- if (eap->nextcmd != NULL)
- *p = NUL;
- if (!eap->skip && !got_int) {
- fp = find_func(name);
- if (fp != NULL) {
- list_func_head(fp, !eap->forceit, eap->forceit);
- for (int j = 0; j < fp->uf_lines.ga_len && !got_int; j++) {
- if (FUNCLINE(fp, j) == NULL) {
- continue;
- }
- msg_putchar('\n');
- if (!eap->forceit) {
- msg_outnum((long)j + 1);
- if (j < 9) {
- msg_putchar(' ');
- }
- if (j < 99) {
- msg_putchar(' ');
- }
- }
- msg_prt_line(FUNCLINE(fp, j), false);
- ui_flush(); // show a line at a time
- os_breakcheck();
- }
- if (!got_int) {
- msg_putchar('\n');
- msg_puts(eap->forceit ? "endfunction" : " endfunction");
- }
- } else
- emsg_funcname(N_("E123: Undefined function: %s"), name);
- }
- goto ret_free;
- }
-
- /*
- * ":function name(arg1, arg2)" Define function.
- */
- p = skipwhite(p);
- if (*p != '(') {
- if (!eap->skip) {
- EMSG2(_("E124: Missing '(': %s"), eap->arg);
- goto ret_free;
- }
- /* attempt to continue by skipping some text */
- if (vim_strchr(p, '(') != NULL)
- p = vim_strchr(p, '(');
- }
- p = skipwhite(p + 1);
-
- ga_init(&newargs, (int)sizeof(char_u *), 3);
- ga_init(&newlines, (int)sizeof(char_u *), 3);
-
- if (!eap->skip) {
- /* Check the name of the function. Unless it's a dictionary function
- * (that we are overwriting). */
- if (name != NULL)
- arg = name;
- else
- arg = fudi.fd_newkey;
- if (arg != NULL && (fudi.fd_di == NULL || !tv_is_func(fudi.fd_di->di_tv))) {
- int j = (*arg == K_SPECIAL) ? 3 : 0;
- while (arg[j] != NUL && (j == 0 ? eval_isnamec1(arg[j])
- : eval_isnamec(arg[j])))
- ++j;
- if (arg[j] != NUL)
- emsg_funcname((char *)e_invarg2, arg);
- }
- /* Disallow using the g: dict. */
- if (fudi.fd_dict != NULL && fudi.fd_dict->dv_scope == VAR_DEF_SCOPE)
- EMSG(_("E862: Cannot use g: here"));
- }
-
- if (get_function_args(&p, ')', &newargs, &varargs, eap->skip) == FAIL) {
- goto errret_2;
- }
-
- if (KeyTyped && ui_has(kUICmdline)) {
- show_block = true;
- ui_ext_cmdline_block_append(0, (const char *)eap->cmd);
- }
-
- // find extra arguments "range", "dict", "abort" and "closure"
- for (;; ) {
- p = skipwhite(p);
- if (STRNCMP(p, "range", 5) == 0) {
- flags |= FC_RANGE;
- p += 5;
- } else if (STRNCMP(p, "dict", 4) == 0) {
- flags |= FC_DICT;
- p += 4;
- } else if (STRNCMP(p, "abort", 5) == 0) {
- flags |= FC_ABORT;
- p += 5;
- } else if (STRNCMP(p, "closure", 7) == 0) {
- flags |= FC_CLOSURE;
- p += 7;
- if (current_funccal == NULL) {
- emsg_funcname(N_
- ("E932: Closure function should not be at top level: %s"),
- name == NULL ? (char_u *)"" : name);
- goto erret;
- }
- } else {
- break;
- }
- }
-
- /* When there is a line break use what follows for the function body.
- * Makes 'exe "func Test()\n...\nendfunc"' work. */
- if (*p == '\n') {
- line_arg = p + 1;
- } else if (*p != NUL && *p != '"' && !eap->skip && !did_emsg) {
- EMSG(_(e_trailing));
- }
-
- /*
- * Read the body of the function, until ":endfunction" is found.
- */
- if (KeyTyped) {
- /* Check if the function already exists, don't let the user type the
- * whole function before telling him it doesn't work! For a script we
- * need to skip the body to be able to find what follows. */
- if (!eap->skip && !eap->forceit) {
- if (fudi.fd_dict != NULL && fudi.fd_newkey == NULL)
- EMSG(_(e_funcdict));
- else if (name != NULL && find_func(name) != NULL)
- emsg_funcname(e_funcexts, name);
- }
-
- if (!eap->skip && did_emsg)
- goto erret;
-
- if (!ui_has(kUICmdline)) {
- msg_putchar('\n'); // don't overwrite the function name
- }
- cmdline_row = msg_row;
- }
-
- // Save the starting line number.
- sourcing_lnum_top = sourcing_lnum;
-
- indent = 2;
- nesting = 0;
- for (;; ) {
- if (KeyTyped) {
- msg_scroll = true;
- saved_wait_return = false;
- }
- need_wait_return = false;
-
- if (line_arg != NULL) {
- /* Use eap->arg, split up in parts by line breaks. */
- theline = line_arg;
- p = vim_strchr(theline, '\n');
- if (p == NULL)
- line_arg += STRLEN(line_arg);
- else {
- *p = NUL;
- line_arg = p + 1;
- }
- } else {
- xfree(line_to_free);
- if (eap->getline == NULL) {
- theline = getcmdline(':', 0L, indent, do_concat);
- } else {
- theline = eap->getline(':', eap->cookie, indent, do_concat);
- }
- line_to_free = theline;
- }
- if (KeyTyped) {
- lines_left = Rows - 1;
- }
- if (theline == NULL) {
- EMSG(_("E126: Missing :endfunction"));
- goto erret;
- }
- if (show_block) {
- assert(indent >= 0);
- ui_ext_cmdline_block_append((size_t)indent, (const char *)theline);
- }
-
- // Detect line continuation: sourcing_lnum increased more than one.
- sourcing_lnum_off = get_sourced_lnum(eap->getline, eap->cookie);
- if (sourcing_lnum < sourcing_lnum_off) {
- sourcing_lnum_off -= sourcing_lnum;
- } else {
- sourcing_lnum_off = 0;
- }
-
- if (skip_until != NULL) {
- // Don't check for ":endfunc" between
- // * ":append" and "."
- // * ":python <<EOF" and "EOF"
- // * ":let {var-name} =<< [trim] {marker}" and "{marker}"
- if (heredoc_trimmed == NULL
- || (is_heredoc && skipwhite(theline) == theline)
- || STRNCMP(theline, heredoc_trimmed,
- STRLEN(heredoc_trimmed)) == 0) {
- if (heredoc_trimmed == NULL) {
- p = theline;
- } else if (is_heredoc) {
- p = skipwhite(theline) == theline
- ? theline : theline + STRLEN(heredoc_trimmed);
- } else {
- p = theline + STRLEN(heredoc_trimmed);
- }
- if (STRCMP(p, skip_until) == 0) {
- XFREE_CLEAR(skip_until);
- XFREE_CLEAR(heredoc_trimmed);
- do_concat = true;
- is_heredoc = false;
- }
- }
- } else {
- /* skip ':' and blanks*/
- for (p = theline; ascii_iswhite(*p) || *p == ':'; ++p)
- ;
-
- /* Check for "endfunction". */
- if (checkforcmd(&p, "endfunction", 4) && nesting-- == 0) {
- if (*p == '!') {
- p++;
- }
- char_u *nextcmd = NULL;
- if (*p == '|') {
- nextcmd = p + 1;
- } else if (line_arg != NULL && *skipwhite(line_arg) != NUL) {
- nextcmd = line_arg;
- } else if (*p != NUL && *p != '"' && p_verbose > 0) {
- give_warning2((char_u *)_("W22: Text found after :endfunction: %s"),
- p, true);
- }
- if (nextcmd != NULL) {
- // Another command follows. If the line came from "eap" we
- // can simply point into it, otherwise we need to change
- // "eap->cmdlinep".
- eap->nextcmd = nextcmd;
- if (line_to_free != NULL) {
- xfree(*eap->cmdlinep);
- *eap->cmdlinep = line_to_free;
- line_to_free = NULL;
- }
- }
- break;
- }
-
- /* Increase indent inside "if", "while", "for" and "try", decrease
- * at "end". */
- if (indent > 2 && STRNCMP(p, "end", 3) == 0)
- indent -= 2;
- else if (STRNCMP(p, "if", 2) == 0
- || STRNCMP(p, "wh", 2) == 0
- || STRNCMP(p, "for", 3) == 0
- || STRNCMP(p, "try", 3) == 0)
- indent += 2;
-
- /* Check for defining a function inside this function. */
- if (checkforcmd(&p, "function", 2)) {
- if (*p == '!') {
- p = skipwhite(p + 1);
- }
- p += eval_fname_script((const char *)p);
- xfree(trans_function_name(&p, true, 0, NULL, NULL));
- if (*skipwhite(p) == '(') {
- nesting++;
- indent += 2;
- }
- }
-
- // Check for ":append", ":change", ":insert".
- p = skip_range(p, NULL);
- if ((p[0] == 'a' && (!ASCII_ISALPHA(p[1]) || p[1] == 'p'))
- || (p[0] == 'c'
- && (!ASCII_ISALPHA(p[1])
- || (p[1] == 'h' && (!ASCII_ISALPHA(p[2])
- || (p[2] == 'a'
- && (STRNCMP(&p[3], "nge", 3) != 0
- || !ASCII_ISALPHA(p[6])))))))
- || (p[0] == 'i'
- && (!ASCII_ISALPHA(p[1]) || (p[1] == 'n'
- && (!ASCII_ISALPHA(p[2])
- || (p[2] == 's')))))) {
- skip_until = vim_strsave((char_u *)".");
- }
-
- // heredoc: Check for ":python <<EOF", ":lua <<EOF", etc.
- arg = skipwhite(skiptowhite(p));
- if (arg[0] == '<' && arg[1] =='<'
- && ((p[0] == 'p' && p[1] == 'y'
- && (!ASCII_ISALNUM(p[2]) || p[2] == 't'
- || ((p[2] == '3' || p[2] == 'x')
- && !ASCII_ISALPHA(p[3]))))
- || (p[0] == 'p' && p[1] == 'e'
- && (!ASCII_ISALPHA(p[2]) || p[2] == 'r'))
- || (p[0] == 't' && p[1] == 'c'
- && (!ASCII_ISALPHA(p[2]) || p[2] == 'l'))
- || (p[0] == 'l' && p[1] == 'u' && p[2] == 'a'
- && !ASCII_ISALPHA(p[3]))
- || (p[0] == 'r' && p[1] == 'u' && p[2] == 'b'
- && (!ASCII_ISALPHA(p[3]) || p[3] == 'y'))
- || (p[0] == 'm' && p[1] == 'z'
- && (!ASCII_ISALPHA(p[2]) || p[2] == 's'))
- )) {
- /* ":python <<" continues until a dot, like ":append" */
- p = skipwhite(arg + 2);
- if (*p == NUL)
- skip_until = vim_strsave((char_u *)".");
- else
- skip_until = vim_strsave(p);
- }
-
- // Check for ":let v =<< [trim] EOF"
- // and ":let [a, b] =<< [trim] EOF"
- arg = skipwhite(skiptowhite(p));
- if (*arg == '[') {
- arg = vim_strchr(arg, ']');
- }
- if (arg != NULL) {
- arg = skipwhite(skiptowhite(arg));
- if (arg[0] == '='
- && arg[1] == '<'
- && arg[2] =='<'
- && (p[0] == 'l'
- && p[1] == 'e'
- && (!ASCII_ISALNUM(p[2])
- || (p[2] == 't' && !ASCII_ISALNUM(p[3]))))) {
- p = skipwhite(arg + 3);
- if (STRNCMP(p, "trim", 4) == 0) {
- // Ignore leading white space.
- p = skipwhite(p + 4);
- heredoc_trimmed =
- vim_strnsave(theline, (int)(skipwhite(theline) - theline));
- }
- skip_until = vim_strnsave(p, (int)(skiptowhite(p) - p));
- do_concat = false;
- is_heredoc = true;
- }
- }
- }
-
- /* Add the line to the function. */
- ga_grow(&newlines, 1 + sourcing_lnum_off);
-
- /* Copy the line to newly allocated memory. get_one_sourceline()
- * allocates 250 bytes per line, this saves 80% on average. The cost
- * is an extra alloc/free. */
- p = vim_strsave(theline);
- ((char_u **)(newlines.ga_data))[newlines.ga_len++] = p;
-
- /* Add NULL lines for continuation lines, so that the line count is
- * equal to the index in the growarray. */
- while (sourcing_lnum_off-- > 0)
- ((char_u **)(newlines.ga_data))[newlines.ga_len++] = NULL;
-
- /* Check for end of eap->arg. */
- if (line_arg != NULL && *line_arg == NUL)
- line_arg = NULL;
- }
-
- /* Don't define the function when skipping commands or when an error was
- * detected. */
- if (eap->skip || did_emsg)
- goto erret;
-
- /*
- * If there are no errors, add the function
- */
- if (fudi.fd_dict == NULL) {
- v = find_var((const char *)name, STRLEN(name), &ht, false);
- if (v != NULL && v->di_tv.v_type == VAR_FUNC) {
- emsg_funcname(N_("E707: Function name conflicts with variable: %s"),
- name);
- goto erret;
- }
-
- fp = find_func(name);
- if (fp != NULL) {
- // Function can be replaced with "function!" and when sourcing the
- // same script again, but only once.
- if (!eap->forceit
- && (fp->uf_script_ctx.sc_sid != current_sctx.sc_sid
- || fp->uf_script_ctx.sc_seq == current_sctx.sc_seq)) {
- emsg_funcname(e_funcexts, name);
- goto erret;
- }
- if (fp->uf_calls > 0) {
- emsg_funcname(N_("E127: Cannot redefine function %s: It is in use"),
- name);
- goto erret;
- }
- if (fp->uf_refcount > 1) {
- // This function is referenced somewhere, don't redefine it but
- // create a new one.
- (fp->uf_refcount)--;
- fp->uf_flags |= FC_REMOVED;
- fp = NULL;
- overwrite = true;
- } else {
- // redefine existing function
- XFREE_CLEAR(name);
- func_clear_items(fp);
- fp->uf_profiling = false;
- fp->uf_prof_initialized = false;
- }
- }
- } else {
- char numbuf[20];
-
- fp = NULL;
- if (fudi.fd_newkey == NULL && !eap->forceit) {
- EMSG(_(e_funcdict));
- goto erret;
- }
- if (fudi.fd_di == NULL) {
- if (tv_check_lock(fudi.fd_dict->dv_lock, (const char *)eap->arg,
- TV_CSTRING)) {
- // Can't add a function to a locked dictionary
- goto erret;
- }
- } else if (tv_check_lock(fudi.fd_di->di_tv.v_lock, (const char *)eap->arg,
- TV_CSTRING)) {
- // Can't change an existing function if it is locked
- goto erret;
- }
-
- /* Give the function a sequential number. Can only be used with a
- * Funcref! */
- xfree(name);
- sprintf(numbuf, "%d", ++func_nr);
- name = vim_strsave((char_u *)numbuf);
- }
-
- if (fp == NULL) {
- if (fudi.fd_dict == NULL && vim_strchr(name, AUTOLOAD_CHAR) != NULL) {
- int slen, plen;
- char_u *scriptname;
-
- /* Check that the autoload name matches the script name. */
- int j = FAIL;
- if (sourcing_name != NULL) {
- scriptname = (char_u *)autoload_name((const char *)name, STRLEN(name));
- p = vim_strchr(scriptname, '/');
- plen = (int)STRLEN(p);
- slen = (int)STRLEN(sourcing_name);
- if (slen > plen && fnamecmp(p,
- sourcing_name + slen - plen) == 0)
- j = OK;
- xfree(scriptname);
- }
- if (j == FAIL) {
- EMSG2(_(
- "E746: Function name does not match script file name: %s"),
- name);
- goto erret;
- }
- }
-
- fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1);
-
- if (fudi.fd_dict != NULL) {
- if (fudi.fd_di == NULL) {
- // Add new dict entry
- fudi.fd_di = tv_dict_item_alloc((const char *)fudi.fd_newkey);
- if (tv_dict_add(fudi.fd_dict, fudi.fd_di) == FAIL) {
- xfree(fudi.fd_di);
- xfree(fp);
- goto erret;
- }
- } else {
- // Overwrite existing dict entry.
- tv_clear(&fudi.fd_di->di_tv);
- }
- fudi.fd_di->di_tv.v_type = VAR_FUNC;
- fudi.fd_di->di_tv.vval.v_string = vim_strsave(name);
-
- /* behave like "dict" was used */
- flags |= FC_DICT;
- }
-
- /* insert the new function in the function list */
- STRCPY(fp->uf_name, name);
- if (overwrite) {
- hi = hash_find(&func_hashtab, name);
- hi->hi_key = UF2HIKEY(fp);
- } else if (hash_add(&func_hashtab, UF2HIKEY(fp)) == FAIL) {
- xfree(fp);
- goto erret;
- }
- fp->uf_refcount = 1;
- }
- fp->uf_args = newargs;
- fp->uf_lines = newlines;
- if ((flags & FC_CLOSURE) != 0) {
- register_closure(fp);
- } else {
- fp->uf_scoped = NULL;
- }
- if (prof_def_func()) {
- func_do_profile(fp);
- }
- fp->uf_varargs = varargs;
- if (sandbox) {
- flags |= FC_SANDBOX;
- }
- fp->uf_flags = flags;
- fp->uf_calls = 0;
- fp->uf_script_ctx = current_sctx;
- fp->uf_script_ctx.sc_lnum += sourcing_lnum_top;
-
- goto ret_free;
-
-erret:
- ga_clear_strings(&newargs);
-errret_2:
- ga_clear_strings(&newlines);
-ret_free:
- xfree(skip_until);
- xfree(line_to_free);
- xfree(fudi.fd_newkey);
- xfree(name);
- did_emsg |= saved_did_emsg;
- need_wait_return |= saved_wait_return;
- if (show_block) {
- ui_ext_cmdline_block_leave();
- }
-} // NOLINT(readability/fn_size)
-
-/// Get a function name, translating "<SID>" and "<SNR>".
-/// Also handles a Funcref in a List or Dictionary.
-/// flags:
-/// TFN_INT: internal function name OK
-/// TFN_QUIET: be quiet
-/// TFN_NO_AUTOLOAD: do not use script autoloading
-/// TFN_NO_DEREF: do not dereference a Funcref
-/// Advances "pp" to just after the function name (if no error).
-///
-/// @return the function name in allocated memory, or NULL for failure.
-static char_u *
-trans_function_name(
- char_u **pp,
- int skip, // only find the end, don't evaluate
- int flags,
- funcdict_T *fdp, // return: info about dictionary used
- partial_T **partial // return: partial of a FuncRef
-)
-{
- char_u *name = NULL;
- const char_u *start;
- const char_u *end;
- int lead;
- int len;
- lval_T lv;
-
- if (fdp != NULL)
- memset(fdp, 0, sizeof(funcdict_T));
- start = *pp;
-
- /* Check for hard coded <SNR>: already translated function ID (from a user
- * command). */
- if ((*pp)[0] == K_SPECIAL && (*pp)[1] == KS_EXTRA
- && (*pp)[2] == (int)KE_SNR) {
- *pp += 3;
- len = get_id_len((const char **)pp) + 3;
- return (char_u *)xmemdupz(start, len);
- }
-
- /* A name starting with "<SID>" or "<SNR>" is local to a script. But
- * don't skip over "s:", get_lval() needs it for "s:dict.func". */
- lead = eval_fname_script((const char *)start);
- if (lead > 2) {
- start += lead;
- }
-
- // Note that TFN_ flags use the same values as GLV_ flags.
- end = get_lval((char_u *)start, NULL, &lv, false, skip, flags | GLV_READ_ONLY,
- lead > 2 ? 0 : FNE_CHECK_START);
- if (end == start) {
- if (!skip)
- EMSG(_("E129: Function name required"));
- goto theend;
- }
- if (end == NULL || (lv.ll_tv != NULL && (lead > 2 || lv.ll_range))) {
- /*
- * Report an invalid expression in braces, unless the expression
- * evaluation has been cancelled due to an aborting error, an
- * interrupt, or an exception.
- */
- if (!aborting()) {
- if (end != NULL) {
- emsgf(_(e_invarg2), start);
- }
- } else {
- *pp = (char_u *)find_name_end(start, NULL, NULL, FNE_INCL_BR);
- }
- goto theend;
- }
-
- if (lv.ll_tv != NULL) {
- if (fdp != NULL) {
- fdp->fd_dict = lv.ll_dict;
- fdp->fd_newkey = lv.ll_newkey;
- lv.ll_newkey = NULL;
- fdp->fd_di = lv.ll_di;
- }
- if (lv.ll_tv->v_type == VAR_FUNC && lv.ll_tv->vval.v_string != NULL) {
- name = vim_strsave(lv.ll_tv->vval.v_string);
- *pp = (char_u *)end;
- } else if (lv.ll_tv->v_type == VAR_PARTIAL
- && lv.ll_tv->vval.v_partial != NULL) {
- if (lv.ll_tv->vval.v_partial == vvlua_partial && *end == '.') {
- len = check_luafunc_name((const char *)end+1, true);
- if (len == 0) {
- EMSG2(e_invexpr2, "v:lua");
- goto theend;
- }
- name = xmallocz(len);
- memcpy(name, end+1, len);
- *pp = (char_u *)end+1+len;
- } else {
- name = vim_strsave(partial_name(lv.ll_tv->vval.v_partial));
- *pp = (char_u *)end;
- }
- if (partial != NULL) {
- *partial = lv.ll_tv->vval.v_partial;
- }
- } else {
- if (!skip && !(flags & TFN_QUIET) && (fdp == NULL
- || lv.ll_dict == NULL
- || fdp->fd_newkey == NULL)) {
- EMSG(_(e_funcref));
- } else {
- *pp = (char_u *)end;
- }
- name = NULL;
- }
- goto theend;
- }
-
- if (lv.ll_name == NULL) {
- // Error found, but continue after the function name.
- *pp = (char_u *)end;
- goto theend;
- }
-
- /* Check if the name is a Funcref. If so, use the value. */
- if (lv.ll_exp_name != NULL) {
- len = (int)strlen(lv.ll_exp_name);
- name = deref_func_name(lv.ll_exp_name, &len, partial,
- flags & TFN_NO_AUTOLOAD);
- if ((const char *)name == lv.ll_exp_name) {
- name = NULL;
- }
- } else if (!(flags & TFN_NO_DEREF)) {
- len = (int)(end - *pp);
- name = deref_func_name((const char *)(*pp), &len, partial,
- flags & TFN_NO_AUTOLOAD);
- if (name == *pp) {
- name = NULL;
- }
- }
- if (name != NULL) {
- name = vim_strsave(name);
- *pp = (char_u *)end;
- if (strncmp((char *)name, "<SNR>", 5) == 0) {
- // Change "<SNR>" to the byte sequence.
- name[0] = K_SPECIAL;
- name[1] = KS_EXTRA;
- name[2] = (int)KE_SNR;
- memmove(name + 3, name + 5, strlen((char *)name + 5) + 1);
- }
- goto theend;
- }
-
- if (lv.ll_exp_name != NULL) {
- len = (int)strlen(lv.ll_exp_name);
- if (lead <= 2 && lv.ll_name == lv.ll_exp_name
- && lv.ll_name_len >= 2 && memcmp(lv.ll_name, "s:", 2) == 0) {
- // When there was "s:" already or the name expanded to get a
- // leading "s:" then remove it.
- lv.ll_name += 2;
- lv.ll_name_len -= 2;
- len -= 2;
- lead = 2;
- }
- } else {
- // Skip over "s:" and "g:".
- if (lead == 2 || (lv.ll_name[0] == 'g' && lv.ll_name[1] == ':')) {
- lv.ll_name += 2;
- lv.ll_name_len -= 2;
- }
- len = (int)((const char *)end - lv.ll_name);
- }
-
- size_t sid_buf_len = 0;
- char sid_buf[20];
-
- // Copy the function name to allocated memory.
- // Accept <SID>name() inside a script, translate into <SNR>123_name().
- // Accept <SNR>123_name() outside a script.
- if (skip) {
- lead = 0; // do nothing
- } else if (lead > 0) {
- lead = 3;
- if ((lv.ll_exp_name != NULL && eval_fname_sid(lv.ll_exp_name))
- || eval_fname_sid((const char *)(*pp))) {
- // It's "s:" or "<SID>".
- if (current_sctx.sc_sid <= 0) {
- EMSG(_(e_usingsid));
- goto theend;
- }
- sid_buf_len = snprintf(sid_buf, sizeof(sid_buf),
- "%" PRIdSCID "_", current_sctx.sc_sid);
- lead += sid_buf_len;
- }
- } else if (!(flags & TFN_INT)
- && builtin_function(lv.ll_name, lv.ll_name_len)) {
- EMSG2(_("E128: Function name must start with a capital or \"s:\": %s"),
- start);
- goto theend;
- }
-
- if (!skip && !(flags & TFN_QUIET) && !(flags & TFN_NO_DEREF)) {
- char_u *cp = xmemrchr(lv.ll_name, ':', lv.ll_name_len);
-
- if (cp != NULL && cp < end) {
- EMSG2(_("E884: Function name cannot contain a colon: %s"), start);
- goto theend;
- }
- }
-
- name = xmalloc(len + lead + 1);
- if (lead > 0){
- name[0] = K_SPECIAL;
- name[1] = KS_EXTRA;
- name[2] = (int)KE_SNR;
- if (sid_buf_len > 0) { // If it's "<SID>"
- memcpy(name + 3, sid_buf, sid_buf_len);
- }
- }
- memmove(name + lead, lv.ll_name, len);
- name[lead + len] = NUL;
- *pp = (char_u *)end;
-
-theend:
- clear_lval(&lv);
- return name;
-}
-
-/*
- * Return 5 if "p" starts with "<SID>" or "<SNR>" (ignoring case).
- * Return 2 if "p" starts with "s:".
- * Return 0 otherwise.
- */
-static int eval_fname_script(const char *const p)
-{
- // Use mb_strnicmp() because in Turkish comparing the "I" may not work with
- // the standard library function.
- if (p[0] == '<'
- && (mb_strnicmp((char_u *)p + 1, (char_u *)"SID>", 4) == 0
- || mb_strnicmp((char_u *)p + 1, (char_u *)"SNR>", 4) == 0)) {
- return 5;
- }
- if (p[0] == 's' && p[1] == ':') {
- return 2;
- }
- return 0;
-}
-
-/// Check whether function name starts with <SID> or s:
-///
-/// @warning Only works for names previously checked by eval_fname_script(), if
-/// it returned non-zero.
-///
-/// @param[in] name Name to check.
-///
-/// @return true if it starts with <SID> or s:, false otherwise.
-static inline bool eval_fname_sid(const char *const name)
- FUNC_ATTR_PURE FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_WARN_UNUSED_RESULT
- FUNC_ATTR_NONNULL_ALL
-{
- return *name == 's' || TOUPPER_ASC(name[2]) == 'I';
-}
-
-/// List the head of the function: "name(arg1, arg2)".
-///
-/// @param[in] fp Function pointer.
-/// @param[in] indent Indent line.
-/// @param[in] force Include bang "!" (i.e.: "function!").
-static void list_func_head(ufunc_T *fp, int indent, bool force)
-{
- msg_start();
- if (indent)
- MSG_PUTS(" ");
- MSG_PUTS(force ? "function! " : "function ");
- if (fp->uf_name[0] == K_SPECIAL) {
- MSG_PUTS_ATTR("<SNR>", HL_ATTR(HLF_8));
- msg_puts((const char *)fp->uf_name + 3);
- } else {
- msg_puts((const char *)fp->uf_name);
- }
- msg_putchar('(');
- int j;
- for (j = 0; j < fp->uf_args.ga_len; j++) {
- if (j) {
- msg_puts(", ");
- }
- msg_puts((const char *)FUNCARG(fp, j));
- }
- if (fp->uf_varargs) {
- if (j) {
- msg_puts(", ");
- }
- msg_puts("...");
- }
- msg_putchar(')');
- if (fp->uf_flags & FC_ABORT) {
- msg_puts(" abort");
- }
- if (fp->uf_flags & FC_RANGE) {
- msg_puts(" range");
- }
- if (fp->uf_flags & FC_DICT) {
- msg_puts(" dict");
- }
- if (fp->uf_flags & FC_CLOSURE) {
- msg_puts(" closure");
- }
- msg_clr_eos();
- if (p_verbose > 0) {
- last_set_msg(fp->uf_script_ctx);
- }
-}
-
-/// Find a function by name, return pointer to it in ufuncs.
-/// @return NULL for unknown function.
-static ufunc_T *find_func(const char_u *name)
-{
- hashitem_T *hi;
-
- hi = hash_find(&func_hashtab, name);
- if (!HASHITEM_EMPTY(hi))
- return HI2UF(hi);
- return NULL;
-}
-
-#if defined(EXITFREE)
-void free_all_functions(void)
-{
- hashitem_T *hi;
- ufunc_T *fp;
- uint64_t skipped = 0;
- uint64_t todo = 1;
- uint64_t used;
-
- // Clean up the call stack.
- while (current_funccal != NULL) {
- tv_clear(current_funccal->rettv);
- cleanup_function_call(current_funccal);
- }
-
- // First clear what the functions contain. Since this may lower the
- // reference count of a function, it may also free a function and change
- // the hash table. Restart if that happens.
- while (todo > 0) {
- todo = func_hashtab.ht_used;
- for (hi = func_hashtab.ht_array; todo > 0; hi++) {
- if (!HASHITEM_EMPTY(hi)) {
- // Only free functions that are not refcounted, those are
- // supposed to be freed when no longer referenced.
- fp = HI2UF(hi);
- if (func_name_refcount(fp->uf_name)) {
- skipped++;
- } else {
- used = func_hashtab.ht_used;
- func_clear(fp, true);
- if (used != func_hashtab.ht_used) {
- skipped = 0;
- break;
- }
- }
- todo--;
- }
- }
- }
-
- // Now actually free the functions. Need to start all over every time,
- // because func_free() may change the hash table.
- skipped = 0;
- while (func_hashtab.ht_used > skipped) {
- todo = func_hashtab.ht_used;
- for (hi = func_hashtab.ht_array; todo > 0; hi++) {
- if (!HASHITEM_EMPTY(hi)) {
- todo--;
- // Only free functions that are not refcounted, those are
- // supposed to be freed when no longer referenced.
- fp = HI2UF(hi);
- if (func_name_refcount(fp->uf_name)) {
- skipped++;
- } else {
- func_free(fp);
- skipped = 0;
- break;
- }
- }
- }
- }
- if (skipped == 0) {
- hash_clear(&func_hashtab);
- }
-}
-
-#endif
-
-bool translated_function_exists(const char *name)
-{
- if (builtin_function(name, -1)) {
- return find_internal_func((char *)name) != NULL;
- }
- return find_func((const char_u *)name) != NULL;
-}
-
-/// Check whether function with the given name exists
-///
-/// @param[in] name Function name.
-/// @param[in] no_deref Whether to dereference a Funcref.
-///
-/// @return True if it exists, false otherwise.
-static bool function_exists(const char *const name, bool no_deref)
-{
- const char_u *nm = (const char_u *)name;
- bool n = false;
- int flag = TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD;
-
- if (no_deref) {
- flag |= TFN_NO_DEREF;
- }
- char *const p = (char *)trans_function_name((char_u **)&nm, false, flag, NULL,
- NULL);
- nm = skipwhite(nm);
-
- /* Only accept "funcname", "funcname ", "funcname (..." and
- * "funcname(...", not "funcname!...". */
- if (p != NULL && (*nm == NUL || *nm == '(')) {
- n = translated_function_exists(p);
- }
- xfree(p);
- return n;
-}
-
-/// Checks if a builtin function with the given name exists.
-///
-/// @param[in] name name of the builtin function to check.
-/// @param[in] len length of "name", or -1 for NUL terminated.
-///
-/// @return true if "name" looks like a builtin function name: starts with a
-/// lower case letter and doesn't contain AUTOLOAD_CHAR.
-static bool builtin_function(const char *name, int len)
-{
- if (!ASCII_ISLOWER(name[0])) {
- return false;
- }
-
- const char *p = (len == -1
- ? strchr(name, AUTOLOAD_CHAR)
- : memchr(name, AUTOLOAD_CHAR, (size_t)len));
-
- return p == NULL;
-}
-
-/*
- * Start profiling function "fp".
- */
-static void func_do_profile(ufunc_T *fp)
+/// Start profiling function "fp".
+void func_do_profile(ufunc_T *fp)
{
int len = fp->uf_lines.ga_len;
@@ -22606,8 +9417,9 @@ void func_dump_profile(FILE *fd)
int st_len = 0;
todo = (int)func_hashtab.ht_used;
- if (todo == 0)
- return; /* nothing to dump */
+ if (todo == 0) {
+ return; // nothing to dump
+ }
sorttab = xmalloc(sizeof(ufunc_T *) * todo);
@@ -22676,7 +9488,7 @@ prof_sort_list(
ufunc_T **sorttab,
int st_len,
char *title,
- int prefer_self /* when equal print only self time */
+ int prefer_self // when equal print only self time
)
{
int i;
@@ -22704,8 +9516,8 @@ static void prof_func_line(
int count,
proftime_T *total,
proftime_T *self,
- int prefer_self /* when equal print only self time */
- )
+ int prefer_self // when equal print only self time
+)
{
if (count > 0) {
fprintf(fd, "%5d ", count);
@@ -22741,6 +9553,33 @@ static int prof_self_cmp(const void *s1, const void *s2)
return profile_cmp(p1->uf_tm_self, p2->uf_tm_self);
}
+/// Return the autoload script name for a function or variable name
+/// Caller must make sure that "name" contains AUTOLOAD_CHAR.
+///
+/// @param[in] name Variable/function name.
+/// @param[in] name_len Name length.
+///
+/// @return [allocated] autoload script name.
+char *autoload_name(const char *const name, const size_t name_len)
+ FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ // Get the script file name: replace '#' with '/', append ".vim".
+ char *const scriptname = xmalloc(name_len + sizeof("autoload/.vim"));
+ memcpy(scriptname, "autoload/", sizeof("autoload/") - 1);
+ memcpy(scriptname + sizeof("autoload/") - 1, name, name_len);
+ size_t auchar_idx = 0;
+ for (size_t i = sizeof("autoload/") - 1;
+ i - sizeof("autoload/") + 1 < name_len;
+ i++) {
+ if (scriptname[i] == AUTOLOAD_CHAR) {
+ scriptname[i] = '/';
+ auchar_idx = i;
+ }
+ }
+ memcpy(scriptname + auchar_idx, ".vim", sizeof(".vim"));
+
+ return scriptname;
+}
/// If name has a package name try autoloading the script for it
///
@@ -22749,8 +9588,8 @@ static int prof_self_cmp(const void *s1, const void *s2)
/// @param[in] reload If true, load script again when already loaded.
///
/// @return true if a package was loaded.
-static bool script_autoload(const char *const name, const size_t name_len,
- const bool reload)
+bool script_autoload(const char *const name, const size_t name_len,
+ const bool reload)
{
// If there is no '#' after name[0] there is no package name.
const char *p = memchr(name, AUTOLOAD_CHAR, name_len);
@@ -22789,1027 +9628,6 @@ static bool script_autoload(const char *const name, const size_t name_len,
return ret;
}
-/// Return the autoload script name for a function or variable name
-/// Caller must make sure that "name" contains AUTOLOAD_CHAR.
-///
-/// @param[in] name Variable/function name.
-/// @param[in] name_len Name length.
-///
-/// @return [allocated] autoload script name.
-static char *autoload_name(const char *const name, const size_t name_len)
- FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT
-{
- // Get the script file name: replace '#' with '/', append ".vim".
- char *const scriptname = xmalloc(name_len + sizeof("autoload/.vim"));
- memcpy(scriptname, "autoload/", sizeof("autoload/") - 1);
- memcpy(scriptname + sizeof("autoload/") - 1, name, name_len);
- size_t auchar_idx = 0;
- for (size_t i = sizeof("autoload/") - 1;
- i - sizeof("autoload/") + 1 < name_len;
- i++) {
- if (scriptname[i] == AUTOLOAD_CHAR) {
- scriptname[i] = '/';
- auchar_idx = i;
- }
- }
- memcpy(scriptname + auchar_idx, ".vim", sizeof(".vim"));
-
- return scriptname;
-}
-
-
-/*
- * 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;
- static hashitem_T *hi;
- ufunc_T *fp;
-
- if (idx == 0) {
- done = 0;
- hi = func_hashtab.ht_array;
- }
- assert(hi);
- if (done < func_hashtab.ht_used) {
- if (done++ > 0)
- ++hi;
- while (HASHITEM_EMPTY(hi))
- ++hi;
- fp = HI2UF(hi);
-
- if ((fp->uf_flags & FC_DICT)
- || STRNCMP(fp->uf_name, "<lambda>", 8) == 0) {
- return (char_u *)""; // don't show dict and lambda functions
- }
-
- if (STRLEN(fp->uf_name) + 4 >= IOSIZE) {
- return fp->uf_name; // Prevent overflow.
- }
-
- cat_func_name(IObuff, fp);
- if (xp->xp_context != EXPAND_USER_FUNC) {
- STRCAT(IObuff, "(");
- if (!fp->uf_varargs && GA_EMPTY(&fp->uf_args))
- STRCAT(IObuff, ")");
- }
- return IObuff;
- }
- 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.
- */
-static void cat_func_name(char_u *buf, ufunc_T *fp)
-{
- if (fp->uf_name[0] == K_SPECIAL) {
- STRCPY(buf, "<SNR>");
- STRCAT(buf, fp->uf_name + 3);
- } else
- STRCPY(buf, fp->uf_name);
-}
-
-/// There are two kinds of function names:
-/// 1. ordinary names, function defined with :function
-/// 2. numbered functions and lambdas
-/// For the first we only count the name stored in func_hashtab as a reference,
-/// using function() does not count as a reference, because the function is
-/// looked up by name.
-static bool func_name_refcount(char_u *name)
-{
- return isdigit(*name) || *name == '<';
-}
-
-/// ":delfunction {name}"
-void ex_delfunction(exarg_T *eap)
-{
- ufunc_T *fp = NULL;
- char_u *p;
- char_u *name;
- funcdict_T fudi;
-
- p = eap->arg;
- name = trans_function_name(&p, eap->skip, 0, &fudi, NULL);
- xfree(fudi.fd_newkey);
- if (name == NULL) {
- if (fudi.fd_dict != NULL && !eap->skip)
- EMSG(_(e_funcref));
- return;
- }
- if (!ends_excmd(*skipwhite(p))) {
- xfree(name);
- EMSG(_(e_trailing));
- return;
- }
- eap->nextcmd = check_nextcmd(p);
- if (eap->nextcmd != NULL)
- *p = NUL;
-
- if (!eap->skip)
- fp = find_func(name);
- xfree(name);
-
- if (!eap->skip) {
- if (fp == NULL) {
- if (!eap->forceit) {
- EMSG2(_(e_nofunc), eap->arg);
- }
- return;
- }
- if (fp->uf_calls > 0) {
- EMSG2(_("E131: Cannot delete function %s: It is in use"), eap->arg);
- return;
- }
- // check `uf_refcount > 2` because deleting a function should also reduce
- // the reference count, and 1 is the initial refcount.
- if (fp->uf_refcount > 2) {
- EMSG2(_("Cannot delete function %s: It is being used internally"),
- eap->arg);
- return;
- }
-
- if (fudi.fd_dict != NULL) {
- // Delete the dict item that refers to the function, it will
- // invoke func_unref() and possibly delete the function.
- tv_dict_item_remove(fudi.fd_dict, fudi.fd_di);
- } else {
- // A normal function (not a numbered function or lambda) has a
- // refcount of 1 for the entry in the hashtable. When deleting
- // it and the refcount is more than one, it should be kept.
- // A numbered function or lambda should be kept if the refcount is
- // one or more.
- if (fp->uf_refcount > (func_name_refcount(fp->uf_name) ? 0 : 1)) {
- // Function is still referenced somewhere. Don't free it but
- // do remove it from the hashtable.
- if (func_remove(fp)) {
- fp->uf_refcount--;
- }
- fp->uf_flags |= FC_DELETED;
- } else {
- func_clear_free(fp, false);
- }
- }
- }
-}
-
-/// Remove the function from the function hashtable. If the function was
-/// deleted while it still has references this was already done.
-///
-/// @return true if the entry was deleted, false if it wasn't found.
-static bool func_remove(ufunc_T *fp)
-{
- hashitem_T *hi = hash_find(&func_hashtab, UF2HIKEY(fp));
-
- if (!HASHITEM_EMPTY(hi)) {
- hash_remove(&func_hashtab, hi);
- return true;
- }
-
- return false;
-}
-
-static void func_clear_items(ufunc_T *fp)
-{
- ga_clear_strings(&(fp->uf_args));
- ga_clear_strings(&(fp->uf_lines));
-
- XFREE_CLEAR(fp->uf_tml_count);
- XFREE_CLEAR(fp->uf_tml_total);
- XFREE_CLEAR(fp->uf_tml_self);
-}
-
-/// 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.
-static void func_clear(ufunc_T *fp, bool force)
-{
- if (fp->uf_cleared) {
- return;
- }
- fp->uf_cleared = true;
-
- // clear this function
- func_clear_items(fp);
- funccal_unref(fp->uf_scoped, fp, 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.
-static void func_free(ufunc_T *fp)
-{
- // only remove it when not done already, otherwise we would remove a newer
- // version of the function
- if ((fp->uf_flags & (FC_DELETED | FC_REMOVED)) == 0) {
- func_remove(fp);
- }
- xfree(fp);
-}
-
-/// Free all things that a function contains and free the function itself.
-///
-/// param[in] force When true, we are exiting.
-static void func_clear_free(ufunc_T *fp, bool force)
-{
- func_clear(fp, force);
- func_free(fp);
-}
-
-/*
- * Unreference a Function: decrement the reference count and free it when it
- * becomes zero.
- */
-void func_unref(char_u *name)
-{
- ufunc_T *fp = NULL;
-
- if (name == NULL || !func_name_refcount(name)) {
- return;
- }
-
- fp = find_func(name);
- if (fp == NULL && isdigit(*name)) {
-#ifdef EXITFREE
- if (!entered_free_all_mem) {
- internal_error("func_unref()");
- abort();
- }
-#else
- internal_error("func_unref()");
- abort();
-#endif
- }
- func_ptr_unref(fp);
-}
-
-/// Unreference a Function: decrement the reference count and free it when it
-/// becomes zero.
-/// Unreference user function, freeing it if needed
-///
-/// Decrements the reference count and frees when it becomes zero.
-///
-/// @param fp Function to unreference.
-void func_ptr_unref(ufunc_T *fp)
-{
- if (fp != NULL && --fp->uf_refcount <= 0) {
- // Only delete it when it's not being used. Otherwise it's done
- // when "uf_calls" becomes zero.
- if (fp->uf_calls == 0) {
- func_clear_free(fp, false);
- }
- }
-}
-
-/// Count a reference to a Function.
-void func_ref(char_u *name)
-{
- ufunc_T *fp;
-
- if (name == NULL || !func_name_refcount(name)) {
- return;
- }
- fp = find_func(name);
- if (fp != NULL) {
- (fp->uf_refcount)++;
- } else if (isdigit(*name)) {
- // Only give an error for a numbered function.
- // Fail silently, when named or lambda function isn't found.
- internal_error("func_ref()");
- }
-}
-
-/// Count a reference to a Function.
-void func_ptr_ref(ufunc_T *fp)
-{
- if (fp != NULL) {
- (fp->uf_refcount)++;
- }
-}
-
-/// Check whether funccall is still referenced outside
-///
-/// It is supposed to be referenced if either it is referenced itself or if l:,
-/// a: or a:000 are referenced as all these are statically allocated within
-/// funccall structure.
-static inline bool fc_referenced(const funccall_T *const fc)
- FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
- FUNC_ATTR_NONNULL_ALL
-{
- return ((fc->l_varlist.lv_refcount // NOLINT(runtime/deprecated)
- != DO_NOT_FREE_CNT)
- || fc->l_vars.dv_refcount != DO_NOT_FREE_CNT
- || fc->l_avars.dv_refcount != DO_NOT_FREE_CNT
- || fc->fc_refcount > 0);
-}
-
-/// 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.
-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)
-{
- char_u *save_sourcing_name;
- linenr_T save_sourcing_lnum;
- bool using_sandbox = false;
- funccall_T *fc;
- int save_did_emsg;
- static int depth = 0;
- dictitem_T *v;
- int fixvar_idx = 0; /* index in fixvar[] */
- int ai;
- bool islambda = false;
- char_u numbuf[NUMBUFLEN];
- char_u *name;
- proftime_T wait_start;
- proftime_T call_start;
- int started_profiling = false;
- bool did_save_redo = false;
- save_redo_T save_redo;
-
- /* If depth of calling is getting too high, don't execute the function */
- if (depth >= p_mfd) {
- EMSG(_("E132: Function call depth is higher than 'maxfuncdepth'"));
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = -1;
- return;
- }
- ++depth;
- // Save search patterns and redo buffer.
- save_search_patterns();
- if (!ins_compl_active()) {
- saveRedobuff(&save_redo);
- did_save_redo = true;
- }
- ++fp->uf_calls;
- // check for CTRL-C hit
- line_breakcheck();
- // prepare the funccall_T structure
- fc = xmalloc(sizeof(funccall_T));
- fc->caller = current_funccal;
- current_funccal = fc;
- fc->func = fp;
- fc->rettv = rettv;
- rettv->vval.v_number = 0;
- fc->linenr = 0;
- fc->returned = FALSE;
- fc->level = ex_nesting_level;
- /* Check if this function has a breakpoint. */
- fc->breakpoint = dbg_find_breakpoint(FALSE, fp->uf_name, (linenr_T)0);
- fc->dbg_tick = debug_tick;
-
- // Set up fields for closure.
- fc->fc_refcount = 0;
- fc->fc_copyID = 0;
- ga_init(&fc->fc_funcs, sizeof(ufunc_T *), 1);
- func_ptr_ref(fp);
-
- if (STRNCMP(fp->uf_name, "<lambda>", 8) == 0) {
- islambda = true;
- }
-
- // Note about using fc->fixvar[]: This is an array of FIXVAR_CNT variables
- // with names up to VAR_SHORT_LEN long. This avoids having to alloc/free
- // each argument variable and saves a lot of time.
- //
- // Init l: variables.
- init_var_dict(&fc->l_vars, &fc->l_vars_var, VAR_DEF_SCOPE);
- if (selfdict != NULL) {
- // Set l:self to "selfdict". Use "name" to avoid a warning from
- // some compiler that checks the destination size.
- v = (dictitem_T *)&fc->fixvar[fixvar_idx++];
-#ifndef __clang_analyzer__
- name = v->di_key;
- STRCPY(name, "self");
-#endif
- v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
- tv_dict_add(&fc->l_vars, v);
- v->di_tv.v_type = VAR_DICT;
- v->di_tv.v_lock = 0;
- v->di_tv.vval.v_dict = selfdict;
- ++selfdict->dv_refcount;
- }
-
- /*
- * Init a: variables.
- * Set a:0 to "argcount".
- * Set a:000 to a list with room for the "..." arguments.
- */
- init_var_dict(&fc->l_avars, &fc->l_avars_var, VAR_SCOPE);
- add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++], "0",
- (varnumber_T)(argcount - fp->uf_args.ga_len));
- fc->l_avars.dv_lock = VAR_FIXED;
- // Use "name" to avoid a warning from some compiler that checks the
- // destination size.
- v = (dictitem_T *)&fc->fixvar[fixvar_idx++];
-#ifndef __clang_analyzer__
- name = v->di_key;
- STRCPY(name, "000");
-#endif
- v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
- tv_dict_add(&fc->l_avars, v);
- v->di_tv.v_type = VAR_LIST;
- v->di_tv.v_lock = VAR_FIXED;
- v->di_tv.vval.v_list = &fc->l_varlist;
- tv_list_init_static(&fc->l_varlist);
- tv_list_set_lock(&fc->l_varlist, VAR_FIXED);
-
- // Set a:firstline to "firstline" and a:lastline to "lastline".
- // Set a:name to named arguments.
- // Set a:N to the "..." arguments.
- add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++],
- "firstline", (varnumber_T)firstline);
- add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++],
- "lastline", (varnumber_T)lastline);
- for (int i = 0; i < argcount; i++) {
- bool addlocal = false;
-
- ai = i - fp->uf_args.ga_len;
- if (ai < 0) {
- // named argument a:name
- name = FUNCARG(fp, i);
- if (islambda) {
- addlocal = true;
- }
- } else {
- // "..." argument a:1, a:2, etc.
- snprintf((char *)numbuf, sizeof(numbuf), "%d", ai + 1);
- name = numbuf;
- }
- if (fixvar_idx < FIXVAR_CNT && STRLEN(name) <= VAR_SHORT_LEN) {
- v = (dictitem_T *)&fc->fixvar[fixvar_idx++];
- v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
- } else {
- v = xmalloc(sizeof(dictitem_T) + STRLEN(name));
- v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX | DI_FLAGS_ALLOC;
- }
- STRCPY(v->di_key, name);
-
- // Note: the values are copied directly to avoid alloc/free.
- // "argvars" must have VAR_FIXED for v_lock.
- v->di_tv = argvars[i];
- v->di_tv.v_lock = VAR_FIXED;
-
- if (addlocal) {
- // Named arguments can be accessed without the "a:" prefix in lambda
- // expressions. Add to the l: dict.
- tv_copy(&v->di_tv, &v->di_tv);
- tv_dict_add(&fc->l_vars, v);
- } else {
- tv_dict_add(&fc->l_avars, v);
- }
-
- if (ai >= 0 && ai < MAX_FUNC_ARGS) {
- tv_list_append(&fc->l_varlist, &fc->l_listitems[ai]);
- *TV_LIST_ITEM_TV(&fc->l_listitems[ai]) = argvars[i];
- TV_LIST_ITEM_TV(&fc->l_listitems[ai])->v_lock = VAR_FIXED;
- }
- }
-
- /* Don't redraw while executing the function. */
- ++RedrawingDisabled;
- save_sourcing_name = sourcing_name;
- save_sourcing_lnum = sourcing_lnum;
- sourcing_lnum = 1;
-
- if (fp->uf_flags & FC_SANDBOX) {
- using_sandbox = true;
- sandbox++;
- }
-
- // need space for new sourcing_name:
- // * save_sourcing_name
- // * "["number"].." or "function "
- // * "<SNR>" + fp->uf_name - 3
- // * terminating NUL
- size_t len = (save_sourcing_name == NULL ? 0 : STRLEN(save_sourcing_name))
- + STRLEN(fp->uf_name) + 27;
- sourcing_name = xmalloc(len);
- {
- if (save_sourcing_name != NULL
- && STRNCMP(save_sourcing_name, "function ", 9) == 0) {
- vim_snprintf((char *)sourcing_name,
- len,
- "%s[%" PRId64 "]..",
- save_sourcing_name,
- (int64_t)save_sourcing_lnum);
- } else {
- STRCPY(sourcing_name, "function ");
- }
- cat_func_name(sourcing_name + STRLEN(sourcing_name), fp);
-
- if (p_verbose >= 12) {
- ++no_wait_return;
- verbose_enter_scroll();
-
- smsg(_("calling %s"), sourcing_name);
- if (p_verbose >= 14) {
- msg_puts("(");
- for (int i = 0; i < argcount; i++) {
- if (i > 0) {
- msg_puts(", ");
- }
- if (argvars[i].v_type == VAR_NUMBER) {
- msg_outnum((long)argvars[i].vval.v_number);
- } else {
- // Do not want errors such as E724 here.
- emsg_off++;
- char *tofree = encode_tv2string(&argvars[i], NULL);
- emsg_off--;
- if (tofree != NULL) {
- char *s = tofree;
- char buf[MSG_BUF_LEN];
- if (vim_strsize((char_u *)s) > MSG_BUF_CLEN) {
- trunc_string((char_u *)s, (char_u *)buf, MSG_BUF_CLEN,
- sizeof(buf));
- s = buf;
- }
- msg_puts(s);
- xfree(tofree);
- }
- }
- }
- msg_puts(")");
- }
- msg_puts("\n"); // don't overwrite this either
-
- verbose_leave_scroll();
- --no_wait_return;
- }
- }
-
- const bool do_profiling_yes = do_profiling == PROF_YES;
-
- bool func_not_yet_profiling_but_should =
- do_profiling_yes
- && !fp->uf_profiling && has_profiling(false, fp->uf_name, NULL);
-
- if (func_not_yet_profiling_but_should) {
- started_profiling = true;
- func_do_profile(fp);
- }
-
- bool func_or_func_caller_profiling =
- do_profiling_yes
- && (fp->uf_profiling
- || (fc->caller != NULL && fc->caller->func->uf_profiling));
-
- if (func_or_func_caller_profiling) {
- ++fp->uf_tm_count;
- call_start = profile_start();
- fp->uf_tm_children = profile_zero();
- }
-
- if (do_profiling_yes) {
- script_prof_save(&wait_start);
- }
-
- const sctx_T save_current_sctx = current_sctx;
- current_sctx = fp->uf_script_ctx;
- save_did_emsg = did_emsg;
- did_emsg = FALSE;
-
- /* call do_cmdline() to execute the lines */
- do_cmdline(NULL, get_func_line, (void *)fc,
- DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT);
-
- --RedrawingDisabled;
-
- // when the function was aborted because of an error, return -1
- if ((did_emsg
- && (fp->uf_flags & FC_ABORT)) || rettv->v_type == VAR_UNKNOWN) {
- tv_clear(rettv);
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = -1;
- }
-
- if (func_or_func_caller_profiling) {
- call_start = profile_end(call_start);
- call_start = profile_sub_wait(wait_start, call_start); // -V614
- fp->uf_tm_total = profile_add(fp->uf_tm_total, call_start);
- fp->uf_tm_self = profile_self(fp->uf_tm_self, call_start,
- fp->uf_tm_children);
- if (fc->caller != NULL && fc->caller->func->uf_profiling) {
- fc->caller->func->uf_tm_children =
- profile_add(fc->caller->func->uf_tm_children, call_start);
- fc->caller->func->uf_tml_children =
- profile_add(fc->caller->func->uf_tml_children, call_start);
- }
- if (started_profiling) {
- // make a ":profdel func" stop profiling the function
- fp->uf_profiling = false;
- }
- }
-
- /* when being verbose, mention the return value */
- if (p_verbose >= 12) {
- ++no_wait_return;
- verbose_enter_scroll();
-
- if (aborting())
- smsg(_("%s aborted"), sourcing_name);
- else if (fc->rettv->v_type == VAR_NUMBER)
- smsg(_("%s returning #%" PRId64 ""),
- sourcing_name, (int64_t)fc->rettv->vval.v_number);
- else {
- char_u buf[MSG_BUF_LEN];
-
- // The value may be very long. Skip the middle part, so that we
- // have some idea how it starts and ends. smsg() would always
- // truncate it at the end. Don't want errors such as E724 here.
- emsg_off++;
- char_u *s = (char_u *) encode_tv2string(fc->rettv, NULL);
- char_u *tofree = s;
- emsg_off--;
- if (s != NULL) {
- if (vim_strsize(s) > MSG_BUF_CLEN) {
- trunc_string(s, buf, MSG_BUF_CLEN, MSG_BUF_LEN);
- s = buf;
- }
- smsg(_("%s returning %s"), sourcing_name, s);
- xfree(tofree);
- }
- }
- msg_puts("\n"); // don't overwrite this either
-
- verbose_leave_scroll();
- --no_wait_return;
- }
-
- xfree(sourcing_name);
- sourcing_name = save_sourcing_name;
- sourcing_lnum = save_sourcing_lnum;
- current_sctx = save_current_sctx;
- if (do_profiling_yes) {
- script_prof_restore(&wait_start);
- }
- if (using_sandbox) {
- sandbox--;
- }
-
- if (p_verbose >= 12 && sourcing_name != NULL) {
- ++no_wait_return;
- verbose_enter_scroll();
-
- smsg(_("continuing in %s"), sourcing_name);
- msg_puts("\n"); // don't overwrite this either
-
- verbose_leave_scroll();
- --no_wait_return;
- }
-
- did_emsg |= save_did_emsg;
- depth--;
-
- cleanup_function_call(fc);
-
- if (--fp->uf_calls <= 0 && fp->uf_refcount <= 0) {
- // Function was unreferenced while being used, free it now.
- func_clear_free(fp, false);
- }
- // restore search patterns and redo buffer
- if (did_save_redo) {
- restoreRedobuff(&save_redo);
- }
- restore_search_patterns();
-}
-
-/// Unreference "fc": decrement the reference count and free it when it
-/// becomes zero. "fp" is detached from "fc".
-///
-/// @param[in] force When true, we are exiting.
-static void funccal_unref(funccall_T *fc, ufunc_T *fp, bool force)
-{
- funccall_T **pfc;
- int i;
-
- if (fc == NULL) {
- return;
- }
-
- fc->fc_refcount--;
- if (force ? fc->fc_refcount <= 0 : !fc_referenced(fc)) {
- for (pfc = &previous_funccal; *pfc != NULL; pfc = &(*pfc)->caller) {
- if (fc == *pfc) {
- *pfc = fc->caller;
- free_funccal(fc, true);
- return;
- }
- }
- }
- for (i = 0; i < fc->fc_funcs.ga_len; i++) {
- if (((ufunc_T **)(fc->fc_funcs.ga_data))[i] == fp) {
- ((ufunc_T **)(fc->fc_funcs.ga_data))[i] = NULL;
- }
- }
-}
-
-/// @return true if items in "fc" do not have "copyID". That means they are not
-/// referenced from anywhere that is in use.
-static int can_free_funccal(funccall_T *fc, int copyID)
-{
- return fc->l_varlist.lv_copyID != copyID
- && fc->l_vars.dv_copyID != copyID
- && fc->l_avars.dv_copyID != copyID
- && fc->fc_copyID != copyID;
-}
-
-/*
- * Free "fc" and what it contains.
- */
-static void
-free_funccal(
- funccall_T *fc,
- int free_val /* a: vars were allocated */
-)
-{
- for (int i = 0; i < fc->fc_funcs.ga_len; i++) {
- ufunc_T *fp = ((ufunc_T **)(fc->fc_funcs.ga_data))[i];
-
- // When garbage collecting a funccall_T may be freed before the
- // function that references it, clear its uf_scoped field.
- // The function may have been redefined and point to another
- // funccal_T, don't clear it then.
- if (fp != NULL && fp->uf_scoped == fc) {
- fp->uf_scoped = NULL;
- }
- }
- ga_clear(&fc->fc_funcs);
-
- // The a: variables typevals may not have been allocated, only free the
- // allocated variables.
- vars_clear_ext(&fc->l_avars.dv_hashtab, free_val);
-
- // Free all l: variables.
- vars_clear(&fc->l_vars.dv_hashtab);
-
- // Free the a:000 variables if they were allocated.
- if (free_val) {
- TV_LIST_ITER(&fc->l_varlist, li, {
- tv_clear(TV_LIST_ITEM_TV(li));
- });
- }
-
- func_ptr_unref(fc->func);
- xfree(fc);
-}
-
-/// Handle the last part of returning from a function: free the local hashtable.
-/// Unless it is still in use by a closure.
-static void cleanup_function_call(funccall_T *fc)
-{
- current_funccal = fc->caller;
-
- // If the a:000 list and the l: and a: dicts are not referenced and there
- // is no closure using it, we can free the funccall_T and what's in it.
- if (!fc_referenced(fc)) {
- free_funccal(fc, false);
- } else {
- // "fc" is still in use. This can happen when returning "a:000",
- // assigning "l:" to a global variable or defining a closure.
- // Link "fc" in the list for garbage collection later.
- fc->caller = previous_funccal;
- previous_funccal = fc;
-
- // Make a copy of the a: variables, since we didn't do that above.
- TV_DICT_ITER(&fc->l_avars, di, {
- tv_copy(&di->di_tv, &di->di_tv);
- });
-
- // Make a copy of the a:000 items, since we didn't do that above.
- TV_LIST_ITER(&fc->l_varlist, li, {
- tv_copy(TV_LIST_ITEM_TV(li), TV_LIST_ITEM_TV(li));
- });
- }
-}
-
-/*
- * 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__
- STRCPY(v->di_key, name);
-#endif
- v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
- tv_dict_add(dp, v);
- v->di_tv.v_type = VAR_NUMBER;
- v->di_tv.v_lock = VAR_FIXED;
- v->di_tv.vval.v_number = nr;
-}
-
-/*
- * ":return [expr]"
- */
-void ex_return(exarg_T *eap)
-{
- char_u *arg = eap->arg;
- typval_T rettv;
- int returning = FALSE;
-
- if (current_funccal == NULL) {
- EMSG(_("E133: :return not inside a function"));
- return;
- }
-
- if (eap->skip)
- ++emsg_skip;
-
- eap->nextcmd = NULL;
- if ((*arg != NUL && *arg != '|' && *arg != '\n')
- && eval0(arg, &rettv, &eap->nextcmd, !eap->skip) != FAIL) {
- if (!eap->skip) {
- returning = do_return(eap, false, true, &rettv);
- } else {
- tv_clear(&rettv);
- }
- } else if (!eap->skip) { // It's safer to return also on error.
- // In return statement, cause_abort should be force_abort.
- update_force_abort();
-
- // Return unless the expression evaluation has been cancelled due to an
- // aborting error, an interrupt, or an exception.
- if (!aborting()) {
- returning = do_return(eap, false, true, NULL);
- }
- }
-
- /* When skipping or the return gets pending, advance to the next command
- * in this line (!returning). Otherwise, ignore the rest of the line.
- * Following lines will be ignored by get_func_line(). */
- if (returning)
- eap->nextcmd = NULL;
- else if (eap->nextcmd == NULL) /* no argument */
- eap->nextcmd = check_nextcmd(arg);
-
- if (eap->skip)
- --emsg_skip;
-}
-
-/*
- * 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.
- */
-int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv)
-{
- int idx;
- cstack_T *const cstack = eap->cstack;
-
- if (reanimate)
- /* Undo the return. */
- current_funccal->returned = FALSE;
-
- /*
- * Cleanup (and inactivate) 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;
-
- if (!is_cmd && !reanimate)
- /* A pending return again gets pending. "rettv" points to an
- * allocated variable with the rettv of the original ":return"'s
- * argument if present or is NULL else. */
- cstack->cs_rettv[idx] = rettv;
- else {
- /* When undoing a return in order to make it pending, get the stored
- * return rettv. */
- if (reanimate) {
- assert(current_funccal->rettv);
- rettv = current_funccal->rettv;
- }
-
- if (rettv != NULL) {
- /* Store the value of the pending return. */
- cstack->cs_rettv[idx] = xcalloc(1, sizeof(typval_T));
- *(typval_T *)cstack->cs_rettv[idx] = *(typval_T *)rettv;
- } else
- cstack->cs_rettv[idx] = NULL;
-
- if (reanimate) {
- /* The pending return value could be overwritten by a ":return"
- * without argument in a finally clause; reset the default
- * return value. */
- current_funccal->rettv->v_type = VAR_NUMBER;
- current_funccal->rettv->vval.v_number = 0;
- }
- }
- report_make_pending(CSTP_RETURN, rettv);
- } else {
- current_funccal->returned = TRUE;
-
- /* If the return is carried out now, store the return value. For
- * a return immediately after reanimation, the value is already
- * there. */
- if (!reanimate && rettv != NULL) {
- tv_clear(current_funccal->rettv);
- *current_funccal->rettv = *(typval_T *)rettv;
- if (!is_cmd)
- xfree(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.
- */
-char_u *get_return_cmd(void *rettv)
-{
- char_u *s = NULL;
- char_u *tofree = NULL;
-
- if (rettv != NULL) {
- tofree = s = (char_u *) encode_tv2echo((typval_T *) rettv, NULL);
- }
- if (s == NULL) {
- s = (char_u *)"";
- }
-
- STRCPY(IObuff, ":return ");
- STRLCPY(IObuff + 8, s, IOSIZE - 8);
- if (STRLEN(s) + 8 >= IOSIZE)
- STRCPY(IObuff + IOSIZE - 4, "...");
- xfree(tofree);
- 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.
- */
-char_u *get_func_line(int c, void *cookie, int indent, bool do_concat)
-{
- funccall_T *fcp = (funccall_T *)cookie;
- ufunc_T *fp = fcp->func;
- char_u *retval;
- garray_T *gap; /* growarray with function lines */
-
- /* If breakpoints have been added/deleted need to check for it. */
- if (fcp->dbg_tick != debug_tick) {
- fcp->breakpoint = dbg_find_breakpoint(FALSE, fp->uf_name,
- sourcing_lnum);
- fcp->dbg_tick = debug_tick;
- }
- if (do_profiling == PROF_YES)
- func_line_end(cookie);
-
- gap = &fp->uf_lines;
- if (((fp->uf_flags & FC_ABORT) && did_emsg && !aborted_in_try())
- || fcp->returned)
- retval = NULL;
- else {
- /* Skip NULL lines (continuation lines). */
- while (fcp->linenr < gap->ga_len
- && ((char_u **)(gap->ga_data))[fcp->linenr] == NULL)
- ++fcp->linenr;
- if (fcp->linenr >= gap->ga_len)
- retval = NULL;
- else {
- retval = vim_strsave(((char_u **)(gap->ga_data))[fcp->linenr++]);
- sourcing_lnum = fcp->linenr;
- if (do_profiling == PROF_YES)
- func_line_start(cookie);
- }
- }
-
- /* Did we encounter a breakpoint? */
- if (fcp->breakpoint != 0 && fcp->breakpoint <= sourcing_lnum) {
- dbg_breakpoint(fp->uf_name, sourcing_lnum);
- /* Find next breakpoint. */
- fcp->breakpoint = dbg_find_breakpoint(FALSE, fp->uf_name,
- sourcing_lnum);
- fcp->dbg_tick = debug_tick;
- }
-
- return retval;
-}
-
/*
* Called when starting to read a function line.
* "sourcing_lnum" must be correct!
@@ -23824,10 +9642,11 @@ void func_line_start(void *cookie)
if (fp->uf_profiling && sourcing_lnum >= 1
&& sourcing_lnum <= fp->uf_lines.ga_len) {
fp->uf_tml_idx = sourcing_lnum - 1;
- /* Skip continuation lines. */
- while (fp->uf_tml_idx > 0 && FUNCLINE(fp, fp->uf_tml_idx) == NULL)
- --fp->uf_tml_idx;
- fp->uf_tml_execed = FALSE;
+ // Skip continuation lines.
+ while (fp->uf_tml_idx > 0 && FUNCLINE(fp, fp->uf_tml_idx) == NULL) {
+ fp->uf_tml_idx--;
+ }
+ fp->uf_tml_execed = false;
fp->uf_tml_start = profile_start();
fp->uf_tml_children = profile_zero();
fp->uf_tml_wait = profile_get_wait();
@@ -23869,28 +9688,6 @@ void func_line_end(void *cookie)
}
}
-/*
- * 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;
-
- /* Ignore the "abort" flag if the abortion behavior has been changed due to
- * an error inside a try conditional. */
- return ((fcp->func->uf_flags & FC_ABORT) && did_emsg && !aborted_in_try())
- || fcp->returned;
-}
-
-/*
- * 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;
-}
-
static var_flavour_T var_flavour(char_u *varname)
{
char_u *p = varname;
@@ -23906,72 +9703,6 @@ static var_flavour_T var_flavour(char_u *varname)
}
}
-/// Search hashitem in parent scope.
-hashitem_T *find_hi_in_scoped_ht(const char *name, hashtab_T **pht)
-{
- if (current_funccal == NULL || current_funccal->func->uf_scoped == NULL) {
- return NULL;
- }
-
- funccall_T *old_current_funccal = current_funccal;
- hashitem_T *hi = NULL;
- const size_t namelen = strlen(name);
- const char *varname;
-
- // Search in parent scope which is possible to reference from lambda
- current_funccal = current_funccal->func->uf_scoped;
- while (current_funccal != NULL) {
- hashtab_T *ht = find_var_ht(name, namelen, &varname);
- if (ht != NULL && *varname != NUL) {
- hi = hash_find_len(ht, varname, namelen - (varname - name));
- if (!HASHITEM_EMPTY(hi)) {
- *pht = ht;
- break;
- }
- }
- if (current_funccal == current_funccal->func->uf_scoped) {
- break;
- }
- current_funccal = current_funccal->func->uf_scoped;
- }
- current_funccal = old_current_funccal;
-
- return hi;
-}
-
-/// Search variable in parent scope.
-dictitem_T *find_var_in_scoped_ht(const char *name, const size_t namelen,
- int no_autoload)
-{
- if (current_funccal == NULL || current_funccal->func->uf_scoped == NULL) {
- return NULL;
- }
-
- dictitem_T *v = NULL;
- funccall_T *old_current_funccal = current_funccal;
- const char *varname;
-
- // Search in parent scope which is possible to reference from lambda
- current_funccal = current_funccal->func->uf_scoped;
- while (current_funccal) {
- hashtab_T *ht = find_var_ht(name, namelen, &varname);
- if (ht != NULL && *varname != NUL) {
- v = find_var_in_ht(ht, *name, varname,
- namelen - (size_t)(varname - name), no_autoload);
- if (v != NULL) {
- break;
- }
- }
- if (current_funccal == current_funccal->func->uf_scoped) {
- break;
- }
- current_funccal = current_funccal->func->uf_scoped;
- }
- current_funccal = old_current_funccal;
-
- return v;
-}
-
/// Iterate over global variables
///
/// @warning No modifications to global variable dictionary must be performed
@@ -24016,10 +9747,9 @@ const void *var_shada_iter(const void *const iter, const char **const name,
void var_set_global(const char *const name, typval_T vartv)
{
- funccall_T *const saved_current_funccal = current_funccal;
- current_funccal = NULL;
+ funccall_T *const saved_funccal = (funccall_T *)save_funccal();
set_var(name, strlen(name), &vartv, false);
- current_funccal = saved_current_funccal;
+ restore_funccal(saved_funccal);
}
int store_session_globals(FILE *fd)
@@ -24138,14 +9868,14 @@ modify_fname(
int has_fullname = 0;
repeat:
- /* ":p" - full path/file_name */
+ // ":p" - full path/file_name
if (src[*usedlen] == ':' && src[*usedlen + 1] == 'p') {
has_fullname = 1;
valid |= VALID_PATH;
*usedlen += 2;
- /* Expand "~/path" for all systems and "~user/path" for Unix */
+ // Expand "~/path" for all systems and "~user/path" for Unix
if ((*fnamep)[0] == '~'
#if !defined(UNIX)
&& ((*fnamep)[1] == '/'
@@ -24157,7 +9887,7 @@ repeat:
&& !(tilde_file && (*fnamep)[1] == NUL)
) {
*fnamep = expand_env_save(*fnamep);
- xfree(*bufp); /* free any allocated file name */
+ xfree(*bufp); // free any allocated file name
*bufp = *fnamep;
if (*fnamep == NULL)
return -1;
@@ -24175,20 +9905,20 @@ repeat:
}
}
- /* FullName_save() is slow, don't use it when not needed. */
+ // FullName_save() is slow, don't use it when not needed.
if (*p != NUL || !vim_isAbsName(*fnamep)) {
- *fnamep = (char_u *)FullName_save((char *)*fnamep, *p != NUL);
- xfree(*bufp); /* free any allocated file name */
+ *fnamep = (char_u *)FullName_save((char *)(*fnamep), *p != NUL);
+ xfree(*bufp); // free any allocated file name
*bufp = *fnamep;
if (*fnamep == NULL)
return -1;
}
- /* Append a path separator to a directory. */
+ // Append a path separator to a directory.
if (os_isdir(*fnamep)) {
- /* Make room for one or two extra characters. */
+ // Make room for one or two extra characters.
*fnamep = vim_strnsave(*fnamep, STRLEN(*fnamep) + 2);
- xfree(*bufp); /* free any allocated file name */
+ xfree(*bufp); // free any allocated file name
*bufp = *fnamep;
if (*fnamep == NULL)
return -1;
@@ -24196,9 +9926,9 @@ repeat:
}
}
- /* ":." - path relative to the current directory */
- /* ":~" - path relative to the home directory */
- /* ":8" - shortname path - postponed till after */
+ // ":." - path relative to the current directory
+ // ":~" - path relative to the home directory
+ // ":8" - shortname path - postponed till after
while (src[*usedlen] == ':'
&& ((c = src[*usedlen + 1]) == '.' || c == '~' || c == '8')) {
*usedlen += 2;
@@ -24206,7 +9936,7 @@ repeat:
continue;
}
pbuf = NULL;
- /* Need full path first (use expand_env() to remove a "~/") */
+ // Need full path first (use expand_env() to remove a "~/")
if (!has_fullname) {
if (c == '.' && **fnamep == '~')
p = pbuf = expand_env_save(*fnamep);
@@ -24224,14 +9954,14 @@ repeat:
if (s != NULL) {
*fnamep = s;
if (pbuf != NULL) {
- xfree(*bufp); /* free any allocated file name */
+ xfree(*bufp); // free any allocated file name
*bufp = pbuf;
pbuf = NULL;
}
}
} else {
- home_replace(NULL, p, dirname, MAXPATHL, TRUE);
- /* Only replace it when it starts with '~' */
+ home_replace(NULL, p, dirname, MAXPATHL, true);
+ // Only replace it when it starts with '~'
if (*dirname == '~') {
s = vim_strsave(dirname);
*fnamep = s;
@@ -24246,8 +9976,8 @@ repeat:
tail = path_tail(*fnamep);
*fnamelen = STRLEN(*fnamep);
- /* ":h" - head, remove "/file_name", can be repeated */
- /* Don't remove the first "/" or "c:\" */
+ // ":h" - head, remove "/file_name", can be repeated
+ // Don't remove the first "/" or "c:\"
while (src[*usedlen] == ':' && src[*usedlen + 1] == 'h') {
valid |= VALID_HEAD;
*usedlen += 2;
@@ -24257,7 +9987,7 @@ repeat:
}
*fnamelen = (size_t)(tail - *fnamep);
if (*fnamelen == 0) {
- /* Result is empty. Turn it into "." to make ":cd %:h" work. */
+ // Result is empty. Turn it into "." to make ":cd %:h" work.
xfree(*bufp);
*bufp = *fnamep = tail = vim_strsave((char_u *)".");
*fnamelen = 1;
@@ -24268,21 +9998,21 @@ repeat:
}
}
- /* ":8" - shortname */
+ // ":8" - shortname
if (src[*usedlen] == ':' && src[*usedlen + 1] == '8') {
*usedlen += 2;
}
- /* ":t" - tail, just the basename */
+ // ":t" - tail, just the basename
if (src[*usedlen] == ':' && src[*usedlen + 1] == 't') {
*usedlen += 2;
*fnamelen -= (size_t)(tail - *fnamep);
*fnamep = tail;
}
- /* ":e" - extension, can be repeated */
- /* ":r" - root, without extension, can be repeated */
+ // ":e" - extension, can be repeated
+ // ":r" - root, without extension, can be repeated
while (src[*usedlen] == ':'
&& (src[*usedlen + 1] == 'e' || src[*usedlen + 1] == 'r')) {
/* find a '.' in the tail:
@@ -24334,8 +10064,8 @@ repeat:
*usedlen += 2;
}
- /* ":s?pat?foo?" - substitute */
- /* ":gs?pat?foo?" - global substitute */
+ // ":s?pat?foo?" - substitute
+ // ":gs?pat?foo?" - global substitute
if (src[*usedlen] == ':'
&& (src[*usedlen + 1] == 's'
|| (src[*usedlen + 1] == 'g' && src[*usedlen + 2] == 's'))) {
@@ -24355,12 +10085,12 @@ repeat:
sep = *s++;
if (sep) {
- /* find end of pattern */
+ // find end of pattern
p = vim_strchr(s, sep);
if (p != NULL) {
pat = vim_strnsave(s, (int)(p - s));
s = p + 1;
- /* find end of substitution */
+ // find end of substitution
p = vim_strchr(s, sep);
if (p != NULL) {
sub = vim_strnsave(s, (int)(p - s));
@@ -24377,9 +10107,10 @@ repeat:
}
xfree(pat);
}
- /* after using ":s", repeat all the modifiers */
- if (didit)
+ // after using ":s", repeat all the modifiers
+ if (didit) {
goto repeat;
+ }
}
}
@@ -24418,7 +10149,7 @@ char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub,
char_u *save_cpo;
char_u *zero_width = NULL;
- /* Make 'cpoptions' empty, so that the 'l' flag doesn't work here */
+ // Make 'cpoptions' empty, so that the 'l' flag doesn't work here
save_cpo = p_cpo;
p_cpo = empty_option;
@@ -24432,7 +10163,7 @@ char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub,
tail = str;
end = str + STRLEN(str);
while (vim_regexec_nl(&regmatch, str, (colnr_T)(tail - str))) {
- /* Skip empty match except for first match. */
+ // Skip empty match except for first match.
if (regmatch.startp[0] == regmatch.endp[0]) {
if (zero_width == regmatch.startp[0]) {
// avoid getting stuck on a match with an empty string
@@ -24454,7 +10185,7 @@ char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub,
ga_grow(&ga, (int)((end - tail) + sublen -
(regmatch.endp[0] - regmatch.startp[0])));
- /* copy the text up to where the match is */
+ // copy the text up to where the match is
int i = (int)(regmatch.startp[0] - tail);
memmove((char_u *)ga.ga_data + ga.ga_len, tail, (size_t)i);
// add the substituted text
@@ -24489,10 +10220,10 @@ char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub,
/// common code for getting job callbacks for jobstart, termopen and rpcstart
///
/// @return true/false on success/failure.
-static inline bool common_job_callbacks(dict_T *vopts,
- CallbackReader *on_stdout,
- CallbackReader *on_stderr,
- Callback *on_exit)
+bool common_job_callbacks(dict_T *vopts,
+ CallbackReader *on_stdout,
+ CallbackReader *on_stderr,
+ Callback *on_exit)
{
if (tv_dict_get_callback(vopts, S_LEN("on_stdout"), &on_stdout->cb)
&&tv_dict_get_callback(vopts, S_LEN("on_stderr"), &on_stderr->cb)
@@ -24516,7 +10247,7 @@ static inline bool common_job_callbacks(dict_T *vopts,
}
-static Channel *find_job(uint64_t id, bool show_error)
+Channel *find_job(uint64_t id, bool show_error)
{
Channel *data = find_channel(id);
if (!data || data->streamtype != kChannelStreamProc
@@ -24534,7 +10265,7 @@ static Channel *find_job(uint64_t id, bool show_error)
}
-static void script_host_eval(char *name, typval_T *argvars, typval_T *rettv)
+void script_host_eval(char *name, typval_T *argvars, typval_T *rettv)
{
if (check_restricted() || check_secure()) {
return;
@@ -24711,3 +10442,51 @@ void ex_checkhealth(exarg_T *eap)
xfree(buf);
}
+
+void invoke_prompt_callback(void)
+{
+ typval_T rettv;
+ typval_T argv[2];
+ char_u *text;
+ char_u *prompt;
+ linenr_T lnum = curbuf->b_ml.ml_line_count;
+
+ // Add a new line for the prompt before invoking the callback, so that
+ // text can always be inserted above the last line.
+ ml_append(lnum, (char_u *)"", 0, false);
+ curwin->w_cursor.lnum = lnum + 1;
+ curwin->w_cursor.col = 0;
+
+ if (curbuf->b_prompt_callback.type == kCallbackNone) {
+ return;
+ }
+ text = ml_get(lnum);
+ prompt = prompt_text();
+ if (STRLEN(text) >= STRLEN(prompt)) {
+ text += STRLEN(prompt);
+ }
+ argv[0].v_type = VAR_STRING;
+ argv[0].vval.v_string = vim_strsave(text);
+ argv[1].v_type = VAR_UNKNOWN;
+
+ callback_call(&curbuf->b_prompt_callback, 1, argv, &rettv);
+ tv_clear(&argv[0]);
+ tv_clear(&rettv);
+}
+
+// Return true When the interrupt callback was invoked.
+bool invoke_prompt_interrupt(void)
+{
+ typval_T rettv;
+ typval_T argv[1];
+
+ if (curbuf->b_prompt_interrupt.type == kCallbackNone) {
+ return false;
+ }
+ argv[0].v_type = VAR_UNKNOWN;
+
+ got_int = false; // don't skip executing commands
+ callback_call(&curbuf->b_prompt_interrupt, 0, argv, &rettv);
+ tv_clear(&rettv);
+ return true;
+}
diff --git a/src/nvim/eval.h b/src/nvim/eval.h
index 2aa08e2074..ebc0eb0b1a 100644
--- a/src/nvim/eval.h
+++ b/src/nvim/eval.h
@@ -1,16 +1,13 @@
#ifndef NVIM_EVAL_H
#define NVIM_EVAL_H
-#include "nvim/hashtab.h" // For hashtab_T
#include "nvim/buffer_defs.h"
-#include "nvim/ex_cmds_defs.h" // For exarg_T
-#include "nvim/eval/typval.h"
-#include "nvim/profile.h"
-#include "nvim/garray.h"
-#include "nvim/event/rstream.h"
-#include "nvim/event/wstream.h"
#include "nvim/channel.h"
-#include "nvim/os/stdpaths_defs.h"
+#include "nvim/eval/funcs.h" // For FunPtr
+#include "nvim/event/time.h" // For TimeWatcher
+#include "nvim/ex_cmds_defs.h" // For exarg_T
+#include "nvim/os/fileio.h" // For FileDescriptor
+#include "nvim/os/stdpaths_defs.h" // For XDGVarType
#define COPYID_INC 2
#define COPYID_MASK (~0x1)
@@ -24,6 +21,50 @@ EXTERN ufunc_T dumuf;
#define HIKEY2UF(p) ((ufunc_T *)(p - offsetof(ufunc_T, uf_name)))
#define HI2UF(hi) HIKEY2UF((hi)->hi_key)
+/*
+ * Structure returned by get_lval() and used by set_var_lval().
+ * For a plain name:
+ * "name" points to the variable name.
+ * "exp_name" is NULL.
+ * "tv" is NULL
+ * For a magic braces name:
+ * "name" points to the expanded variable name.
+ * "exp_name" is non-NULL, to be freed later.
+ * "tv" is NULL
+ * For an index in a list:
+ * "name" points to the (expanded) variable name.
+ * "exp_name" NULL or non-NULL, to be freed later.
+ * "tv" points to the (first) list item value
+ * "li" points to the (first) list item
+ * "range", "n1", "n2" and "empty2" indicate what items are used.
+ * For an existing Dict item:
+ * "name" points to the (expanded) variable name.
+ * "exp_name" NULL or non-NULL, to be freed later.
+ * "tv" points to the dict item value
+ * "newkey" is NULL
+ * For a non-existing Dict item:
+ * "name" points to the (expanded) variable name.
+ * "exp_name" NULL or non-NULL, to be freed later.
+ * "tv" points to the Dictionary typval_T
+ * "newkey" is the key for the new item.
+ */
+typedef struct lval_S {
+ const char *ll_name; ///< Start of variable name (can be NULL).
+ size_t ll_name_len; ///< Length of the .ll_name.
+ char *ll_exp_name; ///< NULL or expanded name in allocated memory.
+ typval_T *ll_tv; ///< Typeval of item being used. If "newkey"
+ ///< isn't NULL it's the Dict to which to add the item.
+ listitem_T *ll_li; ///< The list item or NULL.
+ list_T *ll_list; ///< The list or NULL.
+ int ll_range; ///< TRUE when a [i:j] range was used.
+ long ll_n1; ///< First index for list.
+ long ll_n2; ///< Second index for list range.
+ int ll_empty2; ///< Second index is empty: [i:].
+ dict_T *ll_dict; ///< The Dictionary or NULL.
+ dictitem_T *ll_di; ///< The dictitem or NULL.
+ char_u *ll_newkey; ///< New key for Dict in allocated memory or NULL.
+} lval_T;
+
/// enum used by var_flavour()
typedef enum {
VAR_FLAVOUR_DEFAULT = 1, // doesn't start with uppercase
@@ -139,8 +180,60 @@ extern const list_T *eval_msgpack_type_lists[LAST_MSGPACK_TYPE + 1];
#undef LAST_MSGPACK_TYPE
-typedef int (*ArgvFunc)(int current_argcount, typval_T *argv,
- int called_func_argcount);
+/// trans_function_name() flags
+typedef enum {
+ TFN_INT = 1, ///< May use internal function name
+ TFN_QUIET = 2, ///< Do not emit error messages.
+ TFN_NO_AUTOLOAD = 4, ///< Do not use script autoloading.
+ TFN_NO_DEREF = 8, ///< Do not dereference a Funcref.
+ TFN_READ_ONLY = 16, ///< Will not change the variable.
+} TransFunctionNameFlags;
+
+/// get_lval() flags
+typedef enum {
+ GLV_QUIET = TFN_QUIET, ///< Do not emit error messages.
+ GLV_NO_AUTOLOAD = TFN_NO_AUTOLOAD, ///< Do not use script autoloading.
+ GLV_READ_ONLY = TFN_READ_ONLY, ///< Indicates that caller will not change
+ ///< the value (prevents error message).
+} GetLvalFlags;
+
+/// flags for find_name_end()
+#define FNE_INCL_BR 1 /* find_name_end(): include [] in name */
+#define FNE_CHECK_START 2 /* find_name_end(): check name starts with
+ valid character */
+
+typedef struct {
+ TimeWatcher tw;
+ int timer_id;
+ int repeat_count;
+ int refcount;
+ int emsg_count; ///< Errors in a repeating timer.
+ long timeout;
+ bool stopped;
+ bool paused;
+ Callback callback;
+} timer_T;
+
+/// Type of assert_* check being performed
+typedef enum
+{
+ ASSERT_EQUAL,
+ ASSERT_NOTEQUAL,
+ ASSERT_MATCH,
+ ASSERT_NOTMATCH,
+ ASSERT_INRANGE,
+ ASSERT_OTHER,
+} assert_type_T;
+
+/// Type for dict_list function
+typedef enum {
+ kDictListKeys, ///< List dictionary keys.
+ kDictListValues, ///< List dictionary values.
+ kDictListItems, ///< List dictionary contents: [keys, values].
+} DictListType;
+
+// Used for checking if local variables or arguments used in a lambda.
+extern bool *eval_lavars_used;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval.h.generated.h"
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index efeac70816..65c4cfe553 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -82,6 +82,7 @@ return {
ctxset={args={1, 2}},
ctxsize={},
cursor={args={1, 3}},
+ debugbreak={args={1, 1}},
deepcopy={args={1, 2}},
delete={args={1,2}},
deletebufline={args={2,3}},
@@ -101,6 +102,7 @@ return {
exists={args=1},
exp={args=1, func="float_op_wrapper", data="&exp"},
expand={args={1, 3}},
+ expandcmd={args=1},
extend={args={2, 3}},
feedkeys={args={1, 2}},
file_readable={args=1, func='f_filereadable'}, -- obsolete
@@ -213,6 +215,7 @@ return {
line={args=1},
line2byte={args=1},
lispindent={args=1},
+ list2str={args={1, 2}},
localtime={},
log={args=1, func="float_op_wrapper", data="&log"},
log10={args=1, func="float_op_wrapper", data="&log10"},
@@ -243,12 +246,16 @@ return {
pow={args=2},
prevnonblank={args=1},
printf={args=varargs(1)},
+ prompt_setcallback={args={2, 2}},
+ prompt_setinterrupt={args={2, 2}},
+ prompt_setprompt={args={2, 2}},
pum_getpos={},
pumvisible={},
py3eval={args=1},
pyeval={args=1},
pyxeval={args=1},
range={args={1, 3}},
+ readdir={args={1, 2}},
readfile={args={1, 3}},
reg_executing={},
reg_recording={},
@@ -317,6 +324,7 @@ return {
sqrt={args=1, func="float_op_wrapper", data="&sqrt"},
stdpath={args=1},
str2float={args=1},
+ str2list={args={1, 2}},
str2nr={args={1, 2}},
strcharpart={args={2, 3}},
strchars={args={1,2}},
diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c
index 6074e4ee69..138f638eb2 100644
--- a/src/nvim/eval/encode.c
+++ b/src/nvim/eval/encode.c
@@ -248,7 +248,7 @@ bool encode_vim_list_to_buf(const list_T *const list, size_t *const ret_len,
/// @param[out] read_bytes Is set to amount of bytes read.
///
/// @return OK when reading was finished, FAIL in case of error (i.e. list item
-/// was not a string), NOTDONE if reading was successfull, but there are
+/// was not a string), NOTDONE if reading was successful, but there are
/// more bytes to read.
int encode_read_from_list(ListReaderState *const state, char *const buf,
const size_t nbuf, size_t *const read_bytes)
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
new file mode 100644
index 0000000000..c7df1d6753
--- /dev/null
+++ b/src/nvim/eval/funcs.c
@@ -0,0 +1,11192 @@
+// This is an open source non-commercial project. Dear PVS-Studio, please check
+// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+
+#include <float.h>
+#include <math.h>
+
+#include "nvim/api/private/helpers.h"
+#include "nvim/api/vim.h"
+#include "nvim/ascii.h"
+#include "nvim/assert.h"
+#include "nvim/buffer.h"
+#include "nvim/change.h"
+#include "nvim/channel.h"
+#include "nvim/charset.h"
+#include "nvim/context.h"
+#include "nvim/cursor.h"
+#include "nvim/diff.h"
+#include "nvim/edit.h"
+#include "nvim/eval.h"
+#include "nvim/eval/decode.h"
+#include "nvim/eval/encode.h"
+#include "nvim/eval/executor.h"
+#include "nvim/eval/funcs.h"
+#include "nvim/eval/userfunc.h"
+#include "nvim/ex_cmds2.h"
+#include "nvim/ex_docmd.h"
+#include "nvim/ex_getln.h"
+#include "nvim/file_search.h"
+#include "nvim/fileio.h"
+#include "nvim/fold.h"
+#include "nvim/if_cscope.h"
+#include "nvim/indent.h"
+#include "nvim/indent_c.h"
+#include "nvim/lua/executor.h"
+#include "nvim/mark.h"
+#include "nvim/math.h"
+#include "nvim/memline.h"
+#include "nvim/misc1.h"
+#include "nvim/mouse.h"
+#include "nvim/move.h"
+#include "nvim/msgpack_rpc/channel.h"
+#include "nvim/msgpack_rpc/server.h"
+#include "nvim/ops.h"
+#include "nvim/option.h"
+#include "nvim/os/dl.h"
+#include "nvim/os/input.h"
+#include "nvim/os/shell.h"
+#include "nvim/path.h"
+#include "nvim/popupmnu.h"
+#include "nvim/quickfix.h"
+#include "nvim/regexp.h"
+#include "nvim/screen.h"
+#include "nvim/search.h"
+#include "nvim/sha256.h"
+#include "nvim/sign.h"
+#include "nvim/spell.h"
+#include "nvim/state.h"
+#include "nvim/syntax.h"
+#include "nvim/tag.h"
+#include "nvim/ui.h"
+#include "nvim/undo.h"
+#include "nvim/version.h"
+#include "nvim/vim.h"
+
+
+/// Describe data to return from find_some_match()
+typedef enum {
+ kSomeMatch, ///< Data for match().
+ kSomeMatchEnd, ///< Data for matchend().
+ kSomeMatchList, ///< Data for matchlist().
+ kSomeMatchStr, ///< Data for matchstr().
+ kSomeMatchStrPos, ///< Data for matchstrpos().
+} SomeMatchType;
+
+KHASH_MAP_INIT_STR(functions, VimLFuncDef)
+
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "eval/funcs.c.generated.h"
+
+#ifdef _MSC_VER
+// This prevents MSVC from replacing the functions with intrinsics,
+// and causing errors when trying to get their addresses in funcs.generated.h
+#pragma function(ceil)
+#pragma function(floor)
+#endif
+
+PRAGMA_DIAG_PUSH_IGNORE_MISSING_PROTOTYPES
+#include "funcs.generated.h"
+PRAGMA_DIAG_POP
+#endif
+
+
+static char *e_listarg = N_("E686: Argument of %s must be a List");
+static char *e_stringreq = N_("E928: String required");
+
+/// Dummy va_list for passing to vim_snprintf
+///
+/// Used because:
+/// - passing a NULL pointer doesn't work when va_list isn't a pointer
+/// - locally in the function results in a "used before set" warning
+/// - using va_start() to initialize it gives "function with fixed args" error
+static va_list dummy_ap;
+
+
+/// Function given to ExpandGeneric() to obtain the list of internal
+/// or user defined function names.
+char_u *get_function_name(expand_T *xp, int idx)
+{
+ static int intidx = -1;
+ char_u *name;
+
+ if (idx == 0)
+ intidx = -1;
+ if (intidx < 0) {
+ name = get_user_func_name(xp, idx);
+ if (name != NULL)
+ return name;
+ }
+ while ((size_t)++intidx < ARRAY_SIZE(functions)
+ && functions[intidx].name[0] == '\0') {
+ }
+
+ if ((size_t)intidx >= ARRAY_SIZE(functions)) {
+ return NULL;
+ }
+
+ const char *const key = functions[intidx].name;
+ const size_t key_len = strlen(key);
+ memcpy(IObuff, key, key_len);
+ IObuff[key_len] = '(';
+ if (functions[intidx].max_argc == 0) {
+ IObuff[key_len + 1] = ')';
+ IObuff[key_len + 2] = NUL;
+ } else {
+ IObuff[key_len + 1] = NUL;
+ }
+ return IObuff;
+}
+
+/// Function given to ExpandGeneric() to obtain the list of internal or
+/// user defined variable or function names.
+char_u *get_expr_name(expand_T *xp, int idx)
+{
+ static int intidx = -1;
+ char_u *name;
+
+ if (idx == 0)
+ intidx = -1;
+ if (intidx < 0) {
+ name = get_function_name(xp, idx);
+ if (name != NULL)
+ return name;
+ }
+ return get_user_var_name(xp, ++intidx);
+}
+
+/// Find internal function in hash functions
+///
+/// @param[in] name Name of the function.
+///
+/// Returns pointer to the function definition or NULL if not found.
+const VimLFuncDef *find_internal_func(const char *const name)
+ FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE FUNC_ATTR_NONNULL_ALL
+{
+ size_t len = strlen(name);
+ return find_internal_func_gperf(name, len);
+}
+
+/*
+ * Return TRUE for a non-zero Number and a non-empty String.
+ */
+static int non_zero_arg(typval_T *argvars)
+{
+ return ((argvars[0].v_type == VAR_NUMBER
+ && argvars[0].vval.v_number != 0)
+ || (argvars[0].v_type == VAR_SPECIAL
+ && argvars[0].vval.v_special == kSpecialVarTrue)
+ || (argvars[0].v_type == VAR_STRING
+ && argvars[0].vval.v_string != NULL
+ && *argvars[0].vval.v_string != NUL));
+}
+
+// Apply a floating point C function on a typval with one float_T.
+//
+// Some versions of glibc on i386 have an optimization that makes it harder to
+// call math functions indirectly from inside an inlined function, causing
+// compile-time errors. Avoid `inline` in that case. #3072
+static void float_op_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ float_T f;
+ float_T (*function)(float_T) = (float_T (*)(float_T))fptr;
+
+ rettv->v_type = VAR_FLOAT;
+ if (tv_get_float_chk(argvars, &f)) {
+ rettv->vval.v_float = function(f);
+ } else {
+ rettv->vval.v_float = 0.0;
+ }
+}
+
+static void api_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (check_restricted() || check_secure()) {
+ return;
+ }
+
+ ApiDispatchWrapper fn = (ApiDispatchWrapper)fptr;
+
+ Array args = ARRAY_DICT_INIT;
+
+ for (typval_T *tv = argvars; tv->v_type != VAR_UNKNOWN; tv++) {
+ ADD(args, vim_to_object(tv));
+ }
+
+ Error err = ERROR_INIT;
+ Object result = fn(VIML_INTERNAL_CALL, args, &err);
+
+ if (ERROR_SET(&err)) {
+ emsgf_multiline((const char *)e_api_error, err.msg);
+ goto end;
+ }
+
+ if (!object_to_vim(result, rettv, &err)) {
+ EMSG2(_("Error converting the call result: %s"), err.msg);
+ }
+
+end:
+ api_free_array(args);
+ api_free_object(result);
+ api_clear_error(&err);
+}
+
+/*
+ * "abs(expr)" function
+ */
+static void f_abs(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (argvars[0].v_type == VAR_FLOAT) {
+ float_op_wrapper(argvars, rettv, (FunPtr)&fabs);
+ } else {
+ varnumber_T n;
+ bool error = false;
+
+ n = tv_get_number_chk(&argvars[0], &error);
+ if (error) {
+ rettv->vval.v_number = -1;
+ } else if (n > 0) {
+ rettv->vval.v_number = n;
+ } else {
+ rettv->vval.v_number = -n;
+ }
+ }
+}
+
+/*
+ * "add(list, item)" function
+ */
+static void f_add(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = 1; // Default: failed.
+ if (argvars[0].v_type == VAR_LIST) {
+ list_T *const l = argvars[0].vval.v_list;
+ if (!tv_check_lock(tv_list_locked(l), N_("add() argument"), TV_TRANSLATE)) {
+ tv_list_append_tv(l, &argvars[1]);
+ tv_copy(&argvars[0], rettv);
+ }
+ } else {
+ EMSG(_(e_listreq));
+ }
+}
+
+/*
+ * "and(expr, expr)" function
+ */
+static void f_and(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL)
+ & tv_get_number_chk(&argvars[1], NULL);
+}
+
+
+/// "api_info()" function
+static void f_api_info(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ Dictionary metadata = api_metadata();
+ (void)object_to_vim(DICTIONARY_OBJ(metadata), rettv, NULL);
+ api_free_dictionary(metadata);
+}
+
+// "append(lnum, string/list)" function
+static void f_append(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const linenr_T lnum = tv_get_lnum(&argvars[0]);
+
+ set_buffer_lines(curbuf, lnum, true, &argvars[1], rettv);
+}
+
+// "appendbufline(buf, lnum, string/list)" function
+static void f_appendbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ buf_T *const buf = tv_get_buf(&argvars[0], false);
+ if (buf == NULL) {
+ rettv->vval.v_number = 1; // FAIL
+ } else {
+ const linenr_T lnum = tv_get_lnum_buf(&argvars[1], buf);
+ set_buffer_lines(buf, lnum, true, &argvars[2], rettv);
+ }
+}
+
+static void f_argc(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (argvars[0].v_type == VAR_UNKNOWN) {
+ // use the current window
+ rettv->vval.v_number = ARGCOUNT;
+ } else if (argvars[0].v_type == VAR_NUMBER
+ && tv_get_number(&argvars[0]) == -1) {
+ // use the global argument list
+ rettv->vval.v_number = GARGCOUNT;
+ } else {
+ // use the argument list of the specified window
+ win_T *wp = find_win_by_nr_or_id(&argvars[0]);
+ if (wp != NULL) {
+ rettv->vval.v_number = WARGCOUNT(wp);
+ } else {
+ rettv->vval.v_number = -1;
+ }
+ }
+}
+
+/*
+ * "argidx()" function
+ */
+static void f_argidx(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = curwin->w_arg_idx;
+}
+
+/// "arglistid" function
+static void f_arglistid(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = -1;
+ win_T *wp = find_tabwin(&argvars[0], &argvars[1]);
+ if (wp != NULL) {
+ rettv->vval.v_number = wp->w_alist->id;
+ }
+}
+
+/*
+ * "argv(nr)" function
+ */
+static void f_argv(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ aentry_T *arglist = NULL;
+ int argcount = -1;
+
+ if (argvars[0].v_type != VAR_UNKNOWN) {
+ if (argvars[1].v_type == VAR_UNKNOWN) {
+ arglist = ARGLIST;
+ argcount = ARGCOUNT;
+ } else if (argvars[1].v_type == VAR_NUMBER
+ && tv_get_number(&argvars[1]) == -1) {
+ arglist = GARGLIST;
+ argcount = GARGCOUNT;
+ } else {
+ win_T *wp = find_win_by_nr_or_id(&argvars[1]);
+ if (wp != NULL) {
+ // Use the argument list of the specified window
+ arglist = WARGLIST(wp);
+ argcount = WARGCOUNT(wp);
+ }
+ }
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+ int idx = tv_get_number_chk(&argvars[0], NULL);
+ if (arglist != NULL && idx >= 0 && idx < argcount) {
+ rettv->vval.v_string = (char_u *)xstrdup(
+ (const char *)alist_name(&arglist[idx]));
+ } else if (idx == -1) {
+ get_arglist_as_rettv(arglist, argcount, rettv);
+ }
+ } else {
+ get_arglist_as_rettv(ARGLIST, ARGCOUNT, rettv);
+ }
+}
+
+static void f_assert_beeps(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *const cmd = tv_get_string_chk(&argvars[0]);
+ garray_T ga;
+ int ret = 0;
+
+ called_vim_beep = false;
+ suppress_errthrow = true;
+ emsg_silent = false;
+ do_cmdline_cmd(cmd);
+ if (!called_vim_beep) {
+ prepare_assert_error(&ga);
+ ga_concat(&ga, (const char_u *)"command did not beep: ");
+ ga_concat(&ga, (const char_u *)cmd);
+ assert_error(&ga);
+ ga_clear(&ga);
+ ret = 1;
+ }
+
+ suppress_errthrow = false;
+ emsg_on_display = false;
+ rettv->vval.v_number = ret;
+}
+
+// "assert_equal(expected, actual[, msg])" function
+static void f_assert_equal(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = assert_equal_common(argvars, ASSERT_EQUAL);
+}
+
+// "assert_equalfile(fname-one, fname-two)" function
+static void f_assert_equalfile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = assert_equalfile(argvars);
+}
+
+// "assert_notequal(expected, actual[, msg])" function
+static void f_assert_notequal(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = assert_equal_common(argvars, ASSERT_NOTEQUAL);
+}
+
+/// "assert_report(msg)
+static void f_assert_report(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ garray_T ga;
+
+ prepare_assert_error(&ga);
+ ga_concat(&ga, (const char_u *)tv_get_string(&argvars[0]));
+ assert_error(&ga);
+ ga_clear(&ga);
+ rettv->vval.v_number = 1;
+}
+
+/// "assert_exception(string[, msg])" function
+static void f_assert_exception(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = assert_exception(argvars);
+}
+
+/// "assert_fails(cmd [, error [, msg]])" function
+static void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = assert_fails(argvars);
+}
+
+// "assert_false(actual[, msg])" function
+static void f_assert_false(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = assert_bool(argvars, false);
+}
+
+/// "assert_inrange(lower, upper[, msg])" function
+static void f_assert_inrange(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = assert_inrange(argvars);
+}
+
+/// "assert_match(pattern, actual[, msg])" function
+static void f_assert_match(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = assert_match_common(argvars, ASSERT_MATCH);
+}
+
+/// "assert_notmatch(pattern, actual[, msg])" function
+static void f_assert_notmatch(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = assert_match_common(argvars, ASSERT_NOTMATCH);
+}
+
+// "assert_true(actual[, msg])" function
+static void f_assert_true(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = assert_bool(argvars, true);
+}
+
+/*
+ * "atan2()" function
+ */
+static void f_atan2(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ float_T fx;
+ float_T fy;
+
+ rettv->v_type = VAR_FLOAT;
+ if (tv_get_float_chk(argvars, &fx) && tv_get_float_chk(&argvars[1], &fy)) {
+ rettv->vval.v_float = atan2(fx, fy);
+ } else {
+ rettv->vval.v_float = 0.0;
+ }
+}
+
+/*
+ * "browse(save, title, initdir, default)" function
+ */
+static void f_browse(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_string = NULL;
+ rettv->v_type = VAR_STRING;
+}
+
+/*
+ * "browsedir(title, initdir)" function
+ */
+static void f_browsedir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ f_browse(argvars, rettv, NULL);
+}
+
+
+/*
+ * Find a buffer by number or exact name.
+ */
+static buf_T *find_buffer(typval_T *avar)
+{
+ buf_T *buf = NULL;
+
+ if (avar->v_type == VAR_NUMBER)
+ buf = buflist_findnr((int)avar->vval.v_number);
+ else if (avar->v_type == VAR_STRING && avar->vval.v_string != NULL) {
+ buf = buflist_findname_exp(avar->vval.v_string);
+ if (buf == NULL) {
+ /* No full path name match, try a match with a URL or a "nofile"
+ * buffer, these don't use the full path. */
+ FOR_ALL_BUFFERS(bp) {
+ if (bp->b_fname != NULL
+ && (path_with_url((char *)bp->b_fname)
+ || bt_nofile(bp)
+ )
+ && STRCMP(bp->b_fname, avar->vval.v_string) == 0) {
+ buf = bp;
+ break;
+ }
+ }
+ }
+ }
+ return buf;
+}
+
+// "bufadd(expr)" function
+static void f_bufadd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char_u *name = (char_u *)tv_get_string(&argvars[0]);
+
+ rettv->vval.v_number = buflist_add(*name == NUL ? NULL : name, 0);
+}
+
+/*
+ * "bufexists(expr)" function
+ */
+static void f_bufexists(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = (find_buffer(&argvars[0]) != NULL);
+}
+
+/*
+ * "buflisted(expr)" function
+ */
+static void f_buflisted(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ buf_T *buf;
+
+ buf = find_buffer(&argvars[0]);
+ rettv->vval.v_number = (buf != NULL && buf->b_p_bl);
+}
+
+// "bufload(expr)" function
+static void f_bufload(typval_T *argvars, typval_T *unused, FunPtr fptr)
+{
+ buf_T *buf = get_buf_arg(&argvars[0]);
+
+ if (buf != NULL && buf->b_ml.ml_mfp == NULL) {
+ aco_save_T aco;
+
+ aucmd_prepbuf(&aco, buf);
+ swap_exists_action = SEA_NONE;
+ open_buffer(false, NULL, 0);
+ aucmd_restbuf(&aco);
+ }
+}
+
+/*
+ * "bufloaded(expr)" function
+ */
+static void f_bufloaded(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ buf_T *buf;
+
+ buf = find_buffer(&argvars[0]);
+ rettv->vval.v_number = (buf != NULL && buf->b_ml.ml_mfp != NULL);
+}
+
+/*
+ * "bufname(expr)" function
+ */
+static void f_bufname(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const buf_T *buf;
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+ if (argvars[0].v_type == VAR_UNKNOWN) {
+ buf = curbuf;
+ } else {
+ if (!tv_check_str_or_nr(&argvars[0])) {
+ return;
+ }
+ emsg_off++;
+ buf = tv_get_buf(&argvars[0], false);
+ emsg_off--;
+ }
+ if (buf != NULL && buf->b_fname != NULL) {
+ rettv->vval.v_string = (char_u *)xstrdup((char *)buf->b_fname);
+ }
+}
+
+/*
+ * "bufnr(expr)" function
+ */
+static void f_bufnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const buf_T *buf;
+ bool error = false;
+
+ rettv->vval.v_number = -1;
+
+ if (argvars[0].v_type == VAR_UNKNOWN) {
+ buf = curbuf;
+ } else {
+ if (!tv_check_str_or_nr(&argvars[0])) {
+ return;
+ }
+ emsg_off++;
+ buf = tv_get_buf(&argvars[0], false);
+ emsg_off--;
+ }
+
+ // If the buffer isn't found and the second argument is not zero create a
+ // new buffer.
+ const char *name;
+ if (buf == NULL
+ && argvars[1].v_type != VAR_UNKNOWN
+ && tv_get_number_chk(&argvars[1], &error) != 0
+ && !error
+ && (name = tv_get_string_chk(&argvars[0])) != NULL) {
+ buf = buflist_new((char_u *)name, NULL, 1, 0);
+ }
+
+ if (buf != NULL) {
+ rettv->vval.v_number = buf->b_fnum;
+ }
+}
+
+static void buf_win_common(typval_T *argvars, typval_T *rettv, bool get_nr)
+{
+ if (!tv_check_str_or_nr(&argvars[0])) {
+ rettv->vval.v_number = -1;
+ return;
+ }
+
+ emsg_off++;
+ buf_T *buf = tv_get_buf(&argvars[0], true);
+ if (buf == NULL) { // no need to search if buffer was not found
+ rettv->vval.v_number = -1;
+ goto end;
+ }
+
+ int winnr = 0;
+ int winid;
+ bool found_buf = false;
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ winnr++;
+ if (wp->w_buffer == buf) {
+ found_buf = true;
+ winid = wp->handle;
+ break;
+ }
+ }
+ rettv->vval.v_number = (found_buf ? (get_nr ? winnr : winid) : -1);
+end:
+ emsg_off--;
+}
+
+/// "bufwinid(nr)" function
+static void f_bufwinid(typval_T *argvars, typval_T *rettv, FunPtr fptr) {
+ buf_win_common(argvars, rettv, false);
+}
+
+/// "bufwinnr(nr)" function
+static void f_bufwinnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ buf_win_common(argvars, rettv, true);
+}
+
+/*
+ * Get buffer by number or pattern.
+ */
+buf_T *tv_get_buf(typval_T *tv, int curtab_only)
+{
+ char_u *name = tv->vval.v_string;
+ int save_magic;
+ char_u *save_cpo;
+ buf_T *buf;
+
+ if (tv->v_type == VAR_NUMBER)
+ return buflist_findnr((int)tv->vval.v_number);
+ if (tv->v_type != VAR_STRING)
+ return NULL;
+ if (name == NULL || *name == NUL)
+ return curbuf;
+ if (name[0] == '$' && name[1] == NUL)
+ return lastbuf;
+
+ // Ignore 'magic' and 'cpoptions' here to make scripts portable
+ save_magic = p_magic;
+ p_magic = TRUE;
+ save_cpo = p_cpo;
+ p_cpo = (char_u *)"";
+
+ buf = buflist_findnr(buflist_findpat(name, name + STRLEN(name),
+ TRUE, FALSE, curtab_only));
+
+ p_magic = save_magic;
+ p_cpo = save_cpo;
+
+ // If not found, try expanding the name, like done for bufexists().
+ if (buf == NULL) {
+ buf = find_buffer(tv);
+ }
+
+ return buf;
+}
+
+/// Get the buffer from "arg" and give an error and return NULL if it is not
+/// valid.
+buf_T * get_buf_arg(typval_T *arg)
+{
+ buf_T *buf;
+
+ emsg_off++;
+ buf = tv_get_buf(arg, false);
+ emsg_off--;
+ if (buf == NULL) {
+ EMSG2(_("E158: Invalid buffer name: %s"), tv_get_string(arg));
+ }
+ return buf;
+}
+
+/*
+ * "byte2line(byte)" function
+ */
+static void f_byte2line(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ long boff = tv_get_number(&argvars[0]) - 1;
+ if (boff < 0) {
+ rettv->vval.v_number = -1;
+ } else {
+ rettv->vval.v_number = (varnumber_T)ml_find_line_or_offset(curbuf, 0,
+ &boff, false);
+ }
+}
+
+static void byteidx(typval_T *argvars, typval_T *rettv, int comp)
+{
+ const char *const str = tv_get_string_chk(&argvars[0]);
+ varnumber_T idx = tv_get_number_chk(&argvars[1], NULL);
+ rettv->vval.v_number = -1;
+ if (str == NULL || idx < 0) {
+ return;
+ }
+
+ const char *t = str;
+ for (; idx > 0; idx--) {
+ if (*t == NUL) { // EOL reached.
+ return;
+ }
+ if (enc_utf8 && comp) {
+ t += utf_ptr2len((const char_u *)t);
+ } else {
+ t += (*mb_ptr2len)((const char_u *)t);
+ }
+ }
+ rettv->vval.v_number = (varnumber_T)(t - str);
+}
+
+/*
+ * "byteidx()" function
+ */
+static void f_byteidx(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ byteidx(argvars, rettv, FALSE);
+}
+
+/*
+ * "byteidxcomp()" function
+ */
+static void f_byteidxcomp(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ byteidx(argvars, rettv, TRUE);
+}
+
+/// "call(func, arglist [, dict])" function
+static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (argvars[1].v_type != VAR_LIST) {
+ EMSG(_(e_listreq));
+ return;
+ }
+ if (argvars[1].vval.v_list == NULL) {
+ return;
+ }
+
+ char_u *func;
+ partial_T *partial = NULL;
+ dict_T *selfdict = NULL;
+ if (argvars[0].v_type == VAR_FUNC) {
+ func = argvars[0].vval.v_string;
+ } else if (argvars[0].v_type == VAR_PARTIAL) {
+ partial = argvars[0].vval.v_partial;
+ func = partial_name(partial);
+ } else {
+ func = (char_u *)tv_get_string(&argvars[0]);
+ }
+ if (*func == NUL) {
+ return; // type error or empty name
+ }
+
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ if (argvars[2].v_type != VAR_DICT) {
+ EMSG(_(e_dictreq));
+ return;
+ }
+ selfdict = argvars[2].vval.v_dict;
+ }
+
+ func_call(func, &argvars[1], partial, selfdict, rettv);
+}
+
+/*
+ * "changenr()" function
+ */
+static void f_changenr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = curbuf->b_u_seq_cur;
+}
+
+// "chanclose(id[, stream])" function
+static void f_chanclose(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = 0;
+
+ if (check_restricted() || check_secure()) {
+ return;
+ }
+
+ if (argvars[0].v_type != VAR_NUMBER || (argvars[1].v_type != VAR_STRING
+ && argvars[1].v_type != VAR_UNKNOWN)) {
+ EMSG(_(e_invarg));
+ return;
+ }
+
+ ChannelPart part = kChannelPartAll;
+ if (argvars[1].v_type == VAR_STRING) {
+ char *stream = (char *)argvars[1].vval.v_string;
+ if (!strcmp(stream, "stdin")) {
+ part = kChannelPartStdin;
+ } else if (!strcmp(stream, "stdout")) {
+ part = kChannelPartStdout;
+ } else if (!strcmp(stream, "stderr")) {
+ part = kChannelPartStderr;
+ } else if (!strcmp(stream, "rpc")) {
+ part = kChannelPartRpc;
+ } else {
+ EMSG2(_("Invalid channel stream \"%s\""), stream);
+ return;
+ }
+ }
+ const char *error;
+ rettv->vval.v_number = channel_close(argvars[0].vval.v_number, part, &error);
+ if (!rettv->vval.v_number) {
+ EMSG(error);
+ }
+}
+
+// "chansend(id, data)" function
+static void f_chansend(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = 0;
+
+ if (check_restricted() || check_secure()) {
+ return;
+ }
+
+ if (argvars[0].v_type != VAR_NUMBER || argvars[1].v_type == VAR_UNKNOWN) {
+ // First argument is the channel id and second is the data to write
+ EMSG(_(e_invarg));
+ return;
+ }
+
+ ptrdiff_t input_len = 0;
+ char *input = save_tv_as_string(&argvars[1], &input_len, false);
+ if (!input) {
+ // Either the error has been handled by save_tv_as_string(),
+ // or there is no input to send.
+ return;
+ }
+ uint64_t id = argvars[0].vval.v_number;
+ const char *error = NULL;
+ rettv->vval.v_number = channel_send(id, input, input_len, &error);
+ if (error) {
+ EMSG(error);
+ }
+}
+
+/*
+ * "char2nr(string)" function
+ */
+static void f_char2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ if (!tv_check_num(&argvars[1])) {
+ return;
+ }
+ }
+
+ rettv->vval.v_number = utf_ptr2char(
+ (const char_u *)tv_get_string(&argvars[0]));
+}
+
+/*
+ * "cindent(lnum)" function
+ */
+static void f_cindent(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ pos_T pos;
+ linenr_T lnum;
+
+ pos = curwin->w_cursor;
+ lnum = tv_get_lnum(argvars);
+ if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) {
+ curwin->w_cursor.lnum = lnum;
+ rettv->vval.v_number = get_c_indent();
+ curwin->w_cursor = pos;
+ } else
+ rettv->vval.v_number = -1;
+}
+
+/*
+ * "clearmatches()" function
+ */
+static void f_clearmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ clear_matches(curwin);
+}
+
+/*
+ * "col(string)" function
+ */
+static void f_col(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ colnr_T col = 0;
+ pos_T *fp;
+ int fnum = curbuf->b_fnum;
+
+ fp = var2fpos(&argvars[0], FALSE, &fnum);
+ if (fp != NULL && fnum == curbuf->b_fnum) {
+ if (fp->col == MAXCOL) {
+ // '> can be MAXCOL, get the length of the line then
+ if (fp->lnum <= curbuf->b_ml.ml_line_count) {
+ col = (colnr_T)STRLEN(ml_get(fp->lnum)) + 1;
+ } else {
+ col = MAXCOL;
+ }
+ } else {
+ col = fp->col + 1;
+ // col(".") when the cursor is on the NUL at the end of the line
+ // because of "coladd" can be seen as an extra column.
+ if (virtual_active() && fp == &curwin->w_cursor) {
+ char_u *p = get_cursor_pos_ptr();
+
+ if (curwin->w_cursor.coladd >= (colnr_T)chartabsize(p,
+ curwin->w_virtcol - curwin->w_cursor.coladd)) {
+ int l;
+
+ if (*p != NUL && p[(l = (*mb_ptr2len)(p))] == NUL)
+ col += l;
+ }
+ }
+ }
+ }
+ rettv->vval.v_number = col;
+}
+
+/*
+ * "complete()" function
+ */
+static void f_complete(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if ((State & INSERT) == 0) {
+ EMSG(_("E785: complete() can only be used in Insert mode"));
+ return;
+ }
+
+ /* Check for undo allowed here, because if something was already inserted
+ * the line was already saved for undo and this check isn't done. */
+ if (!undo_allowed())
+ return;
+
+ if (argvars[1].v_type != VAR_LIST) {
+ EMSG(_(e_invarg));
+ return;
+ }
+
+ const colnr_T startcol = tv_get_number_chk(&argvars[0], NULL);
+ if (startcol <= 0) {
+ return;
+ }
+
+ set_completion(startcol - 1, argvars[1].vval.v_list);
+}
+
+/*
+ * "complete_add()" function
+ */
+static void f_complete_add(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = ins_compl_add_tv(&argvars[0], 0);
+}
+
+/*
+ * "complete_check()" function
+ */
+static void f_complete_check(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int saved = RedrawingDisabled;
+
+ RedrawingDisabled = 0;
+ ins_compl_check_keys(0, true);
+ rettv->vval.v_number = compl_interrupted;
+ RedrawingDisabled = saved;
+}
+
+// "complete_info()" function
+static void f_complete_info(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ tv_dict_alloc_ret(rettv);
+
+ list_T *what_list = NULL;
+
+ if (argvars[0].v_type != VAR_UNKNOWN) {
+ if (argvars[0].v_type != VAR_LIST) {
+ EMSG(_(e_listreq));
+ return;
+ }
+ what_list = argvars[0].vval.v_list;
+ }
+ get_complete_info(what_list, rettv->vval.v_dict);
+}
+
+/*
+ * "confirm(message, buttons[, default [, type]])" function
+ */
+static void f_confirm(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char buf[NUMBUFLEN];
+ char buf2[NUMBUFLEN];
+ const char *message;
+ const char *buttons = NULL;
+ int def = 1;
+ int type = VIM_GENERIC;
+ const char *typestr;
+ bool error = false;
+
+ message = tv_get_string_chk(&argvars[0]);
+ if (message == NULL) {
+ error = true;
+ }
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ buttons = tv_get_string_buf_chk(&argvars[1], buf);
+ if (buttons == NULL) {
+ error = true;
+ }
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ def = tv_get_number_chk(&argvars[2], &error);
+ if (argvars[3].v_type != VAR_UNKNOWN) {
+ typestr = tv_get_string_buf_chk(&argvars[3], buf2);
+ if (typestr == NULL) {
+ error = true;
+ } else {
+ switch (TOUPPER_ASC(*typestr)) {
+ case 'E': type = VIM_ERROR; break;
+ case 'Q': type = VIM_QUESTION; break;
+ case 'I': type = VIM_INFO; break;
+ case 'W': type = VIM_WARNING; break;
+ case 'G': type = VIM_GENERIC; break;
+ }
+ }
+ }
+ }
+ }
+
+ if (buttons == NULL || *buttons == NUL) {
+ buttons = _("&Ok");
+ }
+
+ if (!error) {
+ rettv->vval.v_number = do_dialog(
+ type, NULL, (char_u *)message, (char_u *)buttons, def, NULL, false);
+ }
+}
+
+/*
+ * "copy()" function
+ */
+static void f_copy(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ var_item_copy(NULL, &argvars[0], rettv, false, 0);
+}
+
+/*
+ * "count()" function
+ */
+static void f_count(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ long n = 0;
+ int ic = 0;
+ bool error = false;
+
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ ic = tv_get_number_chk(&argvars[2], &error);
+ }
+
+ if (argvars[0].v_type == VAR_STRING) {
+ const char_u *expr = (char_u *)tv_get_string_chk(&argvars[1]);
+ const char_u *p = argvars[0].vval.v_string;
+
+ if (!error && expr != NULL && *expr != NUL && p != NULL) {
+ if (ic) {
+ const size_t len = STRLEN(expr);
+
+ while (*p != NUL) {
+ if (mb_strnicmp(p, expr, len) == 0) {
+ n++;
+ p += len;
+ } else {
+ MB_PTR_ADV(p);
+ }
+ }
+ } else {
+ char_u *next;
+ while ((next = (char_u *)strstr((char *)p, (char *)expr)) != NULL) {
+ n++;
+ p = next + STRLEN(expr);
+ }
+ }
+ }
+ } else if (argvars[0].v_type == VAR_LIST) {
+ listitem_T *li;
+ list_T *l;
+ long idx;
+
+ if ((l = argvars[0].vval.v_list) != NULL) {
+ li = tv_list_first(l);
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ if (argvars[3].v_type != VAR_UNKNOWN) {
+ idx = tv_get_number_chk(&argvars[3], &error);
+ if (!error) {
+ li = tv_list_find(l, idx);
+ if (li == NULL) {
+ EMSGN(_(e_listidx), idx);
+ }
+ }
+ }
+ if (error)
+ li = NULL;
+ }
+
+ for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) {
+ if (tv_equal(TV_LIST_ITEM_TV(li), &argvars[1], ic, false)) {
+ n++;
+ }
+ }
+ }
+ } else if (argvars[0].v_type == VAR_DICT) {
+ int todo;
+ dict_T *d;
+ hashitem_T *hi;
+
+ if ((d = argvars[0].vval.v_dict) != NULL) {
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ if (argvars[3].v_type != VAR_UNKNOWN) {
+ EMSG(_(e_invarg));
+ }
+ }
+
+ todo = error ? 0 : (int)d->dv_hashtab.ht_used;
+ for (hi = d->dv_hashtab.ht_array; todo > 0; ++hi) {
+ if (!HASHITEM_EMPTY(hi)) {
+ todo--;
+ if (tv_equal(&TV_DICT_HI2DI(hi)->di_tv, &argvars[1], ic, false)) {
+ n++;
+ }
+ }
+ }
+ }
+ } else {
+ EMSG2(_(e_listdictarg), "count()");
+ }
+ rettv->vval.v_number = n;
+}
+
+/*
+ * "cscope_connection([{num} , {dbpath} [, {prepend}]])" function
+ *
+ * Checks the existence of a cscope connection.
+ */
+static void f_cscope_connection(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int num = 0;
+ const char *dbpath = NULL;
+ const char *prepend = NULL;
+ char buf[NUMBUFLEN];
+
+ if (argvars[0].v_type != VAR_UNKNOWN
+ && argvars[1].v_type != VAR_UNKNOWN) {
+ num = (int)tv_get_number(&argvars[0]);
+ dbpath = tv_get_string(&argvars[1]);
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ prepend = tv_get_string_buf(&argvars[2], buf);
+ }
+ }
+
+ rettv->vval.v_number = cs_connection(num, (char_u *)dbpath,
+ (char_u *)prepend);
+}
+
+/// "ctxget([{index}])" function
+static void f_ctxget(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ size_t index = 0;
+ if (argvars[0].v_type == VAR_NUMBER) {
+ index = argvars[0].vval.v_number;
+ } else if (argvars[0].v_type != VAR_UNKNOWN) {
+ EMSG2(_(e_invarg2), "expected nothing or a Number as an argument");
+ return;
+ }
+
+ Context *ctx = ctx_get(index);
+ if (ctx == NULL) {
+ EMSG3(_(e_invargNval), "index", "out of bounds");
+ return;
+ }
+
+ Dictionary ctx_dict = ctx_to_dict(ctx);
+ Error err = ERROR_INIT;
+ object_to_vim(DICTIONARY_OBJ(ctx_dict), rettv, &err);
+ api_free_dictionary(ctx_dict);
+ api_clear_error(&err);
+}
+
+/// "ctxpop()" function
+static void f_ctxpop(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (!ctx_restore(NULL, kCtxAll)) {
+ EMSG(_("Context stack is empty"));
+ }
+}
+
+/// "ctxpush([{types}])" function
+static void f_ctxpush(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int types = kCtxAll;
+ if (argvars[0].v_type == VAR_LIST) {
+ types = 0;
+ TV_LIST_ITER(argvars[0].vval.v_list, li, {
+ typval_T *tv_li = TV_LIST_ITEM_TV(li);
+ if (tv_li->v_type == VAR_STRING) {
+ if (strequal((char *)tv_li->vval.v_string, "regs")) {
+ types |= kCtxRegs;
+ } else if (strequal((char *)tv_li->vval.v_string, "jumps")) {
+ types |= kCtxJumps;
+ } else if (strequal((char *)tv_li->vval.v_string, "bufs")) {
+ types |= kCtxBufs;
+ } else if (strequal((char *)tv_li->vval.v_string, "gvars")) {
+ types |= kCtxGVars;
+ } else if (strequal((char *)tv_li->vval.v_string, "sfuncs")) {
+ types |= kCtxSFuncs;
+ } else if (strequal((char *)tv_li->vval.v_string, "funcs")) {
+ types |= kCtxFuncs;
+ }
+ }
+ });
+ } else if (argvars[0].v_type != VAR_UNKNOWN) {
+ EMSG2(_(e_invarg2), "expected nothing or a List as an argument");
+ return;
+ }
+ ctx_save(NULL, types);
+}
+
+/// "ctxset({context}[, {index}])" function
+static void f_ctxset(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (argvars[0].v_type != VAR_DICT) {
+ EMSG2(_(e_invarg2), "expected dictionary as first argument");
+ return;
+ }
+
+ size_t index = 0;
+ if (argvars[1].v_type == VAR_NUMBER) {
+ index = argvars[1].vval.v_number;
+ } else if (argvars[1].v_type != VAR_UNKNOWN) {
+ EMSG2(_(e_invarg2), "expected nothing or a Number as second argument");
+ return;
+ }
+
+ Context *ctx = ctx_get(index);
+ if (ctx == NULL) {
+ EMSG3(_(e_invargNval), "index", "out of bounds");
+ return;
+ }
+
+ int save_did_emsg = did_emsg;
+ did_emsg = false;
+
+ Dictionary dict = vim_to_object(&argvars[0]).data.dictionary;
+ Context tmp = CONTEXT_INIT;
+ ctx_from_dict(dict, &tmp);
+
+ if (did_emsg) {
+ ctx_free(&tmp);
+ } else {
+ ctx_free(ctx);
+ *ctx = tmp;
+ }
+
+ api_free_dictionary(dict);
+ did_emsg = save_did_emsg;
+}
+
+/// "ctxsize()" function
+static void f_ctxsize(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = ctx_size();
+}
+
+/// "cursor(lnum, col)" function, or
+/// "cursor(list)"
+///
+/// Moves the cursor to the specified line and column.
+///
+/// @returns 0 when the position could be set, -1 otherwise.
+static void f_cursor(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ long line, col;
+ long coladd = 0;
+ bool set_curswant = true;
+
+ rettv->vval.v_number = -1;
+ if (argvars[1].v_type == VAR_UNKNOWN) {
+ pos_T pos;
+ colnr_T curswant = -1;
+
+ if (list2fpos(argvars, &pos, NULL, &curswant) == FAIL) {
+ EMSG(_(e_invarg));
+ return;
+ }
+
+ line = pos.lnum;
+ col = pos.col;
+ coladd = pos.coladd;
+ if (curswant >= 0) {
+ curwin->w_curswant = curswant - 1;
+ set_curswant = false;
+ }
+ } else {
+ line = tv_get_lnum(argvars);
+ col = (long)tv_get_number_chk(&argvars[1], NULL);
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ coladd = (long)tv_get_number_chk(&argvars[2], NULL);
+ }
+ }
+ if (line < 0 || col < 0
+ || coladd < 0) {
+ return; // type error; errmsg already given
+ }
+ if (line > 0) {
+ curwin->w_cursor.lnum = line;
+ }
+ if (col > 0) {
+ curwin->w_cursor.col = col - 1;
+ }
+ curwin->w_cursor.coladd = coladd;
+
+ // Make sure the cursor is in a valid position.
+ check_cursor();
+ // Correct cursor for multi-byte character.
+ if (has_mbyte) {
+ mb_adjust_cursor();
+ }
+
+ curwin->w_set_curswant = set_curswant;
+ rettv->vval.v_number = 0;
+}
+
+// "debugbreak()" function
+static void f_debugbreak(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int pid;
+
+ rettv->vval.v_number = FAIL;
+ pid = (int)tv_get_number(&argvars[0]);
+ if (pid == 0) {
+ EMSG(_(e_invarg));
+ } else {
+#ifdef WIN32
+ HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, pid);
+
+ if (hProcess != NULL) {
+ DebugBreakProcess(hProcess);
+ CloseHandle(hProcess);
+ rettv->vval.v_number = OK;
+ }
+#else
+ uv_kill(pid, SIGINT);
+#endif
+ }
+}
+
+// "deepcopy()" function
+static void f_deepcopy(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int noref = 0;
+
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ noref = tv_get_number_chk(&argvars[1], NULL);
+ }
+ if (noref < 0 || noref > 1) {
+ EMSG(_(e_invarg));
+ } else {
+ var_item_copy(NULL, &argvars[0], rettv, true, (noref == 0
+ ? get_copyID()
+ : 0));
+ }
+}
+
+// "delete()" function
+static void f_delete(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = -1;
+ if (check_restricted() || check_secure()) {
+ return;
+ }
+
+ const char *const name = tv_get_string(&argvars[0]);
+ if (*name == NUL) {
+ EMSG(_(e_invarg));
+ return;
+ }
+
+ char nbuf[NUMBUFLEN];
+ const char *flags;
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ flags = tv_get_string_buf(&argvars[1], nbuf);
+ } else {
+ flags = "";
+ }
+
+ if (*flags == NUL) {
+ // delete a file
+ rettv->vval.v_number = os_remove(name) == 0 ? 0 : -1;
+ } else if (strcmp(flags, "d") == 0) {
+ // delete an empty directory
+ rettv->vval.v_number = os_rmdir(name) == 0 ? 0 : -1;
+ } else if (strcmp(flags, "rf") == 0) {
+ // delete a directory recursively
+ rettv->vval.v_number = delete_recursive(name);
+ } else {
+ emsgf(_(e_invexpr2), flags);
+ }
+}
+
+// dictwatcheradd(dict, key, funcref) function
+static void f_dictwatcheradd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (check_restricted() || check_secure()) {
+ return;
+ }
+
+ if (argvars[0].v_type != VAR_DICT) {
+ emsgf(_(e_invarg2), "dict");
+ return;
+ } else if (argvars[0].vval.v_dict == NULL) {
+ const char *const arg_errmsg = _("dictwatcheradd() argument");
+ const size_t arg_errmsg_len = strlen(arg_errmsg);
+ emsgf(_(e_readonlyvar), (int)arg_errmsg_len, arg_errmsg);
+ return;
+ }
+
+ if (argvars[1].v_type != VAR_STRING && argvars[1].v_type != VAR_NUMBER) {
+ emsgf(_(e_invarg2), "key");
+ return;
+ }
+
+ const char *const key_pattern = tv_get_string_chk(argvars + 1);
+ if (key_pattern == NULL) {
+ return;
+ }
+ const size_t key_pattern_len = strlen(key_pattern);
+
+ Callback callback;
+ if (!callback_from_typval(&callback, &argvars[2])) {
+ emsgf(_(e_invarg2), "funcref");
+ return;
+ }
+
+ tv_dict_watcher_add(argvars[0].vval.v_dict, key_pattern, key_pattern_len,
+ callback);
+}
+
+// dictwatcherdel(dict, key, funcref) function
+static void f_dictwatcherdel(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (check_restricted() || check_secure()) {
+ return;
+ }
+
+ if (argvars[0].v_type != VAR_DICT) {
+ emsgf(_(e_invarg2), "dict");
+ return;
+ }
+
+ if (argvars[2].v_type != VAR_FUNC && argvars[2].v_type != VAR_STRING) {
+ emsgf(_(e_invarg2), "funcref");
+ return;
+ }
+
+ const char *const key_pattern = tv_get_string_chk(argvars + 1);
+ if (key_pattern == NULL) {
+ return;
+ }
+
+ Callback callback;
+ if (!callback_from_typval(&callback, &argvars[2])) {
+ return;
+ }
+
+ if (!tv_dict_watcher_remove(argvars[0].vval.v_dict, key_pattern,
+ strlen(key_pattern), callback)) {
+ EMSG("Couldn't find a watcher matching key and callback");
+ }
+
+ callback_free(&callback);
+}
+
+/// "deletebufline()" function
+static void f_deletebufline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ linenr_T last;
+ buf_T *curbuf_save = NULL;
+ win_T *curwin_save = NULL;
+
+ buf_T *const buf = tv_get_buf(&argvars[0], false);
+ if (buf == NULL) {
+ rettv->vval.v_number = 1; // FAIL
+ return;
+ }
+ const bool is_curbuf = buf == curbuf;
+
+ const linenr_T first = tv_get_lnum_buf(&argvars[1], buf);
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ last = tv_get_lnum_buf(&argvars[2], buf);
+ } else {
+ last = first;
+ }
+
+ if (buf->b_ml.ml_mfp == NULL || first < 1
+ || first > buf->b_ml.ml_line_count || last < first) {
+ rettv->vval.v_number = 1; // FAIL
+ return;
+ }
+
+ if (!is_curbuf) {
+ curbuf_save = curbuf;
+ curwin_save = curwin;
+ curbuf = buf;
+ find_win_for_curbuf();
+ }
+ if (last > curbuf->b_ml.ml_line_count) {
+ last = curbuf->b_ml.ml_line_count;
+ }
+ const long count = last - first + 1;
+
+ // When coming here from Insert mode, sync undo, so that this can be
+ // undone separately from what was previously inserted.
+ if (u_sync_once == 2) {
+ u_sync_once = 1; // notify that u_sync() was called
+ u_sync(true);
+ }
+
+ if (u_save(first - 1, last + 1) == FAIL) {
+ rettv->vval.v_number = 1; // FAIL
+ return;
+ }
+
+ for (linenr_T lnum = first; lnum <= last; lnum++) {
+ ml_delete(first, true);
+ }
+
+ FOR_ALL_TAB_WINDOWS(tp, wp) {
+ if (wp->w_buffer == buf) {
+ if (wp->w_cursor.lnum > last) {
+ wp->w_cursor.lnum -= count;
+ } else if (wp->w_cursor.lnum> first) {
+ wp->w_cursor.lnum = first;
+ }
+ if (wp->w_cursor.lnum > wp->w_buffer->b_ml.ml_line_count) {
+ wp->w_cursor.lnum = wp->w_buffer->b_ml.ml_line_count;
+ }
+ }
+ }
+ check_cursor_col();
+ deleted_lines_mark(first, count);
+
+ if (!is_curbuf) {
+ curbuf = curbuf_save;
+ curwin = curwin_save;
+ }
+}
+
+/*
+ * "did_filetype()" function
+ */
+static void f_did_filetype(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = did_filetype;
+}
+
+/*
+ * "diff_filler()" function
+ */
+static void f_diff_filler(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = diff_check_fill(curwin, tv_get_lnum(argvars));
+}
+
+/*
+ * "diff_hlID()" function
+ */
+static void f_diff_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ linenr_T lnum = tv_get_lnum(argvars);
+ static linenr_T prev_lnum = 0;
+ static int changedtick = 0;
+ static int fnum = 0;
+ static int change_start = 0;
+ static int change_end = 0;
+ static hlf_T hlID = (hlf_T)0;
+ int filler_lines;
+ int col;
+
+ if (lnum < 0) { // ignore type error in {lnum} arg
+ lnum = 0;
+ }
+ if (lnum != prev_lnum
+ || changedtick != buf_get_changedtick(curbuf)
+ || fnum != curbuf->b_fnum) {
+ // New line, buffer, change: need to get the values.
+ filler_lines = diff_check(curwin, lnum);
+ if (filler_lines < 0) {
+ if (filler_lines == -1) {
+ change_start = MAXCOL;
+ change_end = -1;
+ if (diff_find_change(curwin, lnum, &change_start, &change_end)) {
+ hlID = HLF_ADD; // added line
+ } else {
+ hlID = HLF_CHD; // changed line
+ }
+ } else {
+ hlID = HLF_ADD; // added line
+ }
+ } else {
+ hlID = (hlf_T)0;
+ }
+ prev_lnum = lnum;
+ changedtick = buf_get_changedtick(curbuf);
+ fnum = curbuf->b_fnum;
+ }
+
+ if (hlID == HLF_CHD || hlID == HLF_TXD) {
+ col = tv_get_number(&argvars[1]) - 1; // Ignore type error in {col}.
+ if (col >= change_start && col <= change_end) {
+ hlID = HLF_TXD; // Changed text.
+ } else {
+ hlID = HLF_CHD; // Changed line.
+ }
+ }
+ rettv->vval.v_number = hlID == (hlf_T)0 ? 0 : (int)(hlID + 1);
+}
+
+/*
+ * "empty({expr})" function
+ */
+static void f_empty(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ bool n = true;
+
+ switch (argvars[0].v_type) {
+ case VAR_STRING:
+ case VAR_FUNC: {
+ n = argvars[0].vval.v_string == NULL
+ || *argvars[0].vval.v_string == NUL;
+ break;
+ }
+ case VAR_PARTIAL: {
+ n = false;
+ break;
+ }
+ case VAR_NUMBER: {
+ n = argvars[0].vval.v_number == 0;
+ break;
+ }
+ case VAR_FLOAT: {
+ n = argvars[0].vval.v_float == 0.0;
+ break;
+ }
+ case VAR_LIST: {
+ n = (tv_list_len(argvars[0].vval.v_list) == 0);
+ break;
+ }
+ case VAR_DICT: {
+ n = (tv_dict_len(argvars[0].vval.v_dict) == 0);
+ break;
+ }
+ case VAR_SPECIAL: {
+ // Using switch to get warning if SpecialVarValue receives more values.
+ switch (argvars[0].vval.v_special) {
+ case kSpecialVarTrue: {
+ n = false;
+ break;
+ }
+ case kSpecialVarFalse:
+ case kSpecialVarNull: {
+ n = true;
+ break;
+ }
+ }
+ break;
+ }
+ case VAR_UNKNOWN: {
+ internal_error("f_empty(UNKNOWN)");
+ break;
+ }
+ }
+
+ rettv->vval.v_number = n;
+}
+
+/// "environ()" function
+static void f_environ(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ tv_dict_alloc_ret(rettv);
+
+ size_t env_size = os_get_fullenv_size();
+ char **env = xmalloc(sizeof(*env) * (env_size + 1));
+ env[env_size] = NULL;
+
+ os_copy_fullenv(env, env_size);
+
+ for (size_t i = 0; i < env_size; i++) {
+ const char * str = env[i];
+ const char * const end = strchr(str + (str[0] == '=' ? 1 : 0),
+ '=');
+ assert(end != NULL);
+ ptrdiff_t len = end - str;
+ assert(len > 0);
+ const char * value = str + len + 1;
+ tv_dict_add_str(rettv->vval.v_dict,
+ str, len,
+ value);
+ }
+ os_free_fullenv(env);
+}
+
+/*
+ * "escape({string}, {chars})" function
+ */
+static void f_escape(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char buf[NUMBUFLEN];
+
+ rettv->vval.v_string = vim_strsave_escaped(
+ (const char_u *)tv_get_string(&argvars[0]),
+ (const char_u *)tv_get_string_buf(&argvars[1], buf));
+ rettv->v_type = VAR_STRING;
+}
+
+/// "getenv()" function
+static void f_getenv(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char_u *p = (char_u *)vim_getenv(tv_get_string(&argvars[0]));
+
+ if (p == NULL) {
+ rettv->v_type = VAR_SPECIAL;
+ rettv->vval.v_special = kSpecialVarNull;
+ return;
+ }
+ rettv->vval.v_string = p;
+ rettv->v_type = VAR_STRING;
+}
+
+/*
+ * "eval()" function
+ */
+static void f_eval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *s = tv_get_string_chk(&argvars[0]);
+ if (s != NULL) {
+ s = (const char *)skipwhite((const char_u *)s);
+ }
+
+ const char *const expr_start = s;
+ if (s == NULL || eval1((char_u **)&s, rettv, true) == FAIL) {
+ if (expr_start != NULL && !aborting()) {
+ EMSG2(_(e_invexpr2), expr_start);
+ }
+ need_clr_eos = FALSE;
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = 0;
+ } else if (*s != NUL) {
+ EMSG(_(e_trailing));
+ }
+}
+
+/*
+ * "eventhandler()" function
+ */
+static void f_eventhandler(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = vgetc_busy;
+}
+
+/*
+ * "executable()" function
+ */
+static void f_executable(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *name = tv_get_string(&argvars[0]);
+
+ // Check in $PATH and also check directly if there is a directory name
+ rettv->vval.v_number = os_can_exe(name, NULL, true);
+}
+
+typedef struct {
+ const list_T *const l;
+ const listitem_T *li;
+} GetListLineCookie;
+
+static char_u *get_list_line(int c, void *cookie, int indent, bool do_concat)
+{
+ GetListLineCookie *const p = (GetListLineCookie *)cookie;
+
+ const listitem_T *const item = p->li;
+ if (item == NULL) {
+ return NULL;
+ }
+ char buf[NUMBUFLEN];
+ const char *const s = tv_get_string_buf_chk(TV_LIST_ITEM_TV(item), buf);
+ p->li = TV_LIST_ITEM_NEXT(p->l, item);
+ return (char_u *)(s == NULL ? NULL : xstrdup(s));
+}
+
+// "execute(command)" function
+static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const int save_msg_silent = msg_silent;
+ const int save_emsg_silent = emsg_silent;
+ const bool save_emsg_noredir = emsg_noredir;
+ const bool save_redir_off = redir_off;
+ garray_T *const save_capture_ga = capture_ga;
+ const int save_msg_col = msg_col;
+ bool echo_output = false;
+
+ if (check_secure()) {
+ return;
+ }
+
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ char buf[NUMBUFLEN];
+ const char *const s = tv_get_string_buf_chk(&argvars[1], buf);
+
+ if (s == NULL) {
+ return;
+ }
+ if (*s == NUL) {
+ echo_output = true;
+ }
+ if (strncmp(s, "silent", 6) == 0) {
+ msg_silent++;
+ }
+ if (strcmp(s, "silent!") == 0) {
+ emsg_silent = true;
+ emsg_noredir = true;
+ }
+ } else {
+ msg_silent++;
+ }
+
+ garray_T capture_local;
+ ga_init(&capture_local, (int)sizeof(char), 80);
+ capture_ga = &capture_local;
+ redir_off = false;
+ if (!echo_output) {
+ msg_col = 0; // prevent leading spaces
+ }
+
+ if (argvars[0].v_type != VAR_LIST) {
+ do_cmdline_cmd(tv_get_string(&argvars[0]));
+ } else if (argvars[0].vval.v_list != NULL) {
+ list_T *const list = argvars[0].vval.v_list;
+ tv_list_ref(list);
+ GetListLineCookie cookie = {
+ .l = list,
+ .li = tv_list_first(list),
+ };
+ do_cmdline(NULL, get_list_line, (void *)&cookie,
+ DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT|DOCMD_KEYTYPED);
+ tv_list_unref(list);
+ }
+ msg_silent = save_msg_silent;
+ emsg_silent = save_emsg_silent;
+ emsg_noredir = save_emsg_noredir;
+ redir_off = save_redir_off;
+ // "silent reg" or "silent echo x" leaves msg_col somewhere in the line.
+ if (echo_output) {
+ // When not working silently: put it in column zero. A following
+ // "echon" will overwrite the message, unavoidably.
+ msg_col = 0;
+ } else {
+ // When working silently: Put it back where it was, since nothing
+ // should have been written.
+ msg_col = save_msg_col;
+ }
+
+ ga_append(capture_ga, NUL);
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = capture_ga->ga_data;
+
+ capture_ga = save_capture_ga;
+}
+
+/// "exepath()" function
+static void f_exepath(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *arg = tv_get_string(&argvars[0]);
+ char *path = NULL;
+
+ (void)os_can_exe(arg, &path, true);
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = (char_u *)path;
+}
+
+/*
+ * "exists()" function
+ */
+static void f_exists(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int n = false;
+ int len = 0;
+
+ const char *p = tv_get_string(&argvars[0]);
+ if (*p == '$') { // Environment variable.
+ // First try "normal" environment variables (fast).
+ if (os_env_exists(p + 1)) {
+ n = true;
+ } else {
+ // Try expanding things like $VIM and ${HOME}.
+ char_u *const exp = expand_env_save((char_u *)p);
+ if (exp != NULL && *exp != '$') {
+ n = true;
+ }
+ xfree(exp);
+ }
+ } else if (*p == '&' || *p == '+') { // Option.
+ n = (get_option_tv(&p, NULL, true) == OK);
+ if (*skipwhite((const char_u *)p) != NUL) {
+ n = false; // Trailing garbage.
+ }
+ } else if (*p == '*') { // Internal or user defined function.
+ n = function_exists(p + 1, false);
+ } else if (*p == ':') {
+ n = cmd_exists(p + 1);
+ } else if (*p == '#') {
+ if (p[1] == '#') {
+ n = autocmd_supported(p + 2);
+ } else {
+ n = au_exists(p + 1);
+ }
+ } else { // Internal variable.
+ typval_T tv;
+
+ // get_name_len() takes care of expanding curly braces
+ const char *name = p;
+ char *tofree;
+ len = get_name_len((const char **)&p, &tofree, true, false);
+ if (len > 0) {
+ if (tofree != NULL) {
+ name = tofree;
+ }
+ n = (get_var_tv(name, len, &tv, NULL, false, true) == OK);
+ if (n) {
+ // Handle d.key, l[idx], f(expr).
+ n = (handle_subscript(&p, &tv, true, false) == OK);
+ if (n) {
+ tv_clear(&tv);
+ }
+ }
+ }
+ if (*p != NUL)
+ n = FALSE;
+
+ xfree(tofree);
+ }
+
+ rettv->vval.v_number = n;
+}
+
+/*
+ * "expand()" function
+ */
+static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ size_t len;
+ char_u *errormsg;
+ int options = WILD_SILENT|WILD_USE_NL|WILD_LIST_NOTFOUND;
+ expand_T xpc;
+ bool error = false;
+ char_u *result;
+
+ rettv->v_type = VAR_STRING;
+ if (argvars[1].v_type != VAR_UNKNOWN
+ && argvars[2].v_type != VAR_UNKNOWN
+ && tv_get_number_chk(&argvars[2], &error)
+ && !error) {
+ tv_list_set_ret(rettv, NULL);
+ }
+
+ const char *s = tv_get_string(&argvars[0]);
+ if (*s == '%' || *s == '#' || *s == '<') {
+ emsg_off++;
+ result = eval_vars((char_u *)s, (char_u *)s, &len, NULL, &errormsg, NULL);
+ emsg_off--;
+ if (rettv->v_type == VAR_LIST) {
+ tv_list_alloc_ret(rettv, (result != NULL));
+ if (result != NULL) {
+ tv_list_append_string(rettv->vval.v_list, (const char *)result, -1);
+ }
+ XFREE_CLEAR(result);
+ } else {
+ rettv->vval.v_string = result;
+ }
+ } else {
+ // When the optional second argument is non-zero, don't remove matches
+ // for 'wildignore' and don't put matches for 'suffixes' at the end.
+ if (argvars[1].v_type != VAR_UNKNOWN
+ && tv_get_number_chk(&argvars[1], &error)) {
+ options |= WILD_KEEP_ALL;
+ }
+ if (!error) {
+ ExpandInit(&xpc);
+ xpc.xp_context = EXPAND_FILES;
+ if (p_wic) {
+ options += WILD_ICASE;
+ }
+ if (rettv->v_type == VAR_STRING) {
+ rettv->vval.v_string = ExpandOne(&xpc, (char_u *)s, NULL, options,
+ WILD_ALL);
+ } else {
+ ExpandOne(&xpc, (char_u *)s, NULL, options, WILD_ALL_KEEP);
+ tv_list_alloc_ret(rettv, xpc.xp_numfiles);
+ for (int i = 0; i < xpc.xp_numfiles; i++) {
+ tv_list_append_string(rettv->vval.v_list,
+ (const char *)xpc.xp_files[i], -1);
+ }
+ ExpandCleanup(&xpc);
+ }
+ } else {
+ rettv->vval.v_string = NULL;
+ }
+ }
+}
+
+
+/// "menu_get(path [, modes])" function
+static void f_menu_get(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ tv_list_alloc_ret(rettv, kListLenMayKnow);
+ int modes = MENU_ALL_MODES;
+ if (argvars[1].v_type == VAR_STRING) {
+ const char_u *const strmodes = (char_u *)tv_get_string(&argvars[1]);
+ modes = get_menu_cmd_modes(strmodes, false, NULL, NULL);
+ }
+ menu_get((char_u *)tv_get_string(&argvars[0]), modes, rettv->vval.v_list);
+}
+
+// "expandcmd()" function
+// Expand all the special characters in a command string.
+static void f_expandcmd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char_u *errormsg = NULL;
+
+ rettv->v_type = VAR_STRING;
+ char_u *cmdstr = (char_u *)xstrdup(tv_get_string(&argvars[0]));
+
+ exarg_T eap = {
+ .cmd = cmdstr,
+ .arg = cmdstr,
+ .usefilter = false,
+ .nextcmd = NULL,
+ .cmdidx = CMD_USER,
+ };
+ eap.argt |= NOSPC;
+
+ expand_filename(&eap, &cmdstr, &errormsg);
+ if (errormsg != NULL && *errormsg != NUL) {
+ EMSG(errormsg);
+ }
+ rettv->vval.v_string = cmdstr;
+}
+
+/*
+ * "extend(list, list [, idx])" function
+ * "extend(dict, dict [, action])" function
+ */
+static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *const arg_errmsg = N_("extend() argument");
+
+ if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_LIST) {
+ long before;
+ bool error = false;
+
+ list_T *const l1 = argvars[0].vval.v_list;
+ list_T *const l2 = argvars[1].vval.v_list;
+ if (!tv_check_lock(tv_list_locked(l1), arg_errmsg, TV_TRANSLATE)) {
+ listitem_T *item;
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ before = (long)tv_get_number_chk(&argvars[2], &error);
+ if (error) {
+ return; // Type error; errmsg already given.
+ }
+
+ if (before == tv_list_len(l1)) {
+ item = NULL;
+ } else {
+ item = tv_list_find(l1, before);
+ if (item == NULL) {
+ EMSGN(_(e_listidx), before);
+ return;
+ }
+ }
+ } else {
+ item = NULL;
+ }
+ tv_list_extend(l1, l2, item);
+
+ tv_copy(&argvars[0], rettv);
+ }
+ } else if (argvars[0].v_type == VAR_DICT && argvars[1].v_type ==
+ VAR_DICT) {
+ dict_T *const d1 = argvars[0].vval.v_dict;
+ dict_T *const d2 = argvars[1].vval.v_dict;
+ if (d1 == NULL) {
+ const bool locked = tv_check_lock(VAR_FIXED, arg_errmsg, TV_TRANSLATE);
+ (void)locked;
+ assert(locked == true);
+ } else if (d2 == NULL) {
+ // Do nothing
+ tv_copy(&argvars[0], rettv);
+ } else if (!tv_check_lock(d1->dv_lock, arg_errmsg, TV_TRANSLATE)) {
+ const char *action = "force";
+ // Check the third argument.
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ const char *const av[] = { "keep", "force", "error" };
+
+ action = tv_get_string_chk(&argvars[2]);
+ if (action == NULL) {
+ return; // Type error; error message already given.
+ }
+ size_t i;
+ for (i = 0; i < ARRAY_SIZE(av); i++) {
+ if (strcmp(action, av[i]) == 0) {
+ break;
+ }
+ }
+ if (i == 3) {
+ EMSG2(_(e_invarg2), action);
+ return;
+ }
+ }
+
+ tv_dict_extend(d1, d2, action);
+
+ tv_copy(&argvars[0], rettv);
+ }
+ } else {
+ EMSG2(_(e_listdictarg), "extend()");
+ }
+}
+
+/*
+ * "feedkeys()" function
+ */
+static void f_feedkeys(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ // This is not allowed in the sandbox. If the commands would still be
+ // executed in the sandbox it would be OK, but it probably happens later,
+ // when "sandbox" is no longer set.
+ if (check_secure()) {
+ return;
+ }
+
+ const char *const keys = tv_get_string(&argvars[0]);
+ char nbuf[NUMBUFLEN];
+ const char *flags = NULL;
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ flags = tv_get_string_buf(&argvars[1], nbuf);
+ }
+
+ nvim_feedkeys(cstr_as_string((char *)keys),
+ cstr_as_string((char *)flags), true);
+}
+
+/// "filereadable()" function
+static void f_filereadable(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *const p = tv_get_string(&argvars[0]);
+ rettv->vval.v_number =
+ (*p && !os_isdir((const char_u *)p) && os_file_is_readable(p));
+}
+
+/*
+ * Return 0 for not writable, 1 for writable file, 2 for a dir which we have
+ * rights to write into.
+ */
+static void f_filewritable(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *filename = tv_get_string(&argvars[0]);
+ rettv->vval.v_number = os_file_is_writable(filename);
+}
+
+
+static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what)
+{
+ char_u *fresult = NULL;
+ char_u *path = *curbuf->b_p_path == NUL ? p_path : curbuf->b_p_path;
+ int count = 1;
+ bool first = true;
+ bool error = false;
+
+ rettv->vval.v_string = NULL;
+ rettv->v_type = VAR_STRING;
+
+ const char *fname = tv_get_string(&argvars[0]);
+
+ char pathbuf[NUMBUFLEN];
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ const char *p = tv_get_string_buf_chk(&argvars[1], pathbuf);
+ if (p == NULL) {
+ error = true;
+ } else {
+ if (*p != NUL) {
+ path = (char_u *)p;
+ }
+
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ count = tv_get_number_chk(&argvars[2], &error);
+ }
+ }
+ }
+
+ if (count < 0) {
+ tv_list_alloc_ret(rettv, kListLenUnknown);
+ }
+
+ if (*fname != NUL && !error) {
+ do {
+ if (rettv->v_type == VAR_STRING || rettv->v_type == VAR_LIST)
+ xfree(fresult);
+ fresult = find_file_in_path_option(first ? (char_u *)fname : NULL,
+ first ? strlen(fname) : 0,
+ 0, first, path,
+ find_what, curbuf->b_ffname,
+ (find_what == FINDFILE_DIR
+ ? (char_u *)""
+ : curbuf->b_p_sua));
+ first = false;
+
+ if (fresult != NULL && rettv->v_type == VAR_LIST) {
+ tv_list_append_string(rettv->vval.v_list, (const char *)fresult, -1);
+ }
+ } while ((rettv->v_type == VAR_LIST || --count > 0) && fresult != NULL);
+ }
+
+ if (rettv->v_type == VAR_STRING)
+ rettv->vval.v_string = fresult;
+}
+
+
+/*
+ * "filter()" function
+ */
+static void f_filter(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ filter_map(argvars, rettv, FALSE);
+}
+
+/*
+ * "finddir({fname}[, {path}[, {count}]])" function
+ */
+static void f_finddir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ findfilendir(argvars, rettv, FINDFILE_DIR);
+}
+
+/*
+ * "findfile({fname}[, {path}[, {count}]])" function
+ */
+static void f_findfile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ findfilendir(argvars, rettv, FINDFILE_FILE);
+}
+
+/*
+ * "float2nr({float})" function
+ */
+static void f_float2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ float_T f;
+
+ if (tv_get_float_chk(argvars, &f)) {
+ if (f <= -VARNUMBER_MAX + DBL_EPSILON) {
+ rettv->vval.v_number = -VARNUMBER_MAX;
+ } else if (f >= VARNUMBER_MAX - DBL_EPSILON) {
+ rettv->vval.v_number = VARNUMBER_MAX;
+ } else {
+ rettv->vval.v_number = (varnumber_T)f;
+ }
+ }
+}
+
+/*
+ * "fmod()" function
+ */
+static void f_fmod(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ float_T fx;
+ float_T fy;
+
+ rettv->v_type = VAR_FLOAT;
+ if (tv_get_float_chk(argvars, &fx) && tv_get_float_chk(&argvars[1], &fy)) {
+ rettv->vval.v_float = fmod(fx, fy);
+ } else {
+ rettv->vval.v_float = 0.0;
+ }
+}
+
+/*
+ * "fnameescape({string})" function
+ */
+static void f_fnameescape(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_string = (char_u *)vim_strsave_fnameescape(
+ tv_get_string(&argvars[0]), false);
+ rettv->v_type = VAR_STRING;
+}
+
+/*
+ * "fnamemodify({fname}, {mods})" function
+ */
+static void f_fnamemodify(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char_u *fbuf = NULL;
+ size_t len;
+ char buf[NUMBUFLEN];
+ const char *fname = tv_get_string_chk(&argvars[0]);
+ const char *const mods = tv_get_string_buf_chk(&argvars[1], buf);
+ if (fname == NULL || mods == NULL) {
+ fname = NULL;
+ } else {
+ len = strlen(fname);
+ size_t usedlen = 0;
+ (void)modify_fname((char_u *)mods, false, &usedlen,
+ (char_u **)&fname, &fbuf, &len);
+ }
+
+ rettv->v_type = VAR_STRING;
+ if (fname == NULL) {
+ rettv->vval.v_string = NULL;
+ } else {
+ rettv->vval.v_string = (char_u *)xmemdupz(fname, len);
+ }
+ xfree(fbuf);
+}
+
+
+/*
+ * "foldclosed()" function
+ */
+static void foldclosed_both(typval_T *argvars, typval_T *rettv, int end)
+{
+ const linenr_T lnum = tv_get_lnum(argvars);
+ if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) {
+ linenr_T first;
+ linenr_T last;
+ if (hasFoldingWin(curwin, lnum, &first, &last, false, NULL)) {
+ if (end) {
+ rettv->vval.v_number = (varnumber_T)last;
+ } else {
+ rettv->vval.v_number = (varnumber_T)first;
+ }
+ return;
+ }
+ }
+ rettv->vval.v_number = -1;
+}
+
+/*
+ * "foldclosed()" function
+ */
+static void f_foldclosed(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ foldclosed_both(argvars, rettv, FALSE);
+}
+
+/*
+ * "foldclosedend()" function
+ */
+static void f_foldclosedend(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ foldclosed_both(argvars, rettv, TRUE);
+}
+
+/*
+ * "foldlevel()" function
+ */
+static void f_foldlevel(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const linenr_T lnum = tv_get_lnum(argvars);
+ if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) {
+ rettv->vval.v_number = foldLevel(lnum);
+ }
+}
+
+/*
+ * "foldtext()" function
+ */
+static void f_foldtext(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ linenr_T foldstart;
+ linenr_T foldend;
+ char_u *dashes;
+ linenr_T lnum;
+ char_u *s;
+ char_u *r;
+ int len;
+ char *txt;
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+
+ foldstart = (linenr_T)get_vim_var_nr(VV_FOLDSTART);
+ foldend = (linenr_T)get_vim_var_nr(VV_FOLDEND);
+ dashes = get_vim_var_str(VV_FOLDDASHES);
+ if (foldstart > 0 && foldend <= curbuf->b_ml.ml_line_count) {
+ // Find first non-empty line in the fold.
+ for (lnum = foldstart; lnum < foldend; lnum++) {
+ if (!linewhite(lnum)) {
+ break;
+ }
+ }
+
+ // Find interesting text in this line.
+ s = skipwhite(ml_get(lnum));
+ // skip C comment-start
+ if (s[0] == '/' && (s[1] == '*' || s[1] == '/')) {
+ s = skipwhite(s + 2);
+ if (*skipwhite(s) == NUL && lnum + 1 < foldend) {
+ s = skipwhite(ml_get(lnum + 1));
+ if (*s == '*')
+ s = skipwhite(s + 1);
+ }
+ }
+ unsigned long count = (unsigned long)(foldend - foldstart + 1);
+ txt = NGETTEXT("+-%s%3ld line: ", "+-%s%3ld lines: ", count);
+ r = xmalloc(STRLEN(txt)
+ + STRLEN(dashes) // for %s
+ + 20 // for %3ld
+ + STRLEN(s)); // concatenated
+ sprintf((char *)r, txt, dashes, count);
+ len = (int)STRLEN(r);
+ STRCAT(r, s);
+ // remove 'foldmarker' and 'commentstring'
+ foldtext_cleanup(r + len);
+ rettv->vval.v_string = r;
+ }
+}
+
+/*
+ * "foldtextresult(lnum)" function
+ */
+static void f_foldtextresult(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char_u *text;
+ char_u buf[FOLD_TEXT_LEN];
+ foldinfo_T foldinfo;
+ int fold_count;
+ static bool entered = false;
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+ if (entered) {
+ return; // reject recursive use
+ }
+ entered = true;
+ linenr_T lnum = tv_get_lnum(argvars);
+ // Treat illegal types and illegal string values for {lnum} the same.
+ if (lnum < 0) {
+ lnum = 0;
+ }
+ fold_count = foldedCount(curwin, lnum, &foldinfo);
+ if (fold_count > 0) {
+ text = get_foldtext(curwin, lnum, lnum + fold_count - 1, &foldinfo, buf);
+ if (text == buf) {
+ text = vim_strsave(text);
+ }
+ rettv->vval.v_string = text;
+ }
+
+ entered = false;
+}
+
+/*
+ * "foreground()" function
+ */
+static void f_foreground(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+}
+
+static void f_funcref(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ common_function(argvars, rettv, true, fptr);
+}
+
+static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ common_function(argvars, rettv, false, fptr);
+}
+
+/// "garbagecollect()" function
+static void f_garbagecollect(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ // This is postponed until we are back at the toplevel, because we may be
+ // using Lists and Dicts internally. E.g.: ":echo [garbagecollect()]".
+ want_garbage_collect = true;
+
+ if (argvars[0].v_type != VAR_UNKNOWN && tv_get_number(&argvars[0]) == 1) {
+ garbage_collect_at_exit = true;
+ }
+}
+
+/*
+ * "get()" function
+ */
+static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ listitem_T *li;
+ list_T *l;
+ dictitem_T *di;
+ dict_T *d;
+ typval_T *tv = NULL;
+ bool what_is_dict = false;
+
+ if (argvars[0].v_type == VAR_LIST) {
+ if ((l = argvars[0].vval.v_list) != NULL) {
+ bool error = false;
+
+ li = tv_list_find(l, tv_get_number_chk(&argvars[1], &error));
+ if (!error && li != NULL) {
+ tv = TV_LIST_ITEM_TV(li);
+ }
+ }
+ } else if (argvars[0].v_type == VAR_DICT) {
+ if ((d = argvars[0].vval.v_dict) != NULL) {
+ di = tv_dict_find(d, tv_get_string(&argvars[1]), -1);
+ if (di != NULL) {
+ tv = &di->di_tv;
+ }
+ }
+ } else if (tv_is_func(argvars[0])) {
+ partial_T *pt;
+ partial_T fref_pt;
+
+ if (argvars[0].v_type == VAR_PARTIAL) {
+ pt = argvars[0].vval.v_partial;
+ } else {
+ memset(&fref_pt, 0, sizeof(fref_pt));
+ fref_pt.pt_name = argvars[0].vval.v_string;
+ pt = &fref_pt;
+ }
+
+ if (pt != NULL) {
+ const char *const what = tv_get_string(&argvars[1]);
+
+ if (strcmp(what, "func") == 0 || strcmp(what, "name") == 0) {
+ rettv->v_type = (*what == 'f' ? VAR_FUNC : VAR_STRING);
+ const char *const n = (const char *)partial_name(pt);
+ assert(n != NULL);
+ rettv->vval.v_string = (char_u *)xstrdup(n);
+ if (rettv->v_type == VAR_FUNC) {
+ func_ref(rettv->vval.v_string);
+ }
+ } else if (strcmp(what, "dict") == 0) {
+ what_is_dict = true;
+ if (pt->pt_dict != NULL) {
+ tv_dict_set_ret(rettv, pt->pt_dict);
+ }
+ } else if (strcmp(what, "args") == 0) {
+ rettv->v_type = VAR_LIST;
+ if (tv_list_alloc_ret(rettv, pt->pt_argc) != NULL) {
+ for (int i = 0; i < pt->pt_argc; i++) {
+ tv_list_append_tv(rettv->vval.v_list, &pt->pt_argv[i]);
+ }
+ }
+ } else {
+ EMSG2(_(e_invarg2), what);
+ }
+
+ // When {what} == "dict" and pt->pt_dict == NULL, evaluate the
+ // third argument
+ if (!what_is_dict) {
+ return;
+ }
+ }
+ } else {
+ EMSG2(_(e_listdictarg), "get()");
+ }
+
+ if (tv == NULL) {
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ tv_copy(&argvars[2], rettv);
+ }
+ } else {
+ tv_copy(tv, rettv);
+ }
+}
+
+/// "getbufinfo()" function
+static void f_getbufinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ buf_T *argbuf = NULL;
+ bool filtered = false;
+ bool sel_buflisted = false;
+ bool sel_bufloaded = false;
+ bool sel_bufmodified = false;
+
+ tv_list_alloc_ret(rettv, kListLenMayKnow);
+
+ // List of all the buffers or selected buffers
+ if (argvars[0].v_type == VAR_DICT) {
+ dict_T *sel_d = argvars[0].vval.v_dict;
+
+ if (sel_d != NULL) {
+ dictitem_T *di;
+
+ filtered = true;
+
+ di = tv_dict_find(sel_d, S_LEN("buflisted"));
+ if (di != NULL && tv_get_number(&di->di_tv)) {
+ sel_buflisted = true;
+ }
+
+ di = tv_dict_find(sel_d, S_LEN("bufloaded"));
+ if (di != NULL && tv_get_number(&di->di_tv)) {
+ sel_bufloaded = true;
+ }
+ di = tv_dict_find(sel_d, S_LEN("bufmodified"));
+ if (di != NULL && tv_get_number(&di->di_tv)) {
+ sel_bufmodified = true;
+ }
+ }
+ } else if (argvars[0].v_type != VAR_UNKNOWN) {
+ // Information about one buffer. Argument specifies the buffer
+ if (tv_check_num(&argvars[0])) { // issue errmsg if type error
+ emsg_off++;
+ argbuf = tv_get_buf(&argvars[0], false);
+ emsg_off--;
+ if (argbuf == NULL) {
+ return;
+ }
+ }
+ }
+
+ // Return information about all the buffers or a specified buffer
+ FOR_ALL_BUFFERS(buf) {
+ if (argbuf != NULL && argbuf != buf) {
+ continue;
+ }
+ if (filtered && ((sel_bufloaded && buf->b_ml.ml_mfp == NULL)
+ || (sel_buflisted && !buf->b_p_bl)
+ || (sel_bufmodified && !buf->b_changed))) {
+ continue;
+ }
+
+ dict_T *const d = get_buffer_info(buf);
+ tv_list_append_dict(rettv->vval.v_list, d);
+ if (argbuf != NULL) {
+ return;
+ }
+ }
+}
+
+/*
+ * Get line or list of lines from buffer "buf" into "rettv".
+ * Return a range (from start to end) of lines in rettv from the specified
+ * buffer.
+ * If 'retlist' is TRUE, then the lines are returned as a Vim List.
+ */
+static void get_buffer_lines(buf_T *buf,
+ linenr_T start,
+ linenr_T end,
+ int retlist,
+ typval_T *rettv)
+{
+ rettv->v_type = (retlist ? VAR_LIST : VAR_STRING);
+ rettv->vval.v_string = NULL;
+
+ if (buf == NULL || buf->b_ml.ml_mfp == NULL || start < 0 || end < start) {
+ if (retlist) {
+ tv_list_alloc_ret(rettv, 0);
+ }
+ return;
+ }
+
+ if (retlist) {
+ if (start < 1) {
+ start = 1;
+ }
+ if (end > buf->b_ml.ml_line_count) {
+ end = buf->b_ml.ml_line_count;
+ }
+ tv_list_alloc_ret(rettv, end - start + 1);
+ while (start <= end) {
+ tv_list_append_string(rettv->vval.v_list,
+ (const char *)ml_get_buf(buf, start++, false), -1);
+ }
+ } else {
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = ((start >= 1 && start <= buf->b_ml.ml_line_count)
+ ? vim_strsave(ml_get_buf(buf, start, false))
+ : NULL);
+ }
+}
+
+/*
+ * "getbufline()" function
+ */
+static void f_getbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ buf_T *buf = NULL;
+
+ if (tv_check_str_or_nr(&argvars[0])) {
+ emsg_off++;
+ buf = tv_get_buf(&argvars[0], false);
+ emsg_off--;
+ }
+
+ const linenr_T lnum = tv_get_lnum_buf(&argvars[1], buf);
+ const linenr_T end = (argvars[2].v_type == VAR_UNKNOWN
+ ? lnum
+ : tv_get_lnum_buf(&argvars[2], buf));
+
+ get_buffer_lines(buf, lnum, end, true, rettv);
+}
+
+/*
+ * "getbufvar()" function
+ */
+static void f_getbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ bool done = false;
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+
+ if (!tv_check_str_or_nr(&argvars[0])) {
+ goto f_getbufvar_end;
+ }
+
+ const char *varname = tv_get_string_chk(&argvars[1]);
+ emsg_off++;
+ buf_T *const buf = tv_get_buf(&argvars[0], false);
+
+ if (buf != NULL && varname != NULL) {
+ if (*varname == '&') { // buffer-local-option
+ buf_T *const save_curbuf = curbuf;
+
+ // set curbuf to be our buf, temporarily
+ curbuf = buf;
+
+ if (varname[1] == NUL) {
+ // get all buffer-local options in a dict
+ dict_T *opts = get_winbuf_options(true);
+
+ if (opts != NULL) {
+ tv_dict_set_ret(rettv, opts);
+ done = true;
+ }
+ } else if (get_option_tv(&varname, rettv, true) == OK) {
+ // buffer-local-option
+ done = true;
+ }
+
+ // restore previous notion of curbuf
+ curbuf = save_curbuf;
+ } else {
+ // Look up the variable.
+ // Let getbufvar({nr}, "") return the "b:" dictionary.
+ dictitem_T *const v = *varname == NUL
+ ? (dictitem_T *)&buf->b_bufvar
+ : find_var_in_ht(&buf->b_vars->dv_hashtab, 'b',
+ varname, strlen(varname), false);
+ if (v != NULL) {
+ tv_copy(&v->di_tv, rettv);
+ done = true;
+ }
+ }
+ }
+ emsg_off--;
+
+f_getbufvar_end:
+ if (!done && argvars[2].v_type != VAR_UNKNOWN) {
+ // use the default value
+ tv_copy(&argvars[2], rettv);
+ }
+}
+
+// "getchangelist()" function
+static void f_getchangelist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ tv_list_alloc_ret(rettv, 2);
+ vim_ignored = tv_get_number(&argvars[0]); // issue errmsg if type error
+ emsg_off++;
+ const buf_T *const buf = tv_get_buf(&argvars[0], false);
+ emsg_off--;
+ if (buf == NULL) {
+ return;
+ }
+
+ list_T *const l = tv_list_alloc(buf->b_changelistlen);
+ tv_list_append_list(rettv->vval.v_list, l);
+ // The current window change list index tracks only the position in the
+ // current buffer change list. For other buffers, use the change list
+ // length as the current index.
+ tv_list_append_number(rettv->vval.v_list,
+ (buf == curwin->w_buffer)
+ ? curwin->w_changelistidx
+ : buf->b_changelistlen);
+
+ for (int i = 0; i < buf->b_changelistlen; i++) {
+ if (buf->b_changelist[i].mark.lnum == 0) {
+ continue;
+ }
+ dict_T *const d = tv_dict_alloc();
+ tv_list_append_dict(l, d);
+ tv_dict_add_nr(d, S_LEN("lnum"), buf->b_changelist[i].mark.lnum);
+ tv_dict_add_nr(d, S_LEN("col"), buf->b_changelist[i].mark.col);
+ tv_dict_add_nr(d, S_LEN("coladd"), buf->b_changelist[i].mark.coladd);
+ }
+}
+
+/*
+ * "getchar()" function
+ */
+static void f_getchar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ varnumber_T n;
+ bool error = false;
+
+ no_mapping++;
+ for (;; ) {
+ // Position the cursor. Needed after a message that ends in a space,
+ // or if event processing caused a redraw.
+ ui_cursor_goto(msg_row, msg_col);
+
+ if (argvars[0].v_type == VAR_UNKNOWN) {
+ // getchar(): blocking wait.
+ if (!(char_avail() || using_script() || input_available())) {
+ (void)os_inchar(NULL, 0, -1, 0, main_loop.events);
+ if (!multiqueue_empty(main_loop.events)) {
+ multiqueue_process_events(main_loop.events);
+ continue;
+ }
+ }
+ n = safe_vgetc();
+ } else if (tv_get_number_chk(&argvars[0], &error) == 1) {
+ // getchar(1): only check if char avail
+ n = vpeekc_any();
+ } else if (error || vpeekc_any() == NUL) {
+ // illegal argument or getchar(0) and no char avail: return zero
+ n = 0;
+ } else {
+ // getchar(0) and char avail: return char
+ n = safe_vgetc();
+ }
+
+ if (n == K_IGNORE) {
+ continue;
+ }
+ break;
+ }
+ no_mapping--;
+
+ set_vim_var_nr(VV_MOUSE_WIN, 0);
+ set_vim_var_nr(VV_MOUSE_WINID, 0);
+ set_vim_var_nr(VV_MOUSE_LNUM, 0);
+ set_vim_var_nr(VV_MOUSE_COL, 0);
+
+ rettv->vval.v_number = n;
+ if (IS_SPECIAL(n) || mod_mask != 0) {
+ char_u temp[10]; // modifier: 3, mbyte-char: 6, NUL: 1
+ int i = 0;
+
+ // Turn a special key into three bytes, plus modifier.
+ if (mod_mask != 0) {
+ temp[i++] = K_SPECIAL;
+ temp[i++] = KS_MODIFIER;
+ temp[i++] = mod_mask;
+ }
+ if (IS_SPECIAL(n)) {
+ temp[i++] = K_SPECIAL;
+ temp[i++] = K_SECOND(n);
+ temp[i++] = K_THIRD(n);
+ } else {
+ i += utf_char2bytes(n, temp + i);
+ }
+ temp[i++] = NUL;
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = vim_strsave(temp);
+
+ if (is_mouse_key(n)) {
+ int row = mouse_row;
+ int col = mouse_col;
+ int grid = mouse_grid;
+ win_T *win;
+ linenr_T lnum;
+ win_T *wp;
+ int winnr = 1;
+
+ if (row >= 0 && col >= 0) {
+ /* Find the window at the mouse coordinates and compute the
+ * text position. */
+ win = mouse_find_win(&grid, &row, &col);
+ if (win == NULL) {
+ return;
+ }
+ (void)mouse_comp_pos(win, &row, &col, &lnum);
+ for (wp = firstwin; wp != win; wp = wp->w_next)
+ ++winnr;
+ set_vim_var_nr(VV_MOUSE_WIN, winnr);
+ set_vim_var_nr(VV_MOUSE_WINID, wp->handle);
+ set_vim_var_nr(VV_MOUSE_LNUM, lnum);
+ set_vim_var_nr(VV_MOUSE_COL, col + 1);
+ }
+ }
+ }
+}
+
+/*
+ * "getcharmod()" function
+ */
+static void f_getcharmod(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = mod_mask;
+}
+
+/*
+ * "getcharsearch()" function
+ */
+static void f_getcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ tv_dict_alloc_ret(rettv);
+
+ dict_T *dict = rettv->vval.v_dict;
+
+ tv_dict_add_str(dict, S_LEN("char"), last_csearch());
+ tv_dict_add_nr(dict, S_LEN("forward"), last_csearch_forward());
+ tv_dict_add_nr(dict, S_LEN("until"), last_csearch_until());
+}
+
+/*
+ * "getcmdline()" function
+ */
+static void f_getcmdline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = get_cmdline_str();
+}
+
+/*
+ * "getcmdpos()" function
+ */
+static void f_getcmdpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = get_cmdline_pos() + 1;
+}
+
+/*
+ * "getcmdtype()" function
+ */
+static void f_getcmdtype(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = xmallocz(1);
+ rettv->vval.v_string[0] = get_cmdline_type();
+}
+
+/*
+ * "getcmdwintype()" function
+ */
+static void f_getcmdwintype(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+ rettv->vval.v_string = xmallocz(1);
+ rettv->vval.v_string[0] = cmdwin_type;
+}
+
+// "getcompletion()" function
+static void f_getcompletion(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char_u *pat;
+ expand_T xpc;
+ bool filtered = false;
+ int options = WILD_SILENT | WILD_USE_NL | WILD_ADD_SLASH
+ | WILD_NO_BEEP;
+
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ filtered = (bool)tv_get_number_chk(&argvars[2], NULL);
+ }
+
+ if (p_wic) {
+ options |= WILD_ICASE;
+ }
+
+ // For filtered results, 'wildignore' is used
+ if (!filtered) {
+ options |= WILD_KEEP_ALL;
+ }
+
+ if (argvars[0].v_type != VAR_STRING || argvars[1].v_type != VAR_STRING) {
+ EMSG(_(e_invarg));
+ return;
+ }
+
+ if (strcmp(tv_get_string(&argvars[1]), "cmdline") == 0) {
+ set_one_cmd_context(&xpc, tv_get_string(&argvars[0]));
+ xpc.xp_pattern_len = STRLEN(xpc.xp_pattern);
+ goto theend;
+ }
+
+ ExpandInit(&xpc);
+ xpc.xp_pattern = (char_u *)tv_get_string(&argvars[0]);
+ xpc.xp_pattern_len = STRLEN(xpc.xp_pattern);
+ xpc.xp_context = cmdcomplete_str_to_type(
+ (char_u *)tv_get_string(&argvars[1]));
+ if (xpc.xp_context == EXPAND_NOTHING) {
+ EMSG2(_(e_invarg2), argvars[1].vval.v_string);
+ return;
+ }
+
+ if (xpc.xp_context == EXPAND_MENUS) {
+ set_context_in_menu_cmd(&xpc, (char_u *)"menu", xpc.xp_pattern, false);
+ xpc.xp_pattern_len = STRLEN(xpc.xp_pattern);
+ }
+
+ if (xpc.xp_context == EXPAND_CSCOPE) {
+ set_context_in_cscope_cmd(&xpc, (const char *)xpc.xp_pattern, CMD_cscope);
+ xpc.xp_pattern_len = STRLEN(xpc.xp_pattern);
+ }
+
+ if (xpc.xp_context == EXPAND_SIGN) {
+ set_context_in_sign_cmd(&xpc, xpc.xp_pattern);
+ xpc.xp_pattern_len = STRLEN(xpc.xp_pattern);
+ }
+
+theend:
+ pat = addstar(xpc.xp_pattern, xpc.xp_pattern_len, xpc.xp_context);
+ ExpandOne(&xpc, pat, NULL, options, WILD_ALL_KEEP);
+ tv_list_alloc_ret(rettv, xpc.xp_numfiles);
+
+ for (int i = 0; i < xpc.xp_numfiles; i++) {
+ tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i],
+ -1);
+ }
+ xfree(pat);
+ ExpandCleanup(&xpc);
+}
+
+/// `getcwd([{win}[, {tab}]])` function
+///
+/// Every scope not specified implies the currently selected scope object.
+///
+/// @pre The arguments must be of type number.
+/// @pre There may not be more than two arguments.
+/// @pre An argument may not be -1 if preceding arguments are not all -1.
+///
+/// @post The return value will be a string.
+static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ // Possible scope of working directory to return.
+ CdScope scope = kCdScopeInvalid;
+
+ // Numbers of the scope objects (window, tab) we want the working directory
+ // of. A `-1` means to skip this scope, a `0` means the current object.
+ int scope_number[] = {
+ [kCdScopeWindow] = 0, // Number of window to look at.
+ [kCdScopeTab ] = 0, // Number of tab to look at.
+ };
+
+ char_u *cwd = NULL; // Current working directory to print
+ char_u *from = NULL; // The original string to copy
+
+ tabpage_T *tp = curtab; // The tabpage to look at.
+ win_T *win = curwin; // The window to look at.
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+
+ // Pre-conditions and scope extraction together
+ for (int i = MIN_CD_SCOPE; i < MAX_CD_SCOPE; i++) {
+ // If there is no argument there are no more scopes after it, break out.
+ if (argvars[i].v_type == VAR_UNKNOWN) {
+ break;
+ }
+ if (argvars[i].v_type != VAR_NUMBER) {
+ EMSG(_(e_invarg));
+ return;
+ }
+ scope_number[i] = argvars[i].vval.v_number;
+ // It is an error for the scope number to be less than `-1`.
+ if (scope_number[i] < -1) {
+ EMSG(_(e_invarg));
+ return;
+ }
+ // Use the narrowest scope the user requested
+ if (scope_number[i] >= 0 && scope == kCdScopeInvalid) {
+ // The scope is the current iteration step.
+ scope = i;
+ } else if (scope_number[i] < 0) {
+ scope = i + 1;
+ }
+ }
+
+ // If the user didn't specify anything, default to window scope
+ if (scope == kCdScopeInvalid) {
+ scope = MIN_CD_SCOPE;
+ }
+
+ // Find the tabpage by number
+ if (scope_number[kCdScopeTab] > 0) {
+ tp = find_tabpage(scope_number[kCdScopeTab]);
+ if (!tp) {
+ EMSG(_("E5000: Cannot find tab number."));
+ return;
+ }
+ }
+
+ // Find the window in `tp` by number, `NULL` if none.
+ if (scope_number[kCdScopeWindow] >= 0) {
+ if (scope_number[kCdScopeTab] < 0) {
+ EMSG(_("E5001: Higher scope cannot be -1 if lower scope is >= 0."));
+ return;
+ }
+
+ if (scope_number[kCdScopeWindow] > 0) {
+ win = find_win_by_nr(&argvars[0], tp);
+ if (!win) {
+ EMSG(_("E5002: Cannot find window number."));
+ return;
+ }
+ }
+ }
+
+ cwd = xmalloc(MAXPATHL);
+
+ switch (scope) {
+ case kCdScopeWindow:
+ assert(win);
+ from = win->w_localdir;
+ if (from) {
+ break;
+ }
+ FALLTHROUGH;
+ case kCdScopeTab:
+ assert(tp);
+ from = tp->tp_localdir;
+ if (from) {
+ break;
+ }
+ FALLTHROUGH;
+ case kCdScopeGlobal:
+ if (globaldir) { // `globaldir` is not always set.
+ from = globaldir;
+ } else if (os_dirname(cwd, MAXPATHL) == FAIL) { // Get the OS CWD.
+ from = (char_u *)""; // Return empty string on failure.
+ }
+ break;
+ case kCdScopeInvalid: // We should never get here
+ assert(false);
+ }
+
+ if (from) {
+ xstrlcpy((char *)cwd, (char *)from, MAXPATHL);
+ }
+
+ rettv->vval.v_string = vim_strsave(cwd);
+#ifdef BACKSLASH_IN_FILENAME
+ slash_adjust(rettv->vval.v_string);
+#endif
+
+ xfree(cwd);
+}
+
+/*
+ * "getfontname()" function
+ */
+static void f_getfontname(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+}
+
+/*
+ * "getfperm({fname})" function
+ */
+static void f_getfperm(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char *perm = NULL;
+ char_u flags[] = "rwx";
+
+ const char *filename = tv_get_string(&argvars[0]);
+ int32_t file_perm = os_getperm(filename);
+ if (file_perm >= 0) {
+ perm = xstrdup("---------");
+ for (int i = 0; i < 9; i++) {
+ if (file_perm & (1 << (8 - i))) {
+ perm[i] = flags[i % 3];
+ }
+ }
+ }
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = (char_u *)perm;
+}
+
+/*
+ * "getfsize({fname})" function
+ */
+static void f_getfsize(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *fname = tv_get_string(&argvars[0]);
+
+ rettv->v_type = VAR_NUMBER;
+
+ FileInfo file_info;
+ if (os_fileinfo(fname, &file_info)) {
+ uint64_t filesize = os_fileinfo_size(&file_info);
+ if (os_isdir((const char_u *)fname)) {
+ rettv->vval.v_number = 0;
+ } else {
+ rettv->vval.v_number = (varnumber_T)filesize;
+
+ // non-perfect check for overflow
+ if ((uint64_t)rettv->vval.v_number != filesize) {
+ rettv->vval.v_number = -2;
+ }
+ }
+ } else {
+ rettv->vval.v_number = -1;
+ }
+}
+
+/*
+ * "getftime({fname})" function
+ */
+static void f_getftime(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *fname = tv_get_string(&argvars[0]);
+
+ FileInfo file_info;
+ if (os_fileinfo(fname, &file_info)) {
+ rettv->vval.v_number = (varnumber_T)file_info.stat.st_mtim.tv_sec;
+ } else {
+ rettv->vval.v_number = -1;
+ }
+}
+
+/*
+ * "getftype({fname})" function
+ */
+static void f_getftype(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char_u *type = NULL;
+ char *t;
+
+ const char *fname = tv_get_string(&argvars[0]);
+
+ rettv->v_type = VAR_STRING;
+ FileInfo file_info;
+ if (os_fileinfo_link(fname, &file_info)) {
+ uint64_t mode = file_info.stat.st_mode;
+#ifdef S_ISREG
+ if (S_ISREG(mode))
+ t = "file";
+ else if (S_ISDIR(mode))
+ t = "dir";
+# ifdef S_ISLNK
+ else if (S_ISLNK(mode))
+ t = "link";
+# endif
+# ifdef S_ISBLK
+ else if (S_ISBLK(mode))
+ t = "bdev";
+# endif
+# ifdef S_ISCHR
+ else if (S_ISCHR(mode))
+ t = "cdev";
+# endif
+# ifdef S_ISFIFO
+ else if (S_ISFIFO(mode))
+ t = "fifo";
+# endif
+# ifdef S_ISSOCK
+ else if (S_ISSOCK(mode))
+ t = "socket";
+# endif
+ else
+ t = "other";
+#else
+# ifdef S_IFMT
+ switch (mode & S_IFMT) {
+ case S_IFREG: t = "file"; break;
+ case S_IFDIR: t = "dir"; break;
+# ifdef S_IFLNK
+ case S_IFLNK: t = "link"; break;
+# endif
+# ifdef S_IFBLK
+ case S_IFBLK: t = "bdev"; break;
+# endif
+# ifdef S_IFCHR
+ case S_IFCHR: t = "cdev"; break;
+# endif
+# ifdef S_IFIFO
+ case S_IFIFO: t = "fifo"; break;
+# endif
+# ifdef S_IFSOCK
+ case S_IFSOCK: t = "socket"; break;
+# endif
+ default: t = "other";
+ }
+# else
+ if (os_isdir((const char_u *)fname)) {
+ t = "dir";
+ } else {
+ t = "file";
+ }
+# endif
+#endif
+ type = vim_strsave((char_u *)t);
+ }
+ rettv->vval.v_string = type;
+}
+
+// "getjumplist()" function
+static void f_getjumplist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ tv_list_alloc_ret(rettv, kListLenMayKnow);
+ win_T *const wp = find_tabwin(&argvars[0], &argvars[1]);
+ if (wp == NULL) {
+ return;
+ }
+
+ cleanup_jumplist(wp, true);
+
+ list_T *const l = tv_list_alloc(wp->w_jumplistlen);
+ tv_list_append_list(rettv->vval.v_list, l);
+ tv_list_append_number(rettv->vval.v_list, wp->w_jumplistidx);
+
+ for (int i = 0; i < wp->w_jumplistlen; i++) {
+ if (wp->w_jumplist[i].fmark.mark.lnum == 0) {
+ continue;
+ }
+ dict_T *const d = tv_dict_alloc();
+ tv_list_append_dict(l, d);
+ tv_dict_add_nr(d, S_LEN("lnum"), wp->w_jumplist[i].fmark.mark.lnum);
+ tv_dict_add_nr(d, S_LEN("col"), wp->w_jumplist[i].fmark.mark.col);
+ tv_dict_add_nr(d, S_LEN("coladd"), wp->w_jumplist[i].fmark.mark.coladd);
+ tv_dict_add_nr(d, S_LEN("bufnr"), wp->w_jumplist[i].fmark.fnum);
+ if (wp->w_jumplist[i].fname != NULL) {
+ tv_dict_add_str(d, S_LEN("filename"), (char *)wp->w_jumplist[i].fname);
+ }
+ }
+}
+
+/*
+ * "getline(lnum, [end])" function
+ */
+static void f_getline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ linenr_T end;
+ bool retlist;
+
+ const linenr_T lnum = tv_get_lnum(argvars);
+ if (argvars[1].v_type == VAR_UNKNOWN) {
+ end = lnum;
+ retlist = false;
+ } else {
+ end = tv_get_lnum(&argvars[1]);
+ retlist = true;
+ }
+
+ get_buffer_lines(curbuf, lnum, end, retlist, rettv);
+}
+
+/// "getloclist()" function
+static void f_getloclist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ win_T *wp = find_win_by_nr_or_id(&argvars[0]);
+ get_qf_loc_list(false, wp, &argvars[1], rettv);
+}
+
+/*
+ * "getmatches()" function
+ */
+static void f_getmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ matchitem_T *cur = curwin->w_match_head;
+ int i;
+
+ tv_list_alloc_ret(rettv, kListLenMayKnow);
+ 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((int)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;
+ }
+}
+
+/*
+ * "getpid()" function
+ */
+static void f_getpid(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = os_get_pid();
+}
+
+static void getpos_both(typval_T *argvars, typval_T *rettv, bool getcurpos)
+{
+ pos_T *fp;
+ int fnum = -1;
+
+ if (getcurpos) {
+ fp = &curwin->w_cursor;
+ } else {
+ fp = var2fpos(&argvars[0], true, &fnum);
+ }
+
+ list_T *const l = tv_list_alloc_ret(rettv, 4 + (!!getcurpos));
+ tv_list_append_number(l, (fnum != -1) ? (varnumber_T)fnum : (varnumber_T)0);
+ tv_list_append_number(l, ((fp != NULL)
+ ? (varnumber_T)fp->lnum
+ : (varnumber_T)0));
+ tv_list_append_number(
+ l, ((fp != NULL)
+ ? (varnumber_T)(fp->col == MAXCOL ? MAXCOL : fp->col + 1)
+ : (varnumber_T)0));
+ tv_list_append_number(
+ l, (fp != NULL) ? (varnumber_T)fp->coladd : (varnumber_T)0);
+ if (getcurpos) {
+ const int save_set_curswant = curwin->w_set_curswant;
+ const colnr_T save_curswant = curwin->w_curswant;
+ const colnr_T save_virtcol = curwin->w_virtcol;
+
+ update_curswant();
+ tv_list_append_number(l, (curwin->w_curswant == MAXCOL
+ ? (varnumber_T)MAXCOL
+ : (varnumber_T)curwin->w_curswant + 1));
+
+ // Do not change "curswant", as it is unexpected that a get
+ // function has a side effect.
+ if (save_set_curswant) {
+ curwin->w_set_curswant = save_set_curswant;
+ curwin->w_curswant = save_curswant;
+ curwin->w_virtcol = save_virtcol;
+ curwin->w_valid &= ~VALID_VIRTCOL;
+ }
+ }
+}
+
+/*
+ * "getcurpos(string)" function
+ */
+static void f_getcurpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ getpos_both(argvars, rettv, true);
+}
+
+/*
+ * "getpos(string)" function
+ */
+static void f_getpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ getpos_both(argvars, rettv, false);
+}
+
+/// "getqflist()" functions
+static void f_getqflist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ get_qf_loc_list(true, NULL, &argvars[0], rettv);
+}
+
+/// "getreg()" function
+static void f_getreg(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *strregname;
+ int arg2 = false;
+ bool return_list = false;
+ bool error = false;
+
+ if (argvars[0].v_type != VAR_UNKNOWN) {
+ strregname = tv_get_string_chk(&argvars[0]);
+ error = strregname == NULL;
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ arg2 = tv_get_number_chk(&argvars[1], &error);
+ if (!error && argvars[2].v_type != VAR_UNKNOWN) {
+ return_list = tv_get_number_chk(&argvars[2], &error);
+ }
+ }
+ } else {
+ strregname = _(get_vim_var_str(VV_REG));
+ }
+
+ if (error) {
+ return;
+ }
+
+ int regname = (uint8_t)(strregname == NULL ? '"' : *strregname);
+ if (regname == 0) {
+ regname = '"';
+ }
+
+ if (return_list) {
+ rettv->v_type = VAR_LIST;
+ rettv->vval.v_list =
+ get_reg_contents(regname, (arg2 ? kGRegExprSrc : 0) | kGRegList);
+ if (rettv->vval.v_list == NULL) {
+ rettv->vval.v_list = tv_list_alloc(0);
+ }
+ tv_list_ref(rettv->vval.v_list);
+ } else {
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = get_reg_contents(regname, arg2 ? kGRegExprSrc : 0);
+ }
+}
+
+/*
+ * "getregtype()" function
+ */
+static void f_getregtype(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *strregname;
+
+ if (argvars[0].v_type != VAR_UNKNOWN) {
+ strregname = tv_get_string_chk(&argvars[0]);
+ if (strregname == NULL) { // Type error; errmsg already given.
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+ return;
+ }
+ } else {
+ // Default to v:register.
+ strregname = _(get_vim_var_str(VV_REG));
+ }
+
+ int regname = (uint8_t)(strregname == NULL ? '"' : *strregname);
+ if (regname == 0) {
+ regname = '"';
+ }
+
+ colnr_T reglen = 0;
+ char buf[NUMBUFLEN + 2];
+ MotionType reg_type = get_reg_type(regname, &reglen);
+ format_reg_type(reg_type, reglen, buf, ARRAY_SIZE(buf));
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = (char_u *)xstrdup(buf);
+}
+
+/// "gettabinfo()" function
+static void f_gettabinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ tabpage_T *tparg = NULL;
+
+ tv_list_alloc_ret(rettv, (argvars[0].v_type == VAR_UNKNOWN
+ ? 1
+ : kListLenMayKnow));
+
+ if (argvars[0].v_type != VAR_UNKNOWN) {
+ // Information about one tab page
+ tparg = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL));
+ if (tparg == NULL) {
+ return;
+ }
+ }
+
+ // Get information about a specific tab page or all tab pages
+ int tpnr = 0;
+ FOR_ALL_TABS(tp) {
+ tpnr++;
+ if (tparg != NULL && tp != tparg) {
+ continue;
+ }
+ dict_T *const d = get_tabpage_info(tp, tpnr);
+ tv_list_append_dict(rettv->vval.v_list, d);
+ if (tparg != NULL) {
+ return;
+ }
+ }
+}
+
+/*
+ * "gettabvar()" function
+ */
+static void f_gettabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ win_T *oldcurwin;
+ tabpage_T *oldtabpage;
+ bool done = false;
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+
+ const char *const varname = tv_get_string_chk(&argvars[1]);
+ tabpage_T *const tp = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL));
+ if (tp != NULL && varname != NULL) {
+ // Set tp to be our tabpage, temporarily. Also set the window to the
+ // first window in the tabpage, otherwise the window is not valid.
+ win_T *const window = tp == curtab || tp->tp_firstwin == NULL
+ ? firstwin
+ : tp->tp_firstwin;
+ if (switch_win(&oldcurwin, &oldtabpage, window, tp, true) == OK) {
+ // look up the variable
+ // Let gettabvar({nr}, "") return the "t:" dictionary.
+ const dictitem_T *const v = find_var_in_ht(&tp->tp_vars->dv_hashtab, 't',
+ varname, strlen(varname),
+ false);
+ if (v != NULL) {
+ tv_copy(&v->di_tv, rettv);
+ done = true;
+ }
+ }
+
+ // restore previous notion of curwin
+ restore_win(oldcurwin, oldtabpage, true);
+ }
+
+ if (!done && argvars[2].v_type != VAR_UNKNOWN) {
+ tv_copy(&argvars[2], rettv);
+ }
+}
+
+/*
+ * "gettabwinvar()" function
+ */
+static void f_gettabwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ getwinvar(argvars, rettv, 1);
+}
+
+// "gettagstack()" function
+static void f_gettagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ win_T *wp = curwin; // default is current window
+
+ tv_dict_alloc_ret(rettv);
+
+ if (argvars[0].v_type != VAR_UNKNOWN) {
+ wp = find_win_by_nr_or_id(&argvars[0]);
+ if (wp == NULL) {
+ return;
+ }
+ }
+
+ get_tagstack(wp, rettv->vval.v_dict);
+}
+
+/// "getwininfo()" function
+static void f_getwininfo(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ win_T *wparg = NULL;
+
+ tv_list_alloc_ret(rettv, kListLenMayKnow);
+
+ if (argvars[0].v_type != VAR_UNKNOWN) {
+ wparg = win_id2wp(argvars);
+ if (wparg == NULL) {
+ return;
+ }
+ }
+
+ // Collect information about either all the windows across all the tab
+ // pages or one particular window.
+ int16_t tabnr = 0;
+ FOR_ALL_TABS(tp) {
+ tabnr++;
+ int16_t winnr = 0;
+ FOR_ALL_WINDOWS_IN_TAB(wp, tp) {
+ winnr++;
+ if (wparg != NULL && wp != wparg) {
+ continue;
+ }
+ dict_T *const d = get_win_info(wp, tabnr, winnr);
+ tv_list_append_dict(rettv->vval.v_list, d);
+ if (wparg != NULL) {
+ // found information about a specific window
+ return;
+ }
+ }
+ }
+}
+
+// Dummy timer callback. Used by f_wait().
+static void dummy_timer_due_cb(TimeWatcher *tw, void *data)
+{
+}
+
+// Dummy timer close callback. Used by f_wait().
+static void dummy_timer_close_cb(TimeWatcher *tw, void *data)
+{
+ xfree(tw);
+}
+
+/// "wait(timeout, condition[, interval])" function
+static void f_wait(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = -1;
+
+ if (argvars[0].v_type != VAR_NUMBER) {
+ EMSG2(_(e_invargval), "1");
+ return;
+ }
+ if ((argvars[2].v_type != VAR_NUMBER && argvars[2].v_type != VAR_UNKNOWN)
+ || (argvars[2].v_type == VAR_NUMBER && argvars[2].vval.v_number <= 0)) {
+ EMSG2(_(e_invargval), "3");
+ return;
+ }
+
+ int timeout = argvars[0].vval.v_number;
+ typval_T expr = argvars[1];
+ int interval = argvars[2].v_type == VAR_NUMBER
+ ? argvars[2].vval.v_number
+ : 200; // Default.
+ TimeWatcher *tw = xmalloc(sizeof(TimeWatcher));
+
+ // Start dummy timer.
+ time_watcher_init(&main_loop, tw, NULL);
+ tw->events = main_loop.events;
+ tw->blockable = true;
+ time_watcher_start(tw, dummy_timer_due_cb, interval, interval);
+
+ typval_T argv = TV_INITIAL_VALUE;
+ typval_T exprval = TV_INITIAL_VALUE;
+ bool error = false;
+ int save_called_emsg = called_emsg;
+ called_emsg = false;
+
+ LOOP_PROCESS_EVENTS_UNTIL(&main_loop, main_loop.events, timeout,
+ eval_expr_typval(&expr, &argv, 0, &exprval) != OK
+ || tv_get_number_chk(&exprval, &error)
+ || called_emsg || error || got_int);
+
+ if (called_emsg || error) {
+ rettv->vval.v_number = -3;
+ } else if (got_int) {
+ got_int = false;
+ vgetc();
+ rettv->vval.v_number = -2;
+ } else if (tv_get_number_chk(&exprval, &error)) {
+ rettv->vval.v_number = 0;
+ }
+
+ called_emsg = save_called_emsg;
+
+ // Stop dummy timer
+ time_watcher_stop(tw);
+ time_watcher_close(tw, dummy_timer_close_cb);
+}
+
+// "win_screenpos()" function
+static void f_win_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ tv_list_alloc_ret(rettv, 2);
+ const win_T *const wp = find_win_by_nr_or_id(&argvars[0]);
+ tv_list_append_number(rettv->vval.v_list, wp == NULL ? 0 : wp->w_winrow + 1);
+ tv_list_append_number(rettv->vval.v_list, wp == NULL ? 0 : wp->w_wincol + 1);
+}
+
+// "getwinpos({timeout})" function
+static void f_getwinpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ tv_list_alloc_ret(rettv, 2);
+ tv_list_append_number(rettv->vval.v_list, -1);
+ tv_list_append_number(rettv->vval.v_list, -1);
+}
+
+/*
+ * "getwinposx()" function
+ */
+static void f_getwinposx(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = -1;
+}
+
+/*
+ * "getwinposy()" function
+ */
+static void f_getwinposy(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = -1;
+}
+
+/// "getwinvar()" function
+static void f_getwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ getwinvar(argvars, rettv, 0);
+}
+
+/*
+ * "glob()" function
+ */
+static void f_glob(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int options = WILD_SILENT|WILD_USE_NL;
+ expand_T xpc;
+ bool error = false;
+
+ /* When the optional second argument is non-zero, don't remove matches
+ * for 'wildignore' and don't put matches for 'suffixes' at the end. */
+ rettv->v_type = VAR_STRING;
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ if (tv_get_number_chk(&argvars[1], &error)) {
+ options |= WILD_KEEP_ALL;
+ }
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ if (tv_get_number_chk(&argvars[2], &error)) {
+ tv_list_set_ret(rettv, NULL);
+ }
+ if (argvars[3].v_type != VAR_UNKNOWN
+ && tv_get_number_chk(&argvars[3], &error)) {
+ options |= WILD_ALLLINKS;
+ }
+ }
+ }
+ if (!error) {
+ ExpandInit(&xpc);
+ xpc.xp_context = EXPAND_FILES;
+ if (p_wic)
+ options += WILD_ICASE;
+ if (rettv->v_type == VAR_STRING) {
+ rettv->vval.v_string = ExpandOne(
+ &xpc, (char_u *)tv_get_string(&argvars[0]), NULL, options, WILD_ALL);
+ } else {
+ ExpandOne(&xpc, (char_u *)tv_get_string(&argvars[0]), NULL, options,
+ WILD_ALL_KEEP);
+ tv_list_alloc_ret(rettv, xpc.xp_numfiles);
+ for (int i = 0; i < xpc.xp_numfiles; i++) {
+ tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i],
+ -1);
+ }
+ ExpandCleanup(&xpc);
+ }
+ } else
+ rettv->vval.v_string = NULL;
+}
+
+/// "globpath()" function
+static void f_globpath(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int flags = 0; // Flags for globpath.
+ bool error = false;
+
+ // Return a string, or a list if the optional third argument is non-zero.
+ rettv->v_type = VAR_STRING;
+
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ // When the optional second argument is non-zero, don't remove matches
+ // for 'wildignore' and don't put matches for 'suffixes' at the end.
+ if (tv_get_number_chk(&argvars[2], &error)) {
+ flags |= WILD_KEEP_ALL;
+ }
+
+ if (argvars[3].v_type != VAR_UNKNOWN) {
+ if (tv_get_number_chk(&argvars[3], &error)) {
+ tv_list_set_ret(rettv, NULL);
+ }
+ if (argvars[4].v_type != VAR_UNKNOWN
+ && tv_get_number_chk(&argvars[4], &error)) {
+ flags |= WILD_ALLLINKS;
+ }
+ }
+ }
+
+ char buf1[NUMBUFLEN];
+ const char *const file = tv_get_string_buf_chk(&argvars[1], buf1);
+ if (file != NULL && !error) {
+ garray_T ga;
+ ga_init(&ga, (int)sizeof(char_u *), 10);
+ globpath((char_u *)tv_get_string(&argvars[0]), (char_u *)file, &ga, flags);
+
+ if (rettv->v_type == VAR_STRING) {
+ rettv->vval.v_string = ga_concat_strings_sep(&ga, "\n");
+ } else {
+ tv_list_alloc_ret(rettv, ga.ga_len);
+ for (int i = 0; i < ga.ga_len; i++) {
+ tv_list_append_string(rettv->vval.v_list,
+ ((const char **)(ga.ga_data))[i], -1);
+ }
+ }
+
+ ga_clear_strings(&ga);
+ } else {
+ rettv->vval.v_string = NULL;
+ }
+}
+
+// "glob2regpat()" function
+static void f_glob2regpat(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *const pat = tv_get_string_chk(&argvars[0]); // NULL on type error
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = ((pat == NULL)
+ ? NULL
+ : file_pat_to_reg_pat((char_u *)pat, NULL, NULL,
+ false));
+}
+
+/// "has()" function
+static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ static const char *const has_list[] = {
+#if defined(BSD) && !defined(__APPLE__)
+ "bsd",
+#endif
+#ifdef UNIX
+ "unix",
+#endif
+#if defined(WIN32)
+ "win32",
+#endif
+#if defined(WIN64) || defined(_WIN64)
+ "win64",
+#endif
+ "fname_case",
+#ifdef HAVE_ACL
+ "acl",
+#endif
+ "autochdir",
+ "arabic",
+ "autocmd",
+ "browsefilter",
+ "byte_offset",
+ "cindent",
+ "cmdline_compl",
+ "cmdline_hist",
+ "comments",
+ "conceal",
+ "cscope",
+ "cursorbind",
+ "cursorshape",
+#ifdef DEBUG
+ "debug",
+#endif
+ "dialog_con",
+ "diff",
+ "digraphs",
+ "eval", // always present, of course!
+ "ex_extra",
+ "extra_search",
+ "file_in_path",
+ "filterpipe",
+ "find_in_path",
+ "float",
+ "folding",
+#if defined(UNIX)
+ "fork",
+#endif
+ "gettext",
+#if defined(HAVE_ICONV)
+ "iconv",
+#endif
+ "insert_expand",
+ "jumplist",
+ "keymap",
+ "lambda",
+ "langmap",
+ "libcall",
+ "linebreak",
+ "lispindent",
+ "listcmds",
+ "localmap",
+#ifdef __APPLE__
+ "mac",
+ "macunix",
+ "osx",
+ "osxdarwin",
+#endif
+ "menu",
+ "mksession",
+ "modify_fname",
+ "mouse",
+ "multi_byte",
+ "multi_lang",
+ "num64",
+ "packages",
+ "path_extra",
+ "persistent_undo",
+ "postscript",
+ "printer",
+ "profile",
+ "pythonx",
+ "reltime",
+ "quickfix",
+ "rightleft",
+ "scrollbind",
+ "showcmd",
+ "cmdline_info",
+ "shada",
+ "signs",
+ "smartindent",
+ "startuptime",
+ "statusline",
+ "spell",
+ "syntax",
+#if !defined(UNIX)
+ "system", // TODO(SplinterOfChaos): This IS defined for UNIX!
+#endif
+ "tablineat",
+ "tag_binary",
+ "termguicolors",
+ "termresponse",
+ "textobjects",
+ "timers",
+ "title",
+ "user-commands", // was accidentally included in 5.4
+ "user_commands",
+ "vertsplit",
+ "virtualedit",
+ "visual",
+ "visualextra",
+ "vreplace",
+ "wildignore",
+ "wildmenu",
+ "windows",
+ "winaltkeys",
+ "writebackup",
+#if defined(HAVE_WSL)
+ "wsl",
+#endif
+ "nvim",
+ };
+
+ bool n = false;
+ const char *const name = tv_get_string(&argvars[0]);
+ for (size_t i = 0; i < ARRAY_SIZE(has_list); i++) {
+ if (STRICMP(name, has_list[i]) == 0) {
+ n = true;
+ break;
+ }
+ }
+
+ if (!n) {
+ if (STRNICMP(name, "patch", 5) == 0) {
+ if (name[5] == '-'
+ && strlen(name) >= 11
+ && ascii_isdigit(name[6])
+ && ascii_isdigit(name[8])
+ && ascii_isdigit(name[10])) {
+ int major = atoi(name + 6);
+ int minor = atoi(name + 8);
+
+ // Expect "patch-9.9.01234".
+ n = (major < VIM_VERSION_MAJOR
+ || (major == VIM_VERSION_MAJOR
+ && (minor < VIM_VERSION_MINOR
+ || (minor == VIM_VERSION_MINOR
+ && has_vim_patch(atoi(name + 10))))));
+ } else {
+ n = has_vim_patch(atoi(name + 5));
+ }
+ } else if (STRNICMP(name, "nvim-", 5) == 0) {
+ // Expect "nvim-x.y.z"
+ n = has_nvim_version(name + 5);
+ } else if (STRICMP(name, "vim_starting") == 0) {
+ n = (starting != 0);
+ } else if (STRICMP(name, "ttyin") == 0) {
+ n = stdin_isatty;
+ } else if (STRICMP(name, "ttyout") == 0) {
+ n = stdout_isatty;
+ } else if (STRICMP(name, "multi_byte_encoding") == 0) {
+ n = has_mbyte != 0;
+ } else if (STRICMP(name, "syntax_items") == 0) {
+ n = syntax_present(curwin);
+#ifdef UNIX
+ } else if (STRICMP(name, "unnamedplus") == 0) {
+ n = eval_has_provider("clipboard");
+#endif
+ }
+ }
+
+ if (!n && eval_has_provider(name)) {
+ n = true;
+ }
+
+ rettv->vval.v_number = n;
+}
+
+/*
+ * "has_key()" function
+ */
+static void f_has_key(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (argvars[0].v_type != VAR_DICT) {
+ EMSG(_(e_dictreq));
+ return;
+ }
+ if (argvars[0].vval.v_dict == NULL)
+ return;
+
+ rettv->vval.v_number = tv_dict_find(argvars[0].vval.v_dict,
+ tv_get_string(&argvars[1]),
+ -1) != NULL;
+}
+
+/// `haslocaldir([{win}[, {tab}]])` function
+///
+/// Returns `1` if the scope object has a local directory, `0` otherwise. If a
+/// scope object is not specified the current one is implied. This function
+/// share a lot of code with `f_getcwd`.
+///
+/// @pre The arguments must be of type number.
+/// @pre There may not be more than two arguments.
+/// @pre An argument may not be -1 if preceding arguments are not all -1.
+///
+/// @post The return value will be either the number `1` or `0`.
+static void f_haslocaldir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ // Possible scope of working directory to return.
+ CdScope scope = kCdScopeInvalid;
+
+ // Numbers of the scope objects (window, tab) we want the working directory
+ // of. A `-1` means to skip this scope, a `0` means the current object.
+ int scope_number[] = {
+ [kCdScopeWindow] = 0, // Number of window to look at.
+ [kCdScopeTab ] = 0, // Number of tab to look at.
+ };
+
+ tabpage_T *tp = curtab; // The tabpage to look at.
+ win_T *win = curwin; // The window to look at.
+
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = 0;
+
+ // Pre-conditions and scope extraction together
+ for (int i = MIN_CD_SCOPE; i < MAX_CD_SCOPE; i++) {
+ if (argvars[i].v_type == VAR_UNKNOWN) {
+ break;
+ }
+ if (argvars[i].v_type != VAR_NUMBER) {
+ EMSG(_(e_invarg));
+ return;
+ }
+ scope_number[i] = argvars[i].vval.v_number;
+ if (scope_number[i] < -1) {
+ EMSG(_(e_invarg));
+ return;
+ }
+ // Use the narrowest scope the user requested
+ if (scope_number[i] >= 0 && scope == kCdScopeInvalid) {
+ // The scope is the current iteration step.
+ scope = i;
+ } else if (scope_number[i] < 0) {
+ scope = i + 1;
+ }
+ }
+
+ // If the user didn't specify anything, default to window scope
+ if (scope == kCdScopeInvalid) {
+ scope = MIN_CD_SCOPE;
+ }
+
+ // Find the tabpage by number
+ if (scope_number[kCdScopeTab] > 0) {
+ tp = find_tabpage(scope_number[kCdScopeTab]);
+ if (!tp) {
+ EMSG(_("E5000: Cannot find tab number."));
+ return;
+ }
+ }
+
+ // Find the window in `tp` by number, `NULL` if none.
+ if (scope_number[kCdScopeWindow] >= 0) {
+ if (scope_number[kCdScopeTab] < 0) {
+ EMSG(_("E5001: Higher scope cannot be -1 if lower scope is >= 0."));
+ return;
+ }
+
+ if (scope_number[kCdScopeWindow] > 0) {
+ win = find_win_by_nr(&argvars[0], tp);
+ if (!win) {
+ EMSG(_("E5002: Cannot find window number."));
+ return;
+ }
+ }
+ }
+
+ switch (scope) {
+ case kCdScopeWindow:
+ assert(win);
+ rettv->vval.v_number = win->w_localdir ? 1 : 0;
+ break;
+ case kCdScopeTab:
+ assert(tp);
+ rettv->vval.v_number = tp->tp_localdir ? 1 : 0;
+ break;
+ case kCdScopeGlobal:
+ // The global scope never has a local directory
+ break;
+ case kCdScopeInvalid:
+ // We should never get here
+ assert(false);
+ }
+}
+
+/*
+ * "hasmapto()" function
+ */
+static void f_hasmapto(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *mode;
+ const char *const name = tv_get_string(&argvars[0]);
+ bool abbr = false;
+ char buf[NUMBUFLEN];
+ if (argvars[1].v_type == VAR_UNKNOWN) {
+ mode = "nvo";
+ } else {
+ mode = tv_get_string_buf(&argvars[1], buf);
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ abbr = tv_get_number(&argvars[2]);
+ }
+ }
+
+ if (map_to_exists(name, mode, abbr)) {
+ rettv->vval.v_number = true;
+ } else {
+ rettv->vval.v_number = false;
+ }
+}
+
+/*
+ * "histadd()" function
+ */
+static void f_histadd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ HistoryType histype;
+
+ rettv->vval.v_number = false;
+ if (check_secure()) {
+ return;
+ }
+ const char *str = tv_get_string_chk(&argvars[0]); // NULL on type error
+ histype = str != NULL ? get_histtype(str, strlen(str), false) : HIST_INVALID;
+ if (histype != HIST_INVALID) {
+ char buf[NUMBUFLEN];
+ str = tv_get_string_buf(&argvars[1], buf);
+ if (*str != NUL) {
+ init_history();
+ add_to_history(histype, (char_u *)str, false, NUL);
+ rettv->vval.v_number = true;
+ return;
+ }
+ }
+}
+
+/*
+ * "histdel()" function
+ */
+static void f_histdel(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int n;
+ const char *const str = tv_get_string_chk(&argvars[0]); // NULL on type error
+ if (str == NULL) {
+ n = 0;
+ } else if (argvars[1].v_type == VAR_UNKNOWN) {
+ // only one argument: clear entire history
+ n = clr_history(get_histtype(str, strlen(str), false));
+ } else if (argvars[1].v_type == VAR_NUMBER) {
+ // index given: remove that entry
+ n = del_history_idx(get_histtype(str, strlen(str), false),
+ (int)tv_get_number(&argvars[1]));
+ } else {
+ // string given: remove all matching entries
+ char buf[NUMBUFLEN];
+ n = del_history_entry(get_histtype(str, strlen(str), false),
+ (char_u *)tv_get_string_buf(&argvars[1], buf));
+ }
+ rettv->vval.v_number = n;
+}
+
+/*
+ * "histget()" function
+ */
+static void f_histget(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ HistoryType type;
+ int idx;
+
+ const char *const str = tv_get_string_chk(&argvars[0]); // NULL on type error
+ if (str == NULL) {
+ rettv->vval.v_string = NULL;
+ } else {
+ type = get_histtype(str, strlen(str), false);
+ if (argvars[1].v_type == VAR_UNKNOWN) {
+ idx = get_history_idx(type);
+ } else {
+ idx = (int)tv_get_number_chk(&argvars[1], NULL);
+ }
+ // -1 on type error
+ rettv->vval.v_string = vim_strsave(get_history_entry(type, idx));
+ }
+ rettv->v_type = VAR_STRING;
+}
+
+/*
+ * "histnr()" function
+ */
+static void f_histnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *const history = tv_get_string_chk(&argvars[0]);
+ HistoryType i = history == NULL
+ ? HIST_INVALID
+ : get_histtype(history, strlen(history), false);
+ if (i != HIST_INVALID) {
+ i = get_history_idx(i);
+ }
+ rettv->vval.v_number = i;
+}
+
+/*
+ * "highlightID(name)" function
+ */
+static void f_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = syn_name2id(
+ (const char_u *)tv_get_string(&argvars[0]));
+}
+
+/*
+ * "highlight_exists()" function
+ */
+static void f_hlexists(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = highlight_exists(
+ (const char_u *)tv_get_string(&argvars[0]));
+}
+
+/*
+ * "hostname()" function
+ */
+static void f_hostname(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char hostname[256];
+
+ os_get_hostname(hostname, 256);
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = vim_strsave((char_u *)hostname);
+}
+
+/*
+ * iconv() function
+ */
+static void f_iconv(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ vimconv_T vimconv;
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+
+ const char *const str = tv_get_string(&argvars[0]);
+ char buf1[NUMBUFLEN];
+ char_u *const from = enc_canonize(enc_skip(
+ (char_u *)tv_get_string_buf(&argvars[1], buf1)));
+ char buf2[NUMBUFLEN];
+ char_u *const to = enc_canonize(enc_skip(
+ (char_u *)tv_get_string_buf(&argvars[2], buf2)));
+ vimconv.vc_type = CONV_NONE;
+ convert_setup(&vimconv, from, to);
+
+ // If the encodings are equal, no conversion needed.
+ if (vimconv.vc_type == CONV_NONE) {
+ rettv->vval.v_string = (char_u *)xstrdup(str);
+ } else {
+ rettv->vval.v_string = string_convert(&vimconv, (char_u *)str, NULL);
+ }
+
+ convert_setup(&vimconv, NULL, NULL);
+ xfree(from);
+ xfree(to);
+}
+
+/*
+ * "indent()" function
+ */
+static void f_indent(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const linenr_T lnum = tv_get_lnum(argvars);
+ if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) {
+ rettv->vval.v_number = get_indent_lnum(lnum);
+ } else {
+ rettv->vval.v_number = -1;
+ }
+}
+
+/*
+ * "index()" function
+ */
+static void f_index(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ long idx = 0;
+ bool ic = false;
+
+ rettv->vval.v_number = -1;
+ if (argvars[0].v_type != VAR_LIST) {
+ EMSG(_(e_listreq));
+ return;
+ }
+ list_T *const l = argvars[0].vval.v_list;
+ if (l != NULL) {
+ listitem_T *item = tv_list_first(l);
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ bool error = false;
+
+ // Start at specified item.
+ idx = tv_list_uidx(l, tv_get_number_chk(&argvars[2], &error));
+ if (error || idx == -1) {
+ item = NULL;
+ } else {
+ item = tv_list_find(l, idx);
+ assert(item != NULL);
+ }
+ if (argvars[3].v_type != VAR_UNKNOWN) {
+ ic = !!tv_get_number_chk(&argvars[3], &error);
+ if (error) {
+ item = NULL;
+ }
+ }
+ }
+
+ for (; item != NULL; item = TV_LIST_ITEM_NEXT(l, item), idx++) {
+ if (tv_equal(TV_LIST_ITEM_TV(item), &argvars[1], ic, false)) {
+ rettv->vval.v_number = idx;
+ break;
+ }
+ }
+ }
+}
+
+static bool inputsecret_flag = false;
+
+/*
+ * "input()" function
+ * Also handles inputsecret() when inputsecret is set.
+ */
+static void f_input(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ get_user_input(argvars, rettv, FALSE, inputsecret_flag);
+}
+
+/*
+ * "inputdialog()" function
+ */
+static void f_inputdialog(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ get_user_input(argvars, rettv, TRUE, inputsecret_flag);
+}
+
+/*
+ * "inputlist()" function
+ */
+static void f_inputlist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int selected;
+ int mouse_used;
+
+ if (argvars[0].v_type != VAR_LIST) {
+ EMSG2(_(e_listarg), "inputlist()");
+ return;
+ }
+
+ msg_start();
+ msg_row = Rows - 1; // for when 'cmdheight' > 1
+ lines_left = Rows; // avoid more prompt
+ msg_scroll = true;
+ msg_clr_eos();
+
+ TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, {
+ msg_puts(tv_get_string(TV_LIST_ITEM_TV(li)));
+ msg_putchar('\n');
+ });
+
+ // Ask for choice.
+ selected = prompt_for_number(&mouse_used);
+ if (mouse_used) {
+ selected -= lines_left;
+ }
+
+ rettv->vval.v_number = selected;
+}
+
+
+static garray_T ga_userinput = { 0, 0, sizeof(tasave_T), 4, NULL };
+
+/// "inputrestore()" function
+static void f_inputrestore(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (!GA_EMPTY(&ga_userinput)) {
+ ga_userinput.ga_len--;
+ restore_typeahead((tasave_T *)(ga_userinput.ga_data)
+ + ga_userinput.ga_len);
+ // default return is zero == OK
+ } else if (p_verbose > 1) {
+ verb_msg(_("called inputrestore() more often than inputsave()"));
+ rettv->vval.v_number = 1; // Failed
+ }
+}
+
+/// "inputsave()" function
+static void f_inputsave(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ // Add an entry to the stack of typeahead storage.
+ tasave_T *p = GA_APPEND_VIA_PTR(tasave_T, &ga_userinput);
+ save_typeahead(p);
+}
+
+/// "inputsecret()" function
+static void f_inputsecret(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ cmdline_star++;
+ inputsecret_flag = true;
+ f_input(argvars, rettv, NULL);
+ cmdline_star--;
+ inputsecret_flag = false;
+}
+
+/*
+ * "insert()" function
+ */
+static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ list_T *l;
+ bool error = false;
+
+ if (argvars[0].v_type != VAR_LIST) {
+ EMSG2(_(e_listarg), "insert()");
+ } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)),
+ N_("insert() argument"), TV_TRANSLATE)) {
+ long before = 0;
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ before = tv_get_number_chk(&argvars[2], &error);
+ }
+ if (error) {
+ // type error; errmsg already given
+ return;
+ }
+
+ listitem_T *item = NULL;
+ if (before != tv_list_len(l)) {
+ item = tv_list_find(l, before);
+ if (item == NULL) {
+ EMSGN(_(e_listidx), before);
+ l = NULL;
+ }
+ }
+ if (l != NULL) {
+ tv_list_insert_tv(l, &argvars[1], item);
+ tv_copy(&argvars[0], rettv);
+ }
+ }
+}
+
+/*
+ * "invert(expr)" function
+ */
+static void f_invert(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = ~tv_get_number_chk(&argvars[0], NULL);
+}
+
+/*
+ * "isdirectory()" function
+ */
+static void f_isdirectory(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = os_isdir((const char_u *)tv_get_string(&argvars[0]));
+}
+
+/*
+ * "islocked()" function
+ */
+static void f_islocked(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ lval_T lv;
+ dictitem_T *di;
+
+ rettv->vval.v_number = -1;
+ const char_u *const end = get_lval((char_u *)tv_get_string(&argvars[0]),
+ NULL,
+ &lv, false, false,
+ GLV_NO_AUTOLOAD|GLV_READ_ONLY,
+ FNE_CHECK_START);
+ if (end != NULL && lv.ll_name != NULL) {
+ if (*end != NUL) {
+ EMSG(_(e_trailing));
+ } else {
+ if (lv.ll_tv == NULL) {
+ di = find_var((const char *)lv.ll_name, lv.ll_name_len, NULL, true);
+ if (di != NULL) {
+ // Consider a variable locked when:
+ // 1. the variable itself is locked
+ // 2. the value of the variable is locked.
+ // 3. the List or Dict value is locked.
+ rettv->vval.v_number = ((di->di_flags & DI_FLAGS_LOCK)
+ || tv_islocked(&di->di_tv));
+ }
+ } else if (lv.ll_range) {
+ EMSG(_("E786: Range not allowed"));
+ } else if (lv.ll_newkey != NULL) {
+ EMSG2(_(e_dictkey), lv.ll_newkey);
+ } else if (lv.ll_list != NULL) {
+ // List item.
+ rettv->vval.v_number = tv_islocked(TV_LIST_ITEM_TV(lv.ll_li));
+ } else {
+ // Dictionary item.
+ rettv->vval.v_number = tv_islocked(&lv.ll_di->di_tv);
+ }
+ }
+ }
+
+ clear_lval(&lv);
+}
+
+// "isinf()" function
+static void f_isinf(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (argvars[0].v_type == VAR_FLOAT
+ && xisinf(argvars[0].vval.v_float)) {
+ rettv->vval.v_number = argvars[0].vval.v_float > 0.0 ? 1 : -1;
+ }
+}
+
+// "isnan()" function
+static void f_isnan(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = argvars[0].v_type == VAR_FLOAT
+ && xisnan(argvars[0].vval.v_float);
+}
+
+/// "id()" function
+static void f_id(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+ FUNC_ATTR_NONNULL_ALL
+{
+ const int len = vim_vsnprintf_typval(NULL, 0, "%p", dummy_ap, argvars);
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = xmalloc(len + 1);
+ vim_vsnprintf_typval((char *)rettv->vval.v_string, len + 1, "%p",
+ dummy_ap, argvars);
+}
+
+/*
+ * "items(dict)" function
+ */
+static void f_items(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ dict_list(argvars, rettv, 2);
+}
+
+// "jobpid(id)" function
+static void f_jobpid(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = 0;
+
+ if (check_restricted() || check_secure()) {
+ return;
+ }
+
+ if (argvars[0].v_type != VAR_NUMBER) {
+ EMSG(_(e_invarg));
+ return;
+ }
+
+ Channel *data = find_job(argvars[0].vval.v_number, true);
+ if (!data) {
+ return;
+ }
+
+ Process *proc = (Process *)&data->stream.proc;
+ rettv->vval.v_number = proc->pid;
+}
+
+// "jobresize(job, width, height)" function
+static void f_jobresize(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = 0;
+
+ if (check_restricted() || check_secure()) {
+ return;
+ }
+
+ if (argvars[0].v_type != VAR_NUMBER || argvars[1].v_type != VAR_NUMBER
+ || argvars[2].v_type != VAR_NUMBER) {
+ // job id, width, height
+ EMSG(_(e_invarg));
+ return;
+ }
+
+
+ Channel *data = find_job(argvars[0].vval.v_number, true);
+ if (!data) {
+ return;
+ }
+
+ if (data->stream.proc.type != kProcessTypePty) {
+ EMSG(_(e_channotpty));
+ return;
+ }
+
+ pty_process_resize(&data->stream.pty, argvars[1].vval.v_number,
+ argvars[2].vval.v_number);
+ rettv->vval.v_number = 1;
+}
+
+// "jobstart()" function
+static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = 0;
+
+ if (check_restricted() || check_secure()) {
+ return;
+ }
+
+ bool executable = true;
+ char **argv = tv_to_argv(&argvars[0], NULL, &executable);
+ char **env = NULL;
+ if (!argv) {
+ rettv->vval.v_number = executable ? 0 : -1;
+ return; // Did error message in tv_to_argv.
+ }
+
+ if (argvars[1].v_type != VAR_DICT && argvars[1].v_type != VAR_UNKNOWN) {
+ // Wrong argument types
+ EMSG2(_(e_invarg2), "expected dictionary");
+ shell_free_argv(argv);
+ return;
+ }
+
+
+ dict_T *job_opts = NULL;
+ bool detach = false;
+ bool rpc = false;
+ bool pty = false;
+ bool clear_env = false;
+ CallbackReader on_stdout = CALLBACK_READER_INIT,
+ on_stderr = CALLBACK_READER_INIT;
+ Callback on_exit = CALLBACK_NONE;
+ char *cwd = NULL;
+ if (argvars[1].v_type == VAR_DICT) {
+ job_opts = argvars[1].vval.v_dict;
+
+ detach = tv_dict_get_number(job_opts, "detach") != 0;
+ rpc = tv_dict_get_number(job_opts, "rpc") != 0;
+ pty = tv_dict_get_number(job_opts, "pty") != 0;
+ clear_env = tv_dict_get_number(job_opts, "clear_env") != 0;
+ if (pty && rpc) {
+ EMSG2(_(e_invarg2), "job cannot have both 'pty' and 'rpc' options set");
+ shell_free_argv(argv);
+ return;
+ }
+
+ char *new_cwd = tv_dict_get_string(job_opts, "cwd", false);
+ if (new_cwd && strlen(new_cwd) > 0) {
+ cwd = new_cwd;
+ // The new cwd must be a directory.
+ if (!os_isdir_executable((const char *)cwd)) {
+ EMSG2(_(e_invarg2), "expected valid directory");
+ shell_free_argv(argv);
+ return;
+ }
+ }
+ dictitem_T *job_env = tv_dict_find(job_opts, S_LEN("env"));
+ if (job_env) {
+ if (job_env->di_tv.v_type != VAR_DICT) {
+ EMSG2(_(e_invarg2), "env");
+ shell_free_argv(argv);
+ return;
+ }
+
+ size_t custom_env_size = (size_t)tv_dict_len(job_env->di_tv.vval.v_dict);
+ size_t i = 0;
+ size_t env_size = 0;
+
+ if (clear_env) {
+ // + 1 for last null entry
+ env = xmalloc((custom_env_size + 1) * sizeof(*env));
+ env_size = 0;
+ } else {
+ env_size = os_get_fullenv_size();
+
+ env = xmalloc((custom_env_size + env_size + 1) * sizeof(*env));
+
+ os_copy_fullenv(env, env_size);
+ i = env_size;
+ }
+ assert(env); // env must be allocated at this point
+
+ TV_DICT_ITER(job_env->di_tv.vval.v_dict, var, {
+ const char *str = tv_get_string(&var->di_tv);
+ assert(str);
+ size_t len = STRLEN(var->di_key) + strlen(str) + strlen("=") + 1;
+ env[i] = xmalloc(len);
+ snprintf(env[i], len, "%s=%s", (char *)var->di_key, str);
+ i++;
+ });
+
+ // must be null terminated
+ env[env_size + custom_env_size] = NULL;
+ }
+
+
+ if (!common_job_callbacks(job_opts, &on_stdout, &on_stderr, &on_exit)) {
+ shell_free_argv(argv);
+ return;
+ }
+ }
+
+ uint16_t width = 0, height = 0;
+ char *term_name = NULL;
+
+ if (pty) {
+ width = (uint16_t)tv_dict_get_number(job_opts, "width");
+ height = (uint16_t)tv_dict_get_number(job_opts, "height");
+ term_name = tv_dict_get_string(job_opts, "TERM", true);
+ }
+
+ Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit, pty,
+ rpc, detach, cwd, width, height,
+ term_name, env, &rettv->vval.v_number);
+ if (chan) {
+ channel_create_event(chan, NULL);
+ }
+}
+
+// "jobstop()" function
+static void f_jobstop(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = 0;
+
+ if (check_restricted() || check_secure()) {
+ return;
+ }
+
+ if (argvars[0].v_type != VAR_NUMBER) {
+ // Only argument is the job id
+ EMSG(_(e_invarg));
+ return;
+ }
+
+ Channel *data = find_job(argvars[0].vval.v_number, false);
+ if (!data) {
+ return;
+ }
+
+ const char *error = NULL;
+ if (data->is_rpc) {
+ // Ignore return code, but show error later.
+ (void)channel_close(data->id, kChannelPartRpc, &error);
+ }
+ process_stop((Process *)&data->stream.proc);
+ rettv->vval.v_number = 1;
+ if (error) {
+ EMSG(error);
+ }
+}
+
+// "jobwait(ids[, timeout])" function
+static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = 0;
+
+ if (check_restricted() || check_secure()) {
+ return;
+ }
+ if (argvars[0].v_type != VAR_LIST || (argvars[1].v_type != VAR_NUMBER
+ && argvars[1].v_type != VAR_UNKNOWN)) {
+ EMSG(_(e_invarg));
+ return;
+ }
+
+ ui_busy_start();
+ list_T *args = argvars[0].vval.v_list;
+ Channel **jobs = xcalloc(tv_list_len(args), sizeof(*jobs));
+ MultiQueue *waiting_jobs = multiqueue_new_parent(loop_on_put, &main_loop);
+
+ // Validate, prepare jobs for waiting.
+ int i = 0;
+ TV_LIST_ITER_CONST(args, arg, {
+ Channel *chan = NULL;
+ if (TV_LIST_ITEM_TV(arg)->v_type != VAR_NUMBER
+ || !(chan = find_job(TV_LIST_ITEM_TV(arg)->vval.v_number, false))) {
+ jobs[i] = NULL; // Invalid job.
+ } else {
+ jobs[i] = chan;
+ channel_incref(chan);
+ if (chan->stream.proc.status < 0) {
+ // Process any pending events on the job's queue before temporarily
+ // replacing it.
+ multiqueue_process_events(chan->events);
+ multiqueue_replace_parent(chan->events, waiting_jobs);
+ }
+ }
+ i++;
+ });
+
+ int remaining = -1;
+ uint64_t before = 0;
+ if (argvars[1].v_type == VAR_NUMBER && argvars[1].vval.v_number >= 0) {
+ remaining = argvars[1].vval.v_number;
+ before = os_hrtime();
+ }
+
+ for (i = 0; i < tv_list_len(args); i++) {
+ if (remaining == 0) {
+ break; // Timeout.
+ }
+ if (jobs[i] == NULL) {
+ continue; // Invalid job, will assign status=-3 below.
+ }
+ int status = process_wait(&jobs[i]->stream.proc, remaining,
+ waiting_jobs);
+ if (status < 0) {
+ break; // Interrupted (CTRL-C) or timeout, skip remaining jobs.
+ }
+ if (remaining > 0) {
+ uint64_t now = os_hrtime();
+ remaining = MIN(0, remaining - (int)((now - before) / 1000000));
+ before = now;
+ }
+ }
+
+ list_T *const rv = tv_list_alloc(tv_list_len(args));
+
+ // For each job:
+ // * Restore its parent queue if the job is still alive.
+ // * Append its status to the output list, or:
+ // -3 for "invalid job id"
+ // -2 for "interrupted" (user hit CTRL-C)
+ // -1 for jobs that were skipped or timed out
+ for (i = 0; i < tv_list_len(args); i++) {
+ if (jobs[i] == NULL) {
+ tv_list_append_number(rv, -3);
+ continue;
+ }
+ multiqueue_process_events(jobs[i]->events);
+ multiqueue_replace_parent(jobs[i]->events, main_loop.events);
+
+ tv_list_append_number(rv, jobs[i]->stream.proc.status);
+ channel_decref(jobs[i]);
+ }
+
+ multiqueue_free(waiting_jobs);
+ xfree(jobs);
+ ui_busy_stop();
+ tv_list_ref(rv);
+ rettv->v_type = VAR_LIST;
+ rettv->vval.v_list = rv;
+}
+
+/*
+ * "join()" function
+ */
+static void f_join(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (argvars[0].v_type != VAR_LIST) {
+ EMSG(_(e_listreq));
+ return;
+ }
+ const char *const sep = (argvars[1].v_type == VAR_UNKNOWN
+ ? " "
+ : tv_get_string_chk(&argvars[1]));
+
+ rettv->v_type = VAR_STRING;
+
+ if (sep != NULL) {
+ garray_T ga;
+ ga_init(&ga, (int)sizeof(char), 80);
+ tv_list_join(&ga, argvars[0].vval.v_list, sep);
+ ga_append(&ga, NUL);
+ rettv->vval.v_string = (char_u *)ga.ga_data;
+ } else {
+ rettv->vval.v_string = NULL;
+ }
+}
+
+/// json_decode() function
+static void f_json_decode(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char numbuf[NUMBUFLEN];
+ const char *s = NULL;
+ char *tofree = NULL;
+ size_t len;
+ if (argvars[0].v_type == VAR_LIST) {
+ if (!encode_vim_list_to_buf(argvars[0].vval.v_list, &len, &tofree)) {
+ EMSG(_("E474: Failed to convert list to string"));
+ return;
+ }
+ s = tofree;
+ if (s == NULL) {
+ assert(len == 0);
+ s = "";
+ }
+ } else {
+ s = tv_get_string_buf_chk(&argvars[0], numbuf);
+ if (s) {
+ len = strlen(s);
+ } else {
+ return;
+ }
+ }
+ if (json_decode_string(s, len, rettv) == FAIL) {
+ emsgf(_("E474: Failed to parse %.*s"), (int)len, s);
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = 0;
+ }
+ assert(rettv->v_type != VAR_UNKNOWN);
+ xfree(tofree);
+}
+
+/// json_encode() function
+static void f_json_encode(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = (char_u *)encode_tv2json(&argvars[0], NULL);
+}
+
+/*
+ * "keys()" function
+ */
+static void f_keys(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ dict_list(argvars, rettv, 0);
+}
+
+/*
+ * "last_buffer_nr()" function.
+ */
+static void f_last_buffer_nr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int n = 0;
+
+ FOR_ALL_BUFFERS(buf) {
+ if (n < buf->b_fnum) {
+ n = buf->b_fnum;
+ }
+ }
+
+ rettv->vval.v_number = n;
+}
+
+/*
+ * "len()" function
+ */
+static void f_len(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ switch (argvars[0].v_type) {
+ case VAR_STRING:
+ case VAR_NUMBER: {
+ rettv->vval.v_number = (varnumber_T)strlen(
+ tv_get_string(&argvars[0]));
+ break;
+ }
+ case VAR_LIST: {
+ rettv->vval.v_number = tv_list_len(argvars[0].vval.v_list);
+ break;
+ }
+ case VAR_DICT: {
+ rettv->vval.v_number = tv_dict_len(argvars[0].vval.v_dict);
+ break;
+ }
+ case VAR_UNKNOWN:
+ case VAR_SPECIAL:
+ case VAR_FLOAT:
+ case VAR_PARTIAL:
+ case VAR_FUNC: {
+ EMSG(_("E701: Invalid type for len()"));
+ break;
+ }
+ }
+}
+
+static void libcall_common(typval_T *argvars, typval_T *rettv, int out_type)
+{
+ rettv->v_type = out_type;
+ if (out_type != VAR_NUMBER) {
+ rettv->vval.v_string = NULL;
+ }
+
+ if (check_restricted() || check_secure()) {
+ return;
+ }
+
+ // The first two args (libname and funcname) must be strings
+ if (argvars[0].v_type != VAR_STRING || argvars[1].v_type != VAR_STRING) {
+ return;
+ }
+
+ const char *libname = (char *)argvars[0].vval.v_string;
+ const char *funcname = (char *)argvars[1].vval.v_string;
+
+ VarType in_type = argvars[2].v_type;
+
+ // input variables
+ char *str_in = (in_type == VAR_STRING)
+ ? (char *)argvars[2].vval.v_string : NULL;
+ int64_t int_in = argvars[2].vval.v_number;
+
+ // output variables
+ char **str_out = (out_type == VAR_STRING)
+ ? (char **)&rettv->vval.v_string : NULL;
+ int int_out = 0;
+
+ bool success = os_libcall(libname, funcname,
+ str_in, int_in,
+ str_out, &int_out);
+
+ if (!success) {
+ EMSG2(_(e_libcall), funcname);
+ return;
+ }
+
+ if (out_type == VAR_NUMBER) {
+ rettv->vval.v_number = (varnumber_T)int_out;
+ }
+}
+
+/*
+ * "libcall()" function
+ */
+static void f_libcall(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ libcall_common(argvars, rettv, VAR_STRING);
+}
+
+/*
+ * "libcallnr()" function
+ */
+static void f_libcallnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ libcall_common(argvars, rettv, VAR_NUMBER);
+}
+
+/*
+ * "line(string)" function
+ */
+static void f_line(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ linenr_T lnum = 0;
+ pos_T *fp;
+ int fnum;
+
+ fp = var2fpos(&argvars[0], TRUE, &fnum);
+ if (fp != NULL)
+ lnum = fp->lnum;
+ rettv->vval.v_number = lnum;
+}
+
+/*
+ * "line2byte(lnum)" function
+ */
+static void f_line2byte(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const linenr_T lnum = tv_get_lnum(argvars);
+ if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count + 1) {
+ rettv->vval.v_number = -1;
+ } else {
+ rettv->vval.v_number = ml_find_line_or_offset(curbuf, lnum, NULL, false);
+ }
+ if (rettv->vval.v_number >= 0) {
+ rettv->vval.v_number++;
+ }
+}
+
+/*
+ * "lispindent(lnum)" function
+ */
+static void f_lispindent(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const pos_T pos = curwin->w_cursor;
+ const linenr_T lnum = tv_get_lnum(argvars);
+ if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) {
+ curwin->w_cursor.lnum = lnum;
+ rettv->vval.v_number = get_lisp_indent();
+ curwin->w_cursor = pos;
+ } else {
+ rettv->vval.v_number = -1;
+ }
+}
+
+// "list2str()" function
+static void f_list2str(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ garray_T ga;
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+ if (argvars[0].v_type != VAR_LIST) {
+ EMSG(_(e_invarg));
+ return;
+ }
+
+ list_T *const l = argvars[0].vval.v_list;
+ if (l == NULL) {
+ return; // empty list results in empty string
+ }
+
+ ga_init(&ga, 1, 80);
+ char_u buf[MB_MAXBYTES + 1];
+
+ TV_LIST_ITER_CONST(l, li, {
+ buf[utf_char2bytes(tv_get_number(TV_LIST_ITEM_TV(li)), buf)] = NUL;
+ ga_concat(&ga, buf);
+ });
+ ga_append(&ga, NUL);
+
+ rettv->vval.v_string = ga.ga_data;
+}
+
+/*
+ * "localtime()" function
+ */
+static void f_localtime(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = (varnumber_T)time(NULL);
+}
+
+
+static void get_maparg(typval_T *argvars, typval_T *rettv, int exact)
+{
+ char_u *keys_buf = NULL;
+ char_u *rhs;
+ int mode;
+ int abbr = FALSE;
+ int get_dict = FALSE;
+ mapblock_T *mp;
+ int buffer_local;
+
+ // Return empty string for failure.
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+
+ char_u *keys = (char_u *)tv_get_string(&argvars[0]);
+ if (*keys == NUL) {
+ return;
+ }
+
+ char buf[NUMBUFLEN];
+ const char *which;
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ which = tv_get_string_buf_chk(&argvars[1], buf);
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ abbr = tv_get_number(&argvars[2]);
+ if (argvars[3].v_type != VAR_UNKNOWN) {
+ get_dict = tv_get_number(&argvars[3]);
+ }
+ }
+ } else {
+ which = "";
+ }
+ if (which == NULL) {
+ return;
+ }
+
+ mode = get_map_mode((char_u **)&which, 0);
+
+ keys = replace_termcodes(keys, STRLEN(keys), &keys_buf, true, true, true,
+ CPO_TO_CPO_FLAGS);
+ rhs = check_map(keys, mode, exact, false, abbr, &mp, &buffer_local);
+ xfree(keys_buf);
+
+ if (!get_dict) {
+ // Return a string.
+ if (rhs != NULL) {
+ if (*rhs == NUL) {
+ rettv->vval.v_string = vim_strsave((char_u *)"<Nop>");
+ } else {
+ rettv->vval.v_string = (char_u *)str2special_save(
+ (char *)rhs, false, false);
+ }
+ }
+
+ } else {
+ tv_dict_alloc_ret(rettv);
+ if (rhs != NULL) {
+ // Return a dictionary.
+ mapblock_fill_dict(rettv->vval.v_dict, mp, buffer_local, true);
+ }
+ }
+}
+
+/// luaeval() function implementation
+static void f_luaeval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+ FUNC_ATTR_NONNULL_ALL
+{
+ const char *const str = (const char *)tv_get_string_chk(&argvars[0]);
+ if (str == NULL) {
+ return;
+ }
+
+ executor_eval_lua(cstr_as_string((char *)str), &argvars[1], rettv);
+}
+
+/*
+ * "map()" function
+ */
+static void f_map(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ filter_map(argvars, rettv, TRUE);
+}
+
+/*
+ * "maparg()" function
+ */
+static void f_maparg(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ get_maparg(argvars, rettv, TRUE);
+}
+
+/*
+ * "mapcheck()" function
+ */
+static void f_mapcheck(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ get_maparg(argvars, rettv, FALSE);
+}
+
+
+static void find_some_match(typval_T *const argvars, typval_T *const rettv,
+ const SomeMatchType type)
+{
+ char_u *str = NULL;
+ long len = 0;
+ char_u *expr = NULL;
+ regmatch_T regmatch;
+ char_u *save_cpo;
+ long start = 0;
+ long nth = 1;
+ colnr_T startcol = 0;
+ bool match = false;
+ list_T *l = NULL;
+ listitem_T *li = NULL;
+ long idx = 0;
+ char_u *tofree = NULL;
+
+ // Make 'cpoptions' empty, the 'l' flag should not be used here.
+ save_cpo = p_cpo;
+ p_cpo = (char_u *)"";
+
+ rettv->vval.v_number = -1;
+ switch (type) {
+ // matchlist(): return empty list when there are no matches.
+ case kSomeMatchList: {
+ tv_list_alloc_ret(rettv, kListLenMayKnow);
+ break;
+ }
+ // matchstrpos(): return ["", -1, -1, -1]
+ case kSomeMatchStrPos: {
+ tv_list_alloc_ret(rettv, 4);
+ tv_list_append_string(rettv->vval.v_list, "", 0);
+ tv_list_append_number(rettv->vval.v_list, -1);
+ tv_list_append_number(rettv->vval.v_list, -1);
+ tv_list_append_number(rettv->vval.v_list, -1);
+ break;
+ }
+ case kSomeMatchStr: {
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+ break;
+ }
+ case kSomeMatch:
+ case kSomeMatchEnd: {
+ // Do nothing: zero is default.
+ break;
+ }
+ }
+
+ if (argvars[0].v_type == VAR_LIST) {
+ if ((l = argvars[0].vval.v_list) == NULL) {
+ goto theend;
+ }
+ li = tv_list_first(l);
+ } else {
+ expr = str = (char_u *)tv_get_string(&argvars[0]);
+ len = (long)STRLEN(str);
+ }
+
+ char patbuf[NUMBUFLEN];
+ const char *const pat = tv_get_string_buf_chk(&argvars[1], patbuf);
+ if (pat == NULL) {
+ goto theend;
+ }
+
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ bool error = false;
+
+ start = tv_get_number_chk(&argvars[2], &error);
+ if (error) {
+ goto theend;
+ }
+ if (l != NULL) {
+ idx = tv_list_uidx(l, start);
+ if (idx == -1) {
+ goto theend;
+ }
+ li = tv_list_find(l, idx);
+ } else {
+ if (start < 0)
+ start = 0;
+ if (start > len)
+ goto theend;
+ // When "count" argument is there ignore matches before "start",
+ // otherwise skip part of the string. Differs when pattern is "^"
+ // or "\<".
+ if (argvars[3].v_type != VAR_UNKNOWN) {
+ startcol = start;
+ } else {
+ str += start;
+ len -= start;
+ }
+ }
+
+ if (argvars[3].v_type != VAR_UNKNOWN) {
+ nth = tv_get_number_chk(&argvars[3], &error);
+ }
+ if (error) {
+ goto theend;
+ }
+ }
+
+ regmatch.regprog = vim_regcomp((char_u *)pat, RE_MAGIC + RE_STRING);
+ if (regmatch.regprog != NULL) {
+ regmatch.rm_ic = p_ic;
+
+ for (;; ) {
+ if (l != NULL) {
+ if (li == NULL) {
+ match = false;
+ break;
+ }
+ xfree(tofree);
+ tofree = expr = str = (char_u *)encode_tv2echo(TV_LIST_ITEM_TV(li),
+ NULL);
+ if (str == NULL) {
+ break;
+ }
+ }
+
+ match = vim_regexec_nl(&regmatch, str, (colnr_T)startcol);
+
+ if (match && --nth <= 0)
+ break;
+ if (l == NULL && !match)
+ break;
+
+ // Advance to just after the match.
+ if (l != NULL) {
+ li = TV_LIST_ITEM_NEXT(l, li);
+ idx++;
+ } else {
+ startcol = (colnr_T)(regmatch.startp[0]
+ + (*mb_ptr2len)(regmatch.startp[0]) - str);
+ if (startcol > (colnr_T)len || str + startcol <= regmatch.startp[0]) {
+ match = false;
+ break;
+ }
+ }
+ }
+
+ if (match) {
+ switch (type) {
+ case kSomeMatchStrPos: {
+ list_T *const ret_l = rettv->vval.v_list;
+ listitem_T *li1 = tv_list_first(ret_l);
+ listitem_T *li2 = TV_LIST_ITEM_NEXT(ret_l, li1);
+ listitem_T *li3 = TV_LIST_ITEM_NEXT(ret_l, li2);
+ listitem_T *li4 = TV_LIST_ITEM_NEXT(ret_l, li3);
+ xfree(TV_LIST_ITEM_TV(li1)->vval.v_string);
+
+ const size_t rd = (size_t)(regmatch.endp[0] - regmatch.startp[0]);
+ TV_LIST_ITEM_TV(li1)->vval.v_string = xmemdupz(
+ (const char *)regmatch.startp[0], rd);
+ TV_LIST_ITEM_TV(li3)->vval.v_number = (varnumber_T)(
+ regmatch.startp[0] - expr);
+ TV_LIST_ITEM_TV(li4)->vval.v_number = (varnumber_T)(
+ regmatch.endp[0] - expr);
+ if (l != NULL) {
+ TV_LIST_ITEM_TV(li2)->vval.v_number = (varnumber_T)idx;
+ }
+ break;
+ }
+ case kSomeMatchList: {
+ // Return list with matched string and submatches.
+ for (int i = 0; i < NSUBEXP; i++) {
+ if (regmatch.endp[i] == NULL) {
+ tv_list_append_string(rettv->vval.v_list, NULL, 0);
+ } else {
+ tv_list_append_string(rettv->vval.v_list,
+ (const char *)regmatch.startp[i],
+ (regmatch.endp[i] - regmatch.startp[i]));
+ }
+ }
+ break;
+ }
+ case kSomeMatchStr: {
+ // Return matched string.
+ if (l != NULL) {
+ tv_copy(TV_LIST_ITEM_TV(li), rettv);
+ } else {
+ rettv->vval.v_string = (char_u *)xmemdupz(
+ (const char *)regmatch.startp[0],
+ (size_t)(regmatch.endp[0] - regmatch.startp[0]));
+ }
+ break;
+ }
+ case kSomeMatch:
+ case kSomeMatchEnd: {
+ if (l != NULL) {
+ rettv->vval.v_number = idx;
+ } else {
+ if (type == kSomeMatch) {
+ rettv->vval.v_number =
+ (varnumber_T)(regmatch.startp[0] - str);
+ } else {
+ rettv->vval.v_number =
+ (varnumber_T)(regmatch.endp[0] - str);
+ }
+ rettv->vval.v_number += (varnumber_T)(str - expr);
+ }
+ break;
+ }
+ }
+ }
+ vim_regfree(regmatch.regprog);
+ }
+
+theend:
+ if (type == kSomeMatchStrPos && l == NULL && rettv->vval.v_list != NULL) {
+ // matchstrpos() without a list: drop the second item
+ list_T *const ret_l = rettv->vval.v_list;
+ tv_list_item_remove(ret_l, TV_LIST_ITEM_NEXT(ret_l, tv_list_first(ret_l)));
+ }
+
+ xfree(tofree);
+ p_cpo = save_cpo;
+}
+
+/*
+ * "match()" function
+ */
+static void f_match(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ find_some_match(argvars, rettv, kSomeMatch);
+}
+
+/*
+ * "matchadd()" function
+ */
+static 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 = tv_get_number_chk(&argvars[2], &error);
+ if (argvars[3].v_type != VAR_UNKNOWN) {
+ id = 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) {
+ EMSGN(_("E798: ID is reserved for \":match\": %" PRId64), id);
+ return;
+ }
+
+ rettv->vval.v_number = match_add(win, grp, pat, prio, id, NULL, conceal_char);
+}
+
+static 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) {
+ EMSG2(_(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 = tv_get_number_chk(&argvars[2], &error);
+ if (argvars[3].v_type != VAR_UNKNOWN) {
+ id = 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) {
+ EMSGN(_("E798: ID is reserved for \"match\": %" PRId64), id);
+ return;
+ }
+
+ rettv->vval.v_number = match_add(win, group, NULL, prio, id, l, conceal_char);
+}
+
+/*
+ * "matcharg()" function
+ */
+static void f_matcharg(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const int id = 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 = (matchitem_T *)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
+ */
+static void f_matchdelete(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = match_delete(curwin,
+ (int)tv_get_number(&argvars[0]), true);
+}
+
+/*
+ * "matchend()" function
+ */
+static void f_matchend(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ find_some_match(argvars, rettv, kSomeMatchEnd);
+}
+
+/*
+ * "matchlist()" function
+ */
+static void f_matchlist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ find_some_match(argvars, rettv, kSomeMatchList);
+}
+
+/*
+ * "matchstr()" function
+ */
+static void f_matchstr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ find_some_match(argvars, rettv, kSomeMatchStr);
+}
+
+/// "matchstrpos()" function
+static void f_matchstrpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ find_some_match(argvars, rettv, kSomeMatchStrPos);
+}
+
+/// Get maximal/minimal number value in a list or dictionary
+///
+/// @param[in] tv List or dictionary to work with. If it contains something
+/// that is not an integer number (or cannot be coerced to
+/// it) error is given.
+/// @param[out] rettv Location where result will be saved. Only assigns
+/// vval.v_number, type is not touched. Returns zero for
+/// empty lists/dictionaries.
+/// @param[in] domax Determines whether maximal or minimal value is desired.
+static void max_min(const typval_T *const tv, typval_T *const rettv,
+ const bool domax)
+ FUNC_ATTR_NONNULL_ALL
+{
+ bool error = false;
+
+ rettv->vval.v_number = 0;
+ varnumber_T n = (domax ? VARNUMBER_MIN : VARNUMBER_MAX);
+ if (tv->v_type == VAR_LIST) {
+ if (tv_list_len(tv->vval.v_list) == 0) {
+ return;
+ }
+ TV_LIST_ITER_CONST(tv->vval.v_list, li, {
+ const varnumber_T i = tv_get_number_chk(TV_LIST_ITEM_TV(li), &error);
+ if (error) {
+ return;
+ }
+ if (domax ? i > n : i < n) {
+ n = i;
+ }
+ });
+ } else if (tv->v_type == VAR_DICT) {
+ if (tv_dict_len(tv->vval.v_dict) == 0) {
+ return;
+ }
+ TV_DICT_ITER(tv->vval.v_dict, di, {
+ const varnumber_T i = tv_get_number_chk(&di->di_tv, &error);
+ if (error) {
+ return;
+ }
+ if (domax ? i > n : i < n) {
+ n = i;
+ }
+ });
+ } else {
+ EMSG2(_(e_listdictarg), domax ? "max()" : "min()");
+ return;
+ }
+ rettv->vval.v_number = n;
+}
+
+/*
+ * "max()" function
+ */
+static void f_max(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ max_min(argvars, rettv, TRUE);
+}
+
+/*
+ * "min()" function
+ */
+static void f_min(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ max_min(argvars, rettv, FALSE);
+}
+
+/*
+ * "mkdir()" function
+ */
+static void f_mkdir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int prot = 0755; // -V536
+
+ rettv->vval.v_number = FAIL;
+ if (check_restricted() || check_secure())
+ return;
+
+ char buf[NUMBUFLEN];
+ const char *const dir = tv_get_string_buf(&argvars[0], buf);
+ if (*dir == NUL) {
+ return;
+ }
+
+ if (*path_tail((char_u *)dir) == NUL) {
+ // Remove trailing slashes.
+ *path_tail_with_sep((char_u *)dir) = NUL;
+ }
+
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ prot = tv_get_number_chk(&argvars[2], NULL);
+ if (prot == -1) {
+ return;
+ }
+ }
+ if (strcmp(tv_get_string(&argvars[1]), "p") == 0) {
+ char *failed_dir;
+ int ret = os_mkdir_recurse(dir, prot, &failed_dir);
+ if (ret != 0) {
+ EMSG3(_(e_mkdir), failed_dir, os_strerror(ret));
+ xfree(failed_dir);
+ rettv->vval.v_number = FAIL;
+ return;
+ } else {
+ rettv->vval.v_number = OK;
+ return;
+ }
+ }
+ }
+ rettv->vval.v_number = vim_mkdir_emsg(dir, prot);
+}
+
+/// "mode()" function
+static void f_mode(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char *mode = get_mode();
+
+ // Clear out the minor mode when the argument is not a non-zero number or
+ // non-empty string.
+ if (!non_zero_arg(&argvars[0])) {
+ mode[1] = NUL;
+ }
+
+ rettv->vval.v_string = (char_u *)mode;
+ rettv->v_type = VAR_STRING;
+}
+
+/// "msgpackdump()" function
+static void f_msgpackdump(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (argvars[0].v_type != VAR_LIST) {
+ EMSG2(_(e_listarg), "msgpackdump()");
+ return;
+ }
+ list_T *const ret_list = tv_list_alloc_ret(rettv, kListLenMayKnow);
+ list_T *const list = argvars[0].vval.v_list;
+ msgpack_packer *lpacker = msgpack_packer_new(ret_list, &encode_list_write);
+ const char *const msg = _("msgpackdump() argument, index %i");
+ // Assume that translation will not take more then 4 times more space
+ char msgbuf[sizeof("msgpackdump() argument, index ") * 4 + NUMBUFLEN];
+ int idx = 0;
+ TV_LIST_ITER(list, li, {
+ vim_snprintf(msgbuf, sizeof(msgbuf), (char *)msg, idx);
+ idx++;
+ if (encode_vim_to_msgpack(lpacker, TV_LIST_ITEM_TV(li), msgbuf) == FAIL) {
+ break;
+ }
+ });
+ msgpack_packer_free(lpacker);
+}
+
+/// "msgpackparse" function
+static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (argvars[0].v_type != VAR_LIST) {
+ EMSG2(_(e_listarg), "msgpackparse()");
+ return;
+ }
+ list_T *const ret_list = tv_list_alloc_ret(rettv, kListLenMayKnow);
+ const list_T *const list = argvars[0].vval.v_list;
+ if (tv_list_len(list) == 0) {
+ return;
+ }
+ if (TV_LIST_ITEM_TV(tv_list_first(list))->v_type != VAR_STRING) {
+ EMSG2(_(e_invarg2), "List item is not a string");
+ return;
+ }
+ ListReaderState lrstate = encode_init_lrstate(list);
+ msgpack_unpacker *const unpacker = msgpack_unpacker_new(IOSIZE);
+ if (unpacker == NULL) {
+ EMSG(_(e_outofmem));
+ return;
+ }
+ msgpack_unpacked unpacked;
+ msgpack_unpacked_init(&unpacked);
+ do {
+ if (!msgpack_unpacker_reserve_buffer(unpacker, IOSIZE)) {
+ EMSG(_(e_outofmem));
+ goto f_msgpackparse_exit;
+ }
+ size_t read_bytes;
+ const int rlret = encode_read_from_list(
+ &lrstate, msgpack_unpacker_buffer(unpacker), IOSIZE, &read_bytes);
+ if (rlret == FAIL) {
+ EMSG2(_(e_invarg2), "List item is not a string");
+ goto f_msgpackparse_exit;
+ }
+ msgpack_unpacker_buffer_consumed(unpacker, read_bytes);
+ if (read_bytes == 0) {
+ break;
+ }
+ while (unpacker->off < unpacker->used) {
+ const msgpack_unpack_return result = msgpack_unpacker_next(unpacker,
+ &unpacked);
+ if (result == MSGPACK_UNPACK_PARSE_ERROR) {
+ EMSG2(_(e_invarg2), "Failed to parse msgpack string");
+ goto f_msgpackparse_exit;
+ }
+ if (result == MSGPACK_UNPACK_NOMEM_ERROR) {
+ EMSG(_(e_outofmem));
+ goto f_msgpackparse_exit;
+ }
+ if (result == MSGPACK_UNPACK_SUCCESS) {
+ typval_T tv = { .v_type = VAR_UNKNOWN };
+ if (msgpack_to_vim(unpacked.data, &tv) == FAIL) {
+ EMSG2(_(e_invarg2), "Failed to convert msgpack string");
+ goto f_msgpackparse_exit;
+ }
+ tv_list_append_owned_tv(ret_list, tv);
+ }
+ if (result == MSGPACK_UNPACK_CONTINUE) {
+ if (rlret == OK) {
+ EMSG2(_(e_invarg2), "Incomplete msgpack string");
+ }
+ break;
+ }
+ }
+ if (rlret == OK) {
+ break;
+ }
+ } while (true);
+
+f_msgpackparse_exit:
+ msgpack_unpacked_destroy(&unpacked);
+ msgpack_unpacker_free(unpacker);
+ return;
+}
+
+/*
+ * "nextnonblank()" function
+ */
+static void f_nextnonblank(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ linenr_T lnum;
+
+ for (lnum = tv_get_lnum(argvars);; lnum++) {
+ if (lnum < 0 || lnum > curbuf->b_ml.ml_line_count) {
+ lnum = 0;
+ break;
+ }
+ if (*skipwhite(ml_get(lnum)) != NUL) {
+ break;
+ }
+ }
+ rettv->vval.v_number = lnum;
+}
+
+/*
+ * "nr2char()" function
+ */
+static void f_nr2char(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ if (!tv_check_num(&argvars[1])) {
+ return;
+ }
+ }
+
+ bool error = false;
+ const varnumber_T num = tv_get_number_chk(&argvars[0], &error);
+ if (error) {
+ return;
+ }
+ if (num < 0) {
+ EMSG(_("E5070: Character number must not be less than zero"));
+ return;
+ }
+ if (num > INT_MAX) {
+ emsgf(_("E5071: Character number must not be greater than INT_MAX (%i)"),
+ INT_MAX);
+ return;
+ }
+
+ char buf[MB_MAXBYTES];
+ const int len = utf_char2bytes((int)num, (char_u *)buf);
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = xmemdupz(buf, (size_t)len);
+}
+
+/*
+ * "or(expr, expr)" function
+ */
+static void f_or(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL)
+ | tv_get_number_chk(&argvars[1], NULL);
+}
+
+/*
+ * "pathshorten()" function
+ */
+static void f_pathshorten(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_STRING;
+ const char *const s = tv_get_string_chk(&argvars[0]);
+ if (!s) {
+ return;
+ }
+ rettv->vval.v_string = shorten_dir((char_u *)xstrdup(s));
+}
+
+/*
+ * "pow()" function
+ */
+static void f_pow(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ float_T fx;
+ float_T fy;
+
+ rettv->v_type = VAR_FLOAT;
+ if (tv_get_float_chk(argvars, &fx) && tv_get_float_chk(&argvars[1], &fy)) {
+ rettv->vval.v_float = pow(fx, fy);
+ } else {
+ rettv->vval.v_float = 0.0;
+ }
+}
+
+/*
+ * "prevnonblank()" function
+ */
+static void f_prevnonblank(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ linenr_T lnum = tv_get_lnum(argvars);
+ if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count) {
+ lnum = 0;
+ } else {
+ while (lnum >= 1 && *skipwhite(ml_get(lnum)) == NUL) {
+ lnum--;
+ }
+ }
+ rettv->vval.v_number = lnum;
+}
+
+/*
+ * "printf()" function
+ */
+static void f_printf(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+ {
+ int len;
+ int saved_did_emsg = did_emsg;
+
+ // Get the required length, allocate the buffer and do it for real.
+ did_emsg = false;
+ char buf[NUMBUFLEN];
+ const char *fmt = tv_get_string_buf(&argvars[0], buf);
+ len = vim_vsnprintf_typval(NULL, 0, fmt, dummy_ap, argvars + 1);
+ if (!did_emsg) {
+ char *s = xmalloc(len + 1);
+ rettv->vval.v_string = (char_u *)s;
+ (void)vim_vsnprintf_typval(s, len + 1, fmt, dummy_ap, argvars + 1);
+ }
+ did_emsg |= saved_did_emsg;
+ }
+}
+
+// "prompt_setcallback({buffer}, {callback})" function
+static void f_prompt_setcallback(typval_T *argvars,
+ typval_T *rettv, FunPtr fptr)
+{
+ buf_T *buf;
+ Callback prompt_callback = { .type = kCallbackNone };
+
+ if (check_secure()) {
+ return;
+ }
+ buf = tv_get_buf(&argvars[0], false);
+ if (buf == NULL) {
+ return;
+ }
+
+ if (argvars[1].v_type != VAR_STRING || *argvars[1].vval.v_string != NUL) {
+ if (!callback_from_typval(&prompt_callback, &argvars[1])) {
+ return;
+ }
+ }
+
+ callback_free(&buf->b_prompt_callback);
+ buf->b_prompt_callback = prompt_callback;
+}
+
+// "prompt_setinterrupt({buffer}, {callback})" function
+static void f_prompt_setinterrupt(typval_T *argvars,
+ typval_T *rettv, FunPtr fptr)
+{
+ buf_T *buf;
+ Callback interrupt_callback = { .type = kCallbackNone };
+
+ if (check_secure()) {
+ return;
+ }
+ buf = tv_get_buf(&argvars[0], false);
+ if (buf == NULL) {
+ return;
+ }
+
+ if (argvars[1].v_type != VAR_STRING || *argvars[1].vval.v_string != NUL) {
+ if (!callback_from_typval(&interrupt_callback, &argvars[1])) {
+ return;
+ }
+ }
+
+ callback_free(&buf->b_prompt_interrupt);
+ buf->b_prompt_interrupt= interrupt_callback;
+}
+
+// "prompt_setprompt({buffer}, {text})" function
+static void f_prompt_setprompt(typval_T *argvars,
+ typval_T *rettv, FunPtr fptr)
+{
+ buf_T *buf;
+ const char_u *text;
+
+ if (check_secure()) {
+ return;
+ }
+ buf = tv_get_buf(&argvars[0], false);
+ if (buf == NULL) {
+ return;
+ }
+
+ text = (const char_u *)tv_get_string(&argvars[1]);
+ xfree(buf->b_prompt_text);
+ buf->b_prompt_text = vim_strsave(text);
+}
+
+// "pum_getpos()" function
+static void f_pum_getpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ tv_dict_alloc_ret(rettv);
+ pum_set_event_info(rettv->vval.v_dict);
+}
+
+/*
+ * "pumvisible()" function
+ */
+static void f_pumvisible(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (pum_visible())
+ rettv->vval.v_number = 1;
+}
+
+/*
+ * "pyeval()" function
+ */
+static void f_pyeval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ script_host_eval("python", argvars, rettv);
+}
+
+/*
+ * "py3eval()" function
+ */
+static void f_py3eval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ script_host_eval("python3", argvars, rettv);
+}
+
+// "pyxeval()" function
+static void f_pyxeval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ init_pyxversion();
+ if (p_pyx == 2) {
+ f_pyeval(argvars, rettv, NULL);
+ } else {
+ f_py3eval(argvars, rettv, NULL);
+ }
+}
+
+/*
+ * "range()" function
+ */
+static void f_range(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ varnumber_T start;
+ varnumber_T end;
+ varnumber_T stride = 1;
+ varnumber_T i;
+ bool error = false;
+
+ start = tv_get_number_chk(&argvars[0], &error);
+ if (argvars[1].v_type == VAR_UNKNOWN) {
+ end = start - 1;
+ start = 0;
+ } else {
+ end = tv_get_number_chk(&argvars[1], &error);
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ stride = tv_get_number_chk(&argvars[2], &error);
+ }
+ }
+
+ if (error) {
+ return; // Type error; errmsg already given.
+ }
+ if (stride == 0) {
+ EMSG(_("E726: Stride is zero"));
+ } else if (stride > 0 ? end + 1 < start : end - 1 > start) {
+ EMSG(_("E727: Start past end"));
+ } else {
+ tv_list_alloc_ret(rettv, (end - start) / stride);
+ for (i = start; stride > 0 ? i <= end : i >= end; i += stride) {
+ tv_list_append_number(rettv->vval.v_list, (varnumber_T)i);
+ }
+ }
+}
+
+// Evaluate "expr" for readdir().
+static varnumber_T readdir_checkitem(typval_T *expr, const char *name)
+{
+ typval_T save_val;
+ typval_T rettv;
+ typval_T argv[2];
+ varnumber_T retval = 0;
+ bool error = false;
+
+ prepare_vimvar(VV_VAL, &save_val);
+ set_vim_var_string(VV_VAL, name, -1);
+ argv[0].v_type = VAR_STRING;
+ argv[0].vval.v_string = (char_u *)name;
+
+ if (eval_expr_typval(expr, argv, 1, &rettv) == FAIL) {
+ goto theend;
+ }
+
+ retval = tv_get_number_chk(&rettv, &error);
+ if (error) {
+ retval = -1;
+ }
+
+ tv_clear(&rettv);
+
+theend:
+ set_vim_var_string(VV_VAL, NULL, 0);
+ restore_vimvar(VV_VAL, &save_val);
+ return retval;
+}
+
+// "readdir()" function
+static void f_readdir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ typval_T *expr;
+ const char *path;
+ garray_T ga;
+ Directory dir;
+
+ tv_list_alloc_ret(rettv, kListLenUnknown);
+ path = tv_get_string(&argvars[0]);
+ expr = &argvars[1];
+ ga_init(&ga, (int)sizeof(char *), 20);
+
+ if (!os_scandir(&dir, path)) {
+ smsg(_(e_notopen), path);
+ } else {
+ for (;;) {
+ bool ignore;
+
+ path = os_scandir_next(&dir);
+ if (path == NULL) {
+ break;
+ }
+
+ ignore = (path[0] == '.'
+ && (path[1] == NUL || (path[1] == '.' && path[2] == NUL)));
+ if (!ignore && expr->v_type != VAR_UNKNOWN) {
+ varnumber_T r = readdir_checkitem(expr, path);
+
+ if (r < 0) {
+ break;
+ }
+ if (r == 0) {
+ ignore = true;
+ }
+ }
+
+ if (!ignore) {
+ ga_grow(&ga, 1);
+ ((char **)ga.ga_data)[ga.ga_len++] = xstrdup(path);
+ }
+ }
+
+ os_closedir(&dir);
+ }
+
+ if (rettv->vval.v_list != NULL && ga.ga_len > 0) {
+ sort_strings((char_u **)ga.ga_data, ga.ga_len);
+ for (int i = 0; i < ga.ga_len; i++) {
+ path = ((const char **)ga.ga_data)[i];
+ tv_list_append_string(rettv->vval.v_list, path, -1);
+ }
+ }
+ ga_clear_strings(&ga);
+}
+
+/*
+ * "readfile()" function
+ */
+static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ bool binary = false;
+ FILE *fd;
+ char_u buf[(IOSIZE/256) * 256]; // rounded to avoid odd + 1
+ int io_size = sizeof(buf);
+ int readlen; // size of last fread()
+ char_u *prev = NULL; // previously read bytes, if any
+ long prevlen = 0; // length of data in prev
+ long prevsize = 0; // size of prev buffer
+ long maxline = MAXLNUM;
+
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ if (strcmp(tv_get_string(&argvars[1]), "b") == 0) {
+ binary = true;
+ }
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ maxline = tv_get_number(&argvars[2]);
+ }
+ }
+
+ list_T *const l = tv_list_alloc_ret(rettv, kListLenUnknown);
+
+ // Always open the file in binary mode, library functions have a mind of
+ // their own about CR-LF conversion.
+ const char *const fname = tv_get_string(&argvars[0]);
+ if (*fname == NUL || (fd = os_fopen(fname, READBIN)) == NULL) {
+ EMSG2(_(e_notopen), *fname == NUL ? _("<empty>") : fname);
+ return;
+ }
+
+ while (maxline < 0 || tv_list_len(l) < maxline) {
+ readlen = (int)fread(buf, 1, io_size, fd);
+
+ // This for loop processes what was read, but is also entered at end
+ // of file so that either:
+ // - an incomplete line gets written
+ // - a "binary" file gets an empty line at the end if it ends in a
+ // newline.
+ char_u *p; // Position in buf.
+ char_u *start; // Start of current line.
+ for (p = buf, start = buf;
+ p < buf + readlen || (readlen <= 0 && (prevlen > 0 || binary));
+ p++) {
+ if (*p == '\n' || readlen <= 0) {
+ char_u *s = NULL;
+ size_t len = p - start;
+
+ // Finished a line. Remove CRs before NL.
+ if (readlen > 0 && !binary) {
+ while (len > 0 && start[len - 1] == '\r') {
+ len--;
+ }
+ // removal may cross back to the "prev" string
+ if (len == 0) {
+ while (prevlen > 0 && prev[prevlen - 1] == '\r') {
+ prevlen--;
+ }
+ }
+ }
+ if (prevlen == 0) {
+ assert(len < INT_MAX);
+ s = vim_strnsave(start, (int)len);
+ } else {
+ /* Change "prev" buffer to be the right size. This way
+ * the bytes are only copied once, and very long lines are
+ * allocated only once. */
+ s = xrealloc(prev, prevlen + len + 1);
+ memcpy(s + prevlen, start, len);
+ s[prevlen + len] = NUL;
+ prev = NULL; // the list will own the string
+ prevlen = prevsize = 0;
+ }
+
+ tv_list_append_owned_tv(l, (typval_T) {
+ .v_type = VAR_STRING,
+ .v_lock = VAR_UNLOCKED,
+ .vval.v_string = s,
+ });
+
+ start = p + 1; // Step over newline.
+ if (maxline < 0) {
+ if (tv_list_len(l) > -maxline) {
+ assert(tv_list_len(l) == 1 + (-maxline));
+ tv_list_item_remove(l, tv_list_first(l));
+ }
+ } else if (tv_list_len(l) >= maxline) {
+ assert(tv_list_len(l) == maxline);
+ break;
+ }
+ if (readlen <= 0) {
+ break;
+ }
+ } else if (*p == NUL) {
+ *p = '\n';
+ // Check for utf8 "bom"; U+FEFF is encoded as EF BB BF. Do this
+ // when finding the BF and check the previous two bytes.
+ } else if (*p == 0xbf && !binary) {
+ // Find the two bytes before the 0xbf. If p is at buf, or buf + 1,
+ // these may be in the "prev" string.
+ char_u back1 = p >= buf + 1 ? p[-1]
+ : prevlen >= 1 ? prev[prevlen - 1] : NUL;
+ char_u back2 = p >= buf + 2 ? p[-2]
+ : p == buf + 1 && prevlen >= 1 ? prev[prevlen - 1]
+ : prevlen >= 2 ? prev[prevlen - 2] : NUL;
+
+ if (back2 == 0xef && back1 == 0xbb) {
+ char_u *dest = p - 2;
+
+ // Usually a BOM is at the beginning of a file, and so at
+ // the beginning of a line; then we can just step over it.
+ if (start == dest) {
+ start = p + 1;
+ } else {
+ // have to shuffle buf to close gap
+ int adjust_prevlen = 0;
+
+ if (dest < buf) { // -V782
+ adjust_prevlen = (int)(buf - dest); // -V782
+ // adjust_prevlen must be 1 or 2.
+ dest = buf;
+ }
+ if (readlen > p - buf + 1)
+ memmove(dest, p + 1, readlen - (p - buf) - 1);
+ readlen -= 3 - adjust_prevlen;
+ prevlen -= adjust_prevlen;
+ p = dest - 1;
+ }
+ }
+ }
+ } // for
+
+ if ((maxline >= 0 && tv_list_len(l) >= maxline) || readlen <= 0) {
+ break;
+ }
+ if (start < p) {
+ // There's part of a line in buf, store it in "prev".
+ if (p - start + prevlen >= prevsize) {
+ /* A common use case is ordinary text files and "prev" gets a
+ * fragment of a line, so the first allocation is made
+ * small, to avoid repeatedly 'allocing' large and
+ * 'reallocing' small. */
+ if (prevsize == 0)
+ prevsize = (long)(p - start);
+ else {
+ long grow50pc = (prevsize * 3) / 2;
+ long growmin = (long)((p - start) * 2 + prevlen);
+ prevsize = grow50pc > growmin ? grow50pc : growmin;
+ }
+ prev = xrealloc(prev, prevsize);
+ }
+ // Add the line part to end of "prev".
+ memmove(prev + prevlen, start, p - start);
+ prevlen += (long)(p - start);
+ }
+ } // while
+
+ xfree(prev);
+ fclose(fd);
+}
+
+// "reg_executing()" function
+static void f_reg_executing(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ return_register(reg_executing, rettv);
+}
+
+// "reg_recording()" function
+static void f_reg_recording(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ return_register(reg_recording, rettv);
+}
+
+/// list2proftime - convert a List to proftime_T
+///
+/// @param arg The input list, must be of type VAR_LIST and have
+/// exactly 2 items
+/// @param[out] tm The proftime_T representation of `arg`
+/// @return OK In case of success, FAIL in case of error
+static int list2proftime(typval_T *arg, proftime_T *tm) FUNC_ATTR_NONNULL_ALL
+{
+ if (arg->v_type != VAR_LIST || tv_list_len(arg->vval.v_list) != 2) {
+ return FAIL;
+ }
+
+ bool error = false;
+ varnumber_T n1 = tv_list_find_nr(arg->vval.v_list, 0L, &error);
+ varnumber_T n2 = tv_list_find_nr(arg->vval.v_list, 1L, &error);
+ if (error) {
+ return FAIL;
+ }
+
+ // in f_reltime() we split up the 64-bit proftime_T into two 32-bit
+ // values, now we combine them again.
+ union {
+ struct { int32_t low, high; } split;
+ proftime_T prof;
+ } u = { .split.high = n1, .split.low = n2 };
+
+ *tm = u.prof;
+
+ return OK;
+}
+
+/// f_reltime - return an item that represents a time value
+///
+/// @param[out] rettv Without an argument it returns the current time. With
+/// one argument it returns the time passed since the argument.
+/// With two arguments it returns the time passed between
+/// the two arguments.
+static void f_reltime(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ proftime_T res;
+ proftime_T start;
+
+ if (argvars[0].v_type == VAR_UNKNOWN) {
+ // no arguments: get current time.
+ res = profile_start();
+ } else if (argvars[1].v_type == VAR_UNKNOWN) {
+ if (list2proftime(&argvars[0], &res) == FAIL) {
+ return;
+ }
+ res = profile_end(res);
+ } else {
+ // two arguments: compute the difference.
+ if (list2proftime(&argvars[0], &start) == FAIL
+ || list2proftime(&argvars[1], &res) == FAIL) {
+ return;
+ }
+ res = profile_sub(res, start);
+ }
+
+ // we have to store the 64-bit proftime_T inside of a list of int's
+ // (varnumber_T is defined as int). For all our supported platforms, int's
+ // are at least 32-bits wide. So we'll use two 32-bit values to store it.
+ union {
+ struct { int32_t low, high; } split;
+ proftime_T prof;
+ } u = { .prof = res };
+
+ // statically assert that the union type conv will provide the correct
+ // results, if varnumber_T or proftime_T change, the union cast will need
+ // to be revised.
+ STATIC_ASSERT(sizeof(u.prof) == sizeof(u) && sizeof(u.split) == sizeof(u),
+ "type punning will produce incorrect results on this platform");
+
+ tv_list_alloc_ret(rettv, 2);
+ tv_list_append_number(rettv->vval.v_list, u.split.high);
+ tv_list_append_number(rettv->vval.v_list, u.split.low);
+}
+
+/// "reltimestr()" function
+static void f_reltimestr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+ FUNC_ATTR_NONNULL_ALL
+{
+ proftime_T tm;
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+ if (list2proftime(&argvars[0], &tm) == OK) {
+ rettv->vval.v_string = (char_u *)xstrdup(profile_msg(tm));
+ }
+}
+
+/*
+ * "remove()" function
+ */
+static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ list_T *l;
+ listitem_T *item, *item2;
+ listitem_T *li;
+ long idx;
+ long end;
+ dict_T *d;
+ dictitem_T *di;
+ const char *const arg_errmsg = N_("remove() argument");
+
+ if (argvars[0].v_type == VAR_DICT) {
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ EMSG2(_(e_toomanyarg), "remove()");
+ } else if ((d = argvars[0].vval.v_dict) != NULL
+ && !tv_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE)) {
+ const char *key = tv_get_string_chk(&argvars[1]);
+ if (key != NULL) {
+ di = tv_dict_find(d, key, -1);
+ if (di == NULL) {
+ EMSG2(_(e_dictkey), key);
+ } else if (!var_check_fixed(di->di_flags, arg_errmsg, TV_TRANSLATE)
+ && !var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE)) {
+ *rettv = di->di_tv;
+ di->di_tv = TV_INITIAL_VALUE;
+ tv_dict_item_remove(d, di);
+ if (tv_dict_is_watched(d)) {
+ tv_dict_watcher_notify(d, key, NULL, rettv);
+ }
+ }
+ }
+ }
+ } else if (argvars[0].v_type != VAR_LIST) {
+ EMSG2(_(e_listdictarg), "remove()");
+ } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)),
+ arg_errmsg, TV_TRANSLATE)) {
+ bool error = false;
+
+ idx = tv_get_number_chk(&argvars[1], &error);
+ if (error) {
+ // Type error: do nothing, errmsg already given.
+ } else if ((item = tv_list_find(l, idx)) == NULL) {
+ EMSGN(_(e_listidx), idx);
+ } else {
+ if (argvars[2].v_type == VAR_UNKNOWN) {
+ // Remove one item, return its value.
+ tv_list_drop_items(l, item, item);
+ *rettv = *TV_LIST_ITEM_TV(item);
+ xfree(item);
+ } else {
+ // Remove range of items, return list with values.
+ end = tv_get_number_chk(&argvars[2], &error);
+ if (error) {
+ // Type error: do nothing.
+ } else if ((item2 = tv_list_find(l, end)) == NULL) {
+ EMSGN(_(e_listidx), end);
+ } else {
+ int cnt = 0;
+
+ for (li = item; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) {
+ cnt++;
+ if (li == item2) {
+ break;
+ }
+ }
+ if (li == NULL) { // Didn't find "item2" after "item".
+ EMSG(_(e_invrange));
+ } else {
+ tv_list_move_items(l, item, item2, tv_list_alloc_ret(rettv, cnt),
+ cnt);
+ }
+ }
+ }
+ }
+ }
+}
+
+/*
+ * "rename({from}, {to})" function
+ */
+static void f_rename(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (check_restricted() || check_secure()) {
+ rettv->vval.v_number = -1;
+ } else {
+ char buf[NUMBUFLEN];
+ rettv->vval.v_number = vim_rename(
+ (const char_u *)tv_get_string(&argvars[0]),
+ (const char_u *)tv_get_string_buf(&argvars[1], buf));
+ }
+}
+
+/*
+ * "repeat()" function
+ */
+static void f_repeat(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ varnumber_T n = tv_get_number(&argvars[1]);
+ if (argvars[0].v_type == VAR_LIST) {
+ tv_list_alloc_ret(rettv, (n > 0) * n * tv_list_len(argvars[0].vval.v_list));
+ while (n-- > 0) {
+ tv_list_extend(rettv->vval.v_list, argvars[0].vval.v_list, NULL);
+ }
+ } else {
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+ if (n <= 0) {
+ return;
+ }
+
+ const char *const p = tv_get_string(&argvars[0]);
+
+ const size_t slen = strlen(p);
+ if (slen == 0) {
+ return;
+ }
+ const size_t len = slen * n;
+ // Detect overflow.
+ if (len / n != slen) {
+ return;
+ }
+
+ char *const r = xmallocz(len);
+ for (varnumber_T i = 0; i < n; i++) {
+ memmove(r + i * slen, p, slen);
+ }
+
+ rettv->vval.v_string = (char_u *)r;
+ }
+}
+
+/*
+ * "resolve()" function
+ */
+static void f_resolve(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_STRING;
+ const char *fname = tv_get_string(&argvars[0]);
+#ifdef WIN32
+ char *v = os_resolve_shortcut(fname);
+ if (v == NULL) {
+ if (os_is_reparse_point_include(fname)) {
+ v = os_realpath(fname, v);
+ }
+ }
+ rettv->vval.v_string = (char_u *)(v == NULL ? xstrdup(fname) : v);
+#else
+# ifdef HAVE_READLINK
+ {
+ bool is_relative_to_current = false;
+ bool has_trailing_pathsep = false;
+ int limit = 100;
+
+ char *p = xstrdup(fname);
+
+ if (p[0] == '.' && (vim_ispathsep(p[1])
+ || (p[1] == '.' && (vim_ispathsep(p[2]))))) {
+ is_relative_to_current = true;
+ }
+
+ ptrdiff_t len = (ptrdiff_t)strlen(p);
+ if (len > 0 && after_pathsep(p, p + len)) {
+ has_trailing_pathsep = true;
+ p[len - 1] = NUL; // The trailing slash breaks readlink().
+ }
+
+ char *q = (char *)path_next_component(p);
+ char *remain = NULL;
+ if (*q != NUL) {
+ // Separate the first path component in "p", and keep the
+ // remainder (beginning with the path separator).
+ remain = xstrdup(q - 1);
+ q[-1] = NUL;
+ }
+
+ char *const buf = xmallocz(MAXPATHL);
+
+ char *cpy;
+ for (;; ) {
+ for (;; ) {
+ len = readlink(p, buf, MAXPATHL);
+ if (len <= 0) {
+ break;
+ }
+ buf[len] = NUL;
+
+ if (limit-- == 0) {
+ xfree(p);
+ xfree(remain);
+ EMSG(_("E655: Too many symbolic links (cycle?)"));
+ rettv->vval.v_string = NULL;
+ xfree(buf);
+ return;
+ }
+
+ // Ensure that the result will have a trailing path separator
+ // if the argument has one. */
+ if (remain == NULL && has_trailing_pathsep) {
+ add_pathsep(buf);
+ }
+
+ // Separate the first path component in the link value and
+ // concatenate the remainders. */
+ q = (char *)path_next_component(vim_ispathsep(*buf) ? buf + 1 : buf);
+ if (*q != NUL) {
+ cpy = remain;
+ remain = (remain
+ ? (char *)concat_str((char_u *)q - 1, (char_u *)remain)
+ : xstrdup(q - 1));
+ xfree(cpy);
+ q[-1] = NUL;
+ }
+
+ q = (char *)path_tail((char_u *)p);
+ if (q > p && *q == NUL) {
+ // Ignore trailing path separator.
+ q[-1] = NUL;
+ q = (char *)path_tail((char_u *)p);
+ }
+ if (q > p && !path_is_absolute((const char_u *)buf)) {
+ // Symlink is relative to directory of argument. Replace the
+ // symlink with the resolved name in the same directory.
+ const size_t p_len = strlen(p);
+ const size_t buf_len = strlen(buf);
+ p = xrealloc(p, p_len + buf_len + 1);
+ memcpy(path_tail((char_u *)p), buf, buf_len + 1);
+ } else {
+ xfree(p);
+ p = xstrdup(buf);
+ }
+ }
+
+ if (remain == NULL) {
+ break;
+ }
+
+ // Append the first path component of "remain" to "p".
+ q = (char *)path_next_component(remain + 1);
+ len = q - remain - (*q != NUL);
+ const size_t p_len = strlen(p);
+ cpy = xmallocz(p_len + len);
+ memcpy(cpy, p, p_len + 1);
+ xstrlcat(cpy + p_len, remain, len + 1);
+ xfree(p);
+ p = cpy;
+
+ // Shorten "remain".
+ if (*q != NUL) {
+ STRMOVE(remain, q - 1);
+ } else {
+ XFREE_CLEAR(remain);
+ }
+ }
+
+ // If the result is a relative path name, make it explicitly relative to
+ // the current directory if and only if the argument had this form.
+ if (!vim_ispathsep(*p)) {
+ if (is_relative_to_current
+ && *p != NUL
+ && !(p[0] == '.'
+ && (p[1] == NUL
+ || vim_ispathsep(p[1])
+ || (p[1] == '.'
+ && (p[2] == NUL
+ || vim_ispathsep(p[2])))))) {
+ // Prepend "./".
+ cpy = (char *)concat_str((const char_u *)"./", (const char_u *)p);
+ xfree(p);
+ p = cpy;
+ } else if (!is_relative_to_current) {
+ // Strip leading "./".
+ q = p;
+ while (q[0] == '.' && vim_ispathsep(q[1])) {
+ q += 2;
+ }
+ if (q > p) {
+ STRMOVE(p, p + 2);
+ }
+ }
+ }
+
+ // Ensure that the result will have no trailing path separator
+ // if the argument had none. But keep "/" or "//".
+ if (!has_trailing_pathsep) {
+ q = p + strlen(p);
+ if (after_pathsep(p, q)) {
+ *path_tail_with_sep((char_u *)p) = NUL;
+ }
+ }
+
+ rettv->vval.v_string = (char_u *)p;
+ xfree(buf);
+ }
+# else
+ rettv->vval.v_string = (char_u *)xstrdup(p);
+# endif
+#endif
+
+ simplify_filename(rettv->vval.v_string);
+}
+
+/*
+ * "reverse({list})" function
+ */
+static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ list_T *l;
+ if (argvars[0].v_type != VAR_LIST) {
+ EMSG2(_(e_listarg), "reverse()");
+ } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)),
+ N_("reverse() argument"), TV_TRANSLATE)) {
+ tv_list_reverse(l);
+ tv_list_set_ret(rettv, l);
+ }
+}
+
+#define SP_NOMOVE 0x01 ///< don't move cursor
+#define SP_REPEAT 0x02 ///< repeat to find outer pair
+#define SP_RETCOUNT 0x04 ///< return matchcount
+#define SP_SETPCMARK 0x08 ///< set previous context mark
+#define SP_START 0x10 ///< accept match at start position
+#define SP_SUBPAT 0x20 ///< return nr of matching sub-pattern
+#define SP_END 0x40 ///< leave cursor at end of match
+#define SP_COLUMN 0x80 ///< start at cursor column
+
+/*
+ * Get flags for a search function.
+ * Possibly sets "p_ws".
+ * Returns BACKWARD, FORWARD or zero (for an error).
+ */
+static int get_search_arg(typval_T *varp, int *flagsp)
+{
+ int dir = FORWARD;
+ int mask;
+
+ if (varp->v_type != VAR_UNKNOWN) {
+ char nbuf[NUMBUFLEN];
+ const char *flags = tv_get_string_buf_chk(varp, nbuf);
+ if (flags == NULL) {
+ return 0; // Type error; errmsg already given.
+ }
+ while (*flags != NUL) {
+ switch (*flags) {
+ case 'b': dir = BACKWARD; break;
+ case 'w': p_ws = true; break;
+ case 'W': p_ws = false; break;
+ default: {
+ mask = 0;
+ if (flagsp != NULL) {
+ switch (*flags) {
+ case 'c': mask = SP_START; break;
+ case 'e': mask = SP_END; break;
+ case 'm': mask = SP_RETCOUNT; break;
+ case 'n': mask = SP_NOMOVE; break;
+ case 'p': mask = SP_SUBPAT; break;
+ case 'r': mask = SP_REPEAT; break;
+ case 's': mask = SP_SETPCMARK; break;
+ case 'z': mask = SP_COLUMN; break;
+ }
+ }
+ if (mask == 0) {
+ emsgf(_(e_invarg2), flags);
+ dir = 0;
+ } else {
+ *flagsp |= mask;
+ }
+ }
+ }
+ if (dir == 0) {
+ break;
+ }
+ flags++;
+ }
+ }
+ return dir;
+}
+
+// Shared by search() and searchpos() functions.
+static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp)
+{
+ int flags;
+ pos_T pos;
+ pos_T save_cursor;
+ bool save_p_ws = p_ws;
+ int dir;
+ int retval = 0; // default: FAIL
+ long lnum_stop = 0;
+ proftime_T tm;
+ long time_limit = 0;
+ int options = SEARCH_KEEP;
+ int subpatnum;
+ searchit_arg_T sia;
+
+ const char *const pat = tv_get_string(&argvars[0]);
+ dir = get_search_arg(&argvars[1], flagsp); // May set p_ws.
+ if (dir == 0) {
+ goto theend;
+ }
+ flags = *flagsp;
+ if (flags & SP_START) {
+ options |= SEARCH_START;
+ }
+ if (flags & SP_END) {
+ options |= SEARCH_END;
+ }
+ if (flags & SP_COLUMN) {
+ options |= SEARCH_COL;
+ }
+
+ // Optional arguments: line number to stop searching and timeout.
+ if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN) {
+ lnum_stop = tv_get_number_chk(&argvars[2], NULL);
+ if (lnum_stop < 0) {
+ goto theend;
+ }
+ if (argvars[3].v_type != VAR_UNKNOWN) {
+ time_limit = tv_get_number_chk(&argvars[3], NULL);
+ if (time_limit < 0) {
+ goto theend;
+ }
+ }
+ }
+
+ // Set the time limit, if there is one.
+ tm = profile_setlimit(time_limit);
+
+ /*
+ * This function does not accept SP_REPEAT and SP_RETCOUNT flags.
+ * Check to make sure only those flags are set.
+ * Also, Only the SP_NOMOVE or the SP_SETPCMARK flag can be set. Both
+ * flags cannot be set. Check for that condition also.
+ */
+ if (((flags & (SP_REPEAT | SP_RETCOUNT)) != 0)
+ || ((flags & SP_NOMOVE) && (flags & SP_SETPCMARK))) {
+ EMSG2(_(e_invarg2), tv_get_string(&argvars[1]));
+ goto theend;
+ }
+
+ pos = save_cursor = curwin->w_cursor;
+ memset(&sia, 0, sizeof(sia));
+ sia.sa_stop_lnum = (linenr_T)lnum_stop;
+ sia.sa_tm = &tm;
+ subpatnum = searchit(curwin, curbuf, &pos, NULL, dir, (char_u *)pat, 1,
+ options, RE_SEARCH, &sia);
+ if (subpatnum != FAIL) {
+ if (flags & SP_SUBPAT)
+ retval = subpatnum;
+ else
+ retval = pos.lnum;
+ if (flags & SP_SETPCMARK)
+ setpcmark();
+ curwin->w_cursor = pos;
+ if (match_pos != NULL) {
+ // Store the match cursor position
+ match_pos->lnum = pos.lnum;
+ match_pos->col = pos.col + 1;
+ }
+ // "/$" will put the cursor after the end of the line, may need to
+ // correct that here
+ check_cursor();
+ }
+
+ // If 'n' flag is used: restore cursor position.
+ if (flags & SP_NOMOVE) {
+ curwin->w_cursor = save_cursor;
+ } else {
+ curwin->w_set_curswant = true;
+ }
+theend:
+ p_ws = save_p_ws;
+
+ return retval;
+}
+
+// "rpcnotify()" function
+static void f_rpcnotify(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = 0;
+
+ if (check_restricted() || check_secure()) {
+ return;
+ }
+
+ if (argvars[0].v_type != VAR_NUMBER || argvars[0].vval.v_number < 0) {
+ EMSG2(_(e_invarg2), "Channel id must be a positive integer");
+ return;
+ }
+
+ if (argvars[1].v_type != VAR_STRING) {
+ EMSG2(_(e_invarg2), "Event type must be a string");
+ return;
+ }
+
+ Array args = ARRAY_DICT_INIT;
+
+ for (typval_T *tv = argvars + 2; tv->v_type != VAR_UNKNOWN; tv++) {
+ ADD(args, vim_to_object(tv));
+ }
+
+ if (!rpc_send_event((uint64_t)argvars[0].vval.v_number,
+ tv_get_string(&argvars[1]), args)) {
+ EMSG2(_(e_invarg2), "Channel doesn't exist");
+ return;
+ }
+
+ rettv->vval.v_number = 1;
+}
+
+// "rpcrequest()" function
+static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = 0;
+ const int l_provider_call_nesting = provider_call_nesting;
+
+ if (check_restricted() || check_secure()) {
+ return;
+ }
+
+ if (argvars[0].v_type != VAR_NUMBER || argvars[0].vval.v_number <= 0) {
+ EMSG2(_(e_invarg2), "Channel id must be a positive integer");
+ return;
+ }
+
+ if (argvars[1].v_type != VAR_STRING) {
+ EMSG2(_(e_invarg2), "Method name must be a string");
+ return;
+ }
+
+ Array args = ARRAY_DICT_INIT;
+
+ for (typval_T *tv = argvars + 2; tv->v_type != VAR_UNKNOWN; tv++) {
+ ADD(args, vim_to_object(tv));
+ }
+
+ sctx_T save_current_sctx;
+ uint8_t *save_sourcing_name, *save_autocmd_fname, *save_autocmd_match;
+ linenr_T save_sourcing_lnum;
+ int save_autocmd_bufnr;
+ void *save_funccalp;
+
+ if (l_provider_call_nesting) {
+ // If this is called from a provider function, restore the scope
+ // information of the caller.
+ save_current_sctx = current_sctx;
+ save_sourcing_name = sourcing_name;
+ save_sourcing_lnum = sourcing_lnum;
+ save_autocmd_fname = autocmd_fname;
+ save_autocmd_match = autocmd_match;
+ save_autocmd_bufnr = autocmd_bufnr;
+ save_funccalp = save_funccal();
+
+ current_sctx = provider_caller_scope.script_ctx;
+ sourcing_name = provider_caller_scope.sourcing_name;
+ sourcing_lnum = provider_caller_scope.sourcing_lnum;
+ autocmd_fname = provider_caller_scope.autocmd_fname;
+ autocmd_match = provider_caller_scope.autocmd_match;
+ autocmd_bufnr = provider_caller_scope.autocmd_bufnr;
+ restore_funccal(provider_caller_scope.funccalp);
+ }
+
+
+ Error err = ERROR_INIT;
+
+ uint64_t chan_id = (uint64_t)argvars[0].vval.v_number;
+ const char *method = tv_get_string(&argvars[1]);
+
+ Object result = rpc_send_call(chan_id, method, args, &err);
+
+ if (l_provider_call_nesting) {
+ current_sctx = save_current_sctx;
+ sourcing_name = save_sourcing_name;
+ sourcing_lnum = save_sourcing_lnum;
+ autocmd_fname = save_autocmd_fname;
+ autocmd_match = save_autocmd_match;
+ autocmd_bufnr = save_autocmd_bufnr;
+ restore_funccal(save_funccalp);
+ }
+
+ if (ERROR_SET(&err)) {
+ const char *name = NULL;
+ Channel *chan = find_channel(chan_id);
+ if (chan) {
+ name = rpc_client_name(chan);
+ }
+ msg_ext_set_kind("rpc_error");
+ if (name) {
+ emsgf_multiline("Error invoking '%s' on channel %"PRIu64" (%s):\n%s",
+ method, chan_id, name, err.msg);
+ } else {
+ emsgf_multiline("Error invoking '%s' on channel %"PRIu64":\n%s",
+ method, chan_id, err.msg);
+ }
+
+ goto end;
+ }
+
+ if (!object_to_vim(result, rettv, &err)) {
+ EMSG2(_("Error converting the call result: %s"), err.msg);
+ }
+
+end:
+ api_free_object(result);
+ api_clear_error(&err);
+}
+
+// "rpcstart()" function (DEPRECATED)
+static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = 0;
+
+ if (check_restricted() || check_secure()) {
+ return;
+ }
+
+ if (argvars[0].v_type != VAR_STRING
+ || (argvars[1].v_type != VAR_LIST && argvars[1].v_type != VAR_UNKNOWN)) {
+ // Wrong argument types
+ EMSG(_(e_invarg));
+ return;
+ }
+
+ list_T *args = NULL;
+ int argsl = 0;
+ if (argvars[1].v_type == VAR_LIST) {
+ args = argvars[1].vval.v_list;
+ argsl = tv_list_len(args);
+ // Assert that all list items are strings
+ int i = 0;
+ TV_LIST_ITER_CONST(args, arg, {
+ if (TV_LIST_ITEM_TV(arg)->v_type != VAR_STRING) {
+ emsgf(_("E5010: List item %d of the second argument is not a string"),
+ i);
+ return;
+ }
+ i++;
+ });
+ }
+
+ if (argvars[0].vval.v_string == NULL || argvars[0].vval.v_string[0] == NUL) {
+ EMSG(_(e_api_spawn_failed));
+ return;
+ }
+
+ // Allocate extra memory for the argument vector and the NULL pointer
+ int argvl = argsl + 2;
+ char **argv = xmalloc(sizeof(char_u *) * argvl);
+
+ // Copy program name
+ argv[0] = xstrdup((char *)argvars[0].vval.v_string);
+
+ int i = 1;
+ // Copy arguments to the vector
+ if (argsl > 0) {
+ TV_LIST_ITER_CONST(args, arg, {
+ argv[i++] = xstrdup(tv_get_string(TV_LIST_ITEM_TV(arg)));
+ });
+ }
+
+ // The last item of argv must be NULL
+ argv[i] = NULL;
+
+ Channel *chan = channel_job_start(argv, CALLBACK_READER_INIT,
+ CALLBACK_READER_INIT, CALLBACK_NONE,
+ false, true, false, NULL, 0, 0, NULL, NULL,
+ &rettv->vval.v_number);
+ if (chan) {
+ channel_create_event(chan, NULL);
+ }
+}
+
+// "rpcstop()" function
+static void f_rpcstop(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = 0;
+
+ if (check_restricted() || check_secure()) {
+ return;
+ }
+
+ if (argvars[0].v_type != VAR_NUMBER) {
+ // Wrong argument types
+ EMSG(_(e_invarg));
+ return;
+ }
+
+ // if called with a job, stop it, else closes the channel
+ uint64_t id = argvars[0].vval.v_number;
+ if (find_job(id, false)) {
+ f_jobstop(argvars, rettv, NULL);
+ } else {
+ const char *error;
+ rettv->vval.v_number = channel_close(argvars[0].vval.v_number,
+ kChannelPartRpc, &error);
+ if (!rettv->vval.v_number) {
+ EMSG(error);
+ }
+ }
+}
+
+/*
+ * "screenattr()" function
+ */
+static void f_screenattr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int c;
+
+ int row = (int)tv_get_number_chk(&argvars[0], NULL) - 1;
+ int col = (int)tv_get_number_chk(&argvars[1], NULL) - 1;
+ if (row < 0 || row >= default_grid.Rows
+ || col < 0 || col >= default_grid.Columns) {
+ c = -1;
+ } else {
+ ScreenGrid *grid = &default_grid;
+ screenchar_adjust_grid(&grid, &row, &col);
+ c = grid->attrs[grid->line_offset[row] + col];
+ }
+ rettv->vval.v_number = c;
+}
+
+/*
+ * "screenchar()" function
+ */
+static void f_screenchar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int c;
+
+ int row = tv_get_number_chk(&argvars[0], NULL) - 1;
+ int col = tv_get_number_chk(&argvars[1], NULL) - 1;
+ if (row < 0 || row >= default_grid.Rows
+ || col < 0 || col >= default_grid.Columns) {
+ c = -1;
+ } else {
+ ScreenGrid *grid = &default_grid;
+ screenchar_adjust_grid(&grid, &row, &col);
+ c = utf_ptr2char(grid->chars[grid->line_offset[row] + col]);
+ }
+ rettv->vval.v_number = c;
+}
+
+/*
+ * "screencol()" function
+ *
+ * First column is 1 to be consistent with virtcol().
+ */
+static void f_screencol(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = ui_current_col() + 1;
+}
+
+/// "screenpos({winid}, {lnum}, {col})" function
+static void f_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ pos_T pos;
+ int row = 0;
+ int scol = 0, ccol = 0, ecol = 0;
+
+ tv_dict_alloc_ret(rettv);
+ dict_T *dict = rettv->vval.v_dict;
+
+ win_T *wp = find_win_by_nr_or_id(&argvars[0]);
+ if (wp == NULL) {
+ return;
+ }
+
+ pos.lnum = tv_get_number(&argvars[1]);
+ pos.col = tv_get_number(&argvars[2]) - 1;
+ pos.coladd = 0;
+ textpos2screenpos(wp, &pos, &row, &scol, &ccol, &ecol, false);
+
+ tv_dict_add_nr(dict, S_LEN("row"), row);
+ tv_dict_add_nr(dict, S_LEN("col"), scol);
+ tv_dict_add_nr(dict, S_LEN("curscol"), ccol);
+ tv_dict_add_nr(dict, S_LEN("endcol"), ecol);
+}
+
+/*
+ * "screenrow()" function
+ */
+static void f_screenrow(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = ui_current_row() + 1;
+}
+
+/*
+ * "search()" function
+ */
+static void f_search(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int flags = 0;
+
+ rettv->vval.v_number = search_cmn(argvars, NULL, &flags);
+}
+
+/*
+ * "searchdecl()" function
+ */
+static void f_searchdecl(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int locally = 1;
+ int thisblock = 0;
+ bool error = false;
+
+ rettv->vval.v_number = 1; // default: FAIL
+
+ const char *const name = tv_get_string_chk(&argvars[0]);
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ locally = tv_get_number_chk(&argvars[1], &error) == 0;
+ if (!error && argvars[2].v_type != VAR_UNKNOWN) {
+ thisblock = tv_get_number_chk(&argvars[2], &error) != 0;
+ }
+ }
+ if (!error && name != NULL) {
+ rettv->vval.v_number = find_decl((char_u *)name, strlen(name), locally,
+ thisblock, SEARCH_KEEP) == FAIL;
+ }
+}
+
+/*
+ * Used by searchpair() and searchpairpos()
+ */
+static int searchpair_cmn(typval_T *argvars, pos_T *match_pos)
+{
+ bool save_p_ws = p_ws;
+ int dir;
+ int flags = 0;
+ int retval = 0; // default: FAIL
+ long lnum_stop = 0;
+ long time_limit = 0;
+
+ // Get the three pattern arguments: start, middle, end. Will result in an
+ // error if not a valid argument.
+ char nbuf1[NUMBUFLEN];
+ char nbuf2[NUMBUFLEN];
+ const char *spat = tv_get_string_chk(&argvars[0]);
+ const char *mpat = tv_get_string_buf_chk(&argvars[1], nbuf1);
+ const char *epat = tv_get_string_buf_chk(&argvars[2], nbuf2);
+ if (spat == NULL || mpat == NULL || epat == NULL) {
+ goto theend; // Type error.
+ }
+
+ // Handle the optional fourth argument: flags.
+ dir = get_search_arg(&argvars[3], &flags); // may set p_ws.
+ if (dir == 0) {
+ goto theend;
+ }
+
+ // Don't accept SP_END or SP_SUBPAT.
+ // Only one of the SP_NOMOVE or SP_SETPCMARK flags can be set.
+ if ((flags & (SP_END | SP_SUBPAT)) != 0
+ || ((flags & SP_NOMOVE) && (flags & SP_SETPCMARK))) {
+ EMSG2(_(e_invarg2), tv_get_string(&argvars[3]));
+ goto theend;
+ }
+
+ // Using 'r' implies 'W', otherwise it doesn't work.
+ if (flags & SP_REPEAT) {
+ p_ws = false;
+ }
+
+ // Optional fifth argument: skip expression.
+ const typval_T *skip;
+ if (argvars[3].v_type == VAR_UNKNOWN
+ || argvars[4].v_type == VAR_UNKNOWN) {
+ skip = NULL;
+ } else {
+ skip = &argvars[4];
+ if (skip->v_type != VAR_FUNC
+ && skip->v_type != VAR_PARTIAL
+ && skip->v_type != VAR_STRING) {
+ emsgf(_(e_invarg2), tv_get_string(&argvars[4]));
+ goto theend; // Type error.
+ }
+ if (argvars[5].v_type != VAR_UNKNOWN) {
+ lnum_stop = tv_get_number_chk(&argvars[5], NULL);
+ if (lnum_stop < 0) {
+ emsgf(_(e_invarg2), tv_get_string(&argvars[5]));
+ goto theend;
+ }
+ if (argvars[6].v_type != VAR_UNKNOWN) {
+ time_limit = tv_get_number_chk(&argvars[6], NULL);
+ if (time_limit < 0) {
+ emsgf(_(e_invarg2), tv_get_string(&argvars[6]));
+ goto theend;
+ }
+ }
+ }
+ }
+
+ retval = do_searchpair(
+ (char_u *)spat, (char_u *)mpat, (char_u *)epat, dir, skip,
+ flags, match_pos, lnum_stop, time_limit);
+
+theend:
+ p_ws = save_p_ws;
+
+ return retval;
+}
+
+/*
+ * "searchpair()" function
+ */
+static void f_searchpair(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = searchpair_cmn(argvars, NULL);
+}
+
+/*
+ * "searchpairpos()" function
+ */
+static void f_searchpairpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ pos_T match_pos;
+ int lnum = 0;
+ int col = 0;
+
+ tv_list_alloc_ret(rettv, 2);
+
+ if (searchpair_cmn(argvars, &match_pos) > 0) {
+ lnum = match_pos.lnum;
+ col = match_pos.col;
+ }
+
+ tv_list_append_number(rettv->vval.v_list, (varnumber_T)lnum);
+ tv_list_append_number(rettv->vval.v_list, (varnumber_T)col);
+}
+
+/*
+ * Search for a start/middle/end thing.
+ * Used by searchpair(), see its documentation for the details.
+ * Returns 0 or -1 for no match,
+ */
+long
+do_searchpair(
+ char_u *spat, // start pattern
+ char_u *mpat, // middle pattern
+ char_u *epat, // end pattern
+ int dir, // BACKWARD or FORWARD
+ const typval_T *skip, // skip expression
+ int flags, // SP_SETPCMARK and other SP_ values
+ pos_T *match_pos,
+ linenr_T lnum_stop, // stop at this line if not zero
+ long time_limit // stop after this many msec
+)
+{
+ char_u *save_cpo;
+ char_u *pat, *pat2 = NULL, *pat3 = NULL;
+ long retval = 0;
+ pos_T pos;
+ pos_T firstpos;
+ pos_T foundpos;
+ pos_T save_cursor;
+ pos_T save_pos;
+ int n;
+ int nest = 1;
+ bool use_skip = false;
+ int options = SEARCH_KEEP;
+ proftime_T tm;
+ size_t pat2_len;
+ size_t pat3_len;
+
+ // Make 'cpoptions' empty, the 'l' flag should not be used here.
+ save_cpo = p_cpo;
+ p_cpo = empty_option;
+
+ // Set the time limit, if there is one.
+ tm = profile_setlimit(time_limit);
+
+ // Make two search patterns: start/end (pat2, for in nested pairs) and
+ // start/middle/end (pat3, for the top pair).
+ pat2_len = STRLEN(spat) + STRLEN(epat) + 17;
+ pat2 = xmalloc(pat2_len);
+ pat3_len = STRLEN(spat) + STRLEN(mpat) + STRLEN(epat) + 25;
+ pat3 = xmalloc(pat3_len);
+ snprintf((char *)pat2, pat2_len, "\\m\\(%s\\m\\)\\|\\(%s\\m\\)", spat, epat);
+ if (*mpat == NUL) {
+ STRCPY(pat3, pat2);
+ } else {
+ snprintf((char *)pat3, pat3_len,
+ "\\m\\(%s\\m\\)\\|\\(%s\\m\\)\\|\\(%s\\m\\)", spat, epat, mpat);
+ }
+ if (flags & SP_START) {
+ options |= SEARCH_START;
+ }
+
+ if (skip != NULL) {
+ // Empty string means to not use the skip expression.
+ if (skip->v_type == VAR_STRING || skip->v_type == VAR_FUNC) {
+ use_skip = skip->vval.v_string != NULL && *skip->vval.v_string != NUL;
+ }
+ }
+
+ save_cursor = curwin->w_cursor;
+ pos = curwin->w_cursor;
+ clearpos(&firstpos);
+ clearpos(&foundpos);
+ pat = pat3;
+ for (;; ) {
+ searchit_arg_T sia;
+ memset(&sia, 0, sizeof(sia));
+ sia.sa_stop_lnum = lnum_stop;
+ sia.sa_tm = &tm;
+
+ n = searchit(curwin, curbuf, &pos, NULL, dir, pat, 1L,
+ options, RE_SEARCH, &sia);
+ if (n == FAIL || (firstpos.lnum != 0 && equalpos(pos, firstpos))) {
+ // didn't find it or found the first match again: FAIL
+ break;
+ }
+
+ if (firstpos.lnum == 0)
+ firstpos = pos;
+ if (equalpos(pos, foundpos)) {
+ // Found the same position again. Can happen with a pattern that
+ // has "\zs" at the end and searching backwards. Advance one
+ // character and try again.
+ if (dir == BACKWARD) {
+ decl(&pos);
+ } else {
+ incl(&pos);
+ }
+ }
+ foundpos = pos;
+
+ // clear the start flag to avoid getting stuck here
+ options &= ~SEARCH_START;
+
+ // If the skip pattern matches, ignore this match.
+ if (use_skip) {
+ save_pos = curwin->w_cursor;
+ curwin->w_cursor = pos;
+ bool err = false;
+ const bool r = eval_expr_to_bool(skip, &err);
+ curwin->w_cursor = save_pos;
+ if (err) {
+ // Evaluating {skip} caused an error, break here.
+ curwin->w_cursor = save_cursor;
+ retval = -1;
+ break;
+ }
+ if (r)
+ continue;
+ }
+
+ if ((dir == BACKWARD && n == 3) || (dir == FORWARD && n == 2)) {
+ // Found end when searching backwards or start when searching
+ // forward: nested pair.
+ nest++;
+ pat = pat2; // nested, don't search for middle
+ } else {
+ // Found end when searching forward or start when searching
+ // backward: end of (nested) pair; or found middle in outer pair.
+ if (--nest == 1) {
+ pat = pat3; // outer level, search for middle
+ }
+ }
+
+ if (nest == 0) {
+ // Found the match: return matchcount or line number.
+ if (flags & SP_RETCOUNT) {
+ retval++;
+ } else {
+ retval = pos.lnum;
+ }
+ if (flags & SP_SETPCMARK) {
+ setpcmark();
+ }
+ curwin->w_cursor = pos;
+ if (!(flags & SP_REPEAT))
+ break;
+ nest = 1; // search for next unmatched
+ }
+ }
+
+ if (match_pos != NULL) {
+ // Store the match cursor position
+ match_pos->lnum = curwin->w_cursor.lnum;
+ match_pos->col = curwin->w_cursor.col + 1;
+ }
+
+ // If 'n' flag is used or search failed: restore cursor position.
+ if ((flags & SP_NOMOVE) || retval == 0) {
+ curwin->w_cursor = save_cursor;
+ }
+
+ xfree(pat2);
+ xfree(pat3);
+ if (p_cpo == empty_option) {
+ p_cpo = save_cpo;
+ } else {
+ // Darn, evaluating the {skip} expression changed the value.
+ free_string_option(save_cpo);
+ }
+
+ return retval;
+}
+
+/*
+ * "searchpos()" function
+ */
+static void f_searchpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ pos_T match_pos;
+ int flags = 0;
+
+ const int n = search_cmn(argvars, &match_pos, &flags);
+
+ tv_list_alloc_ret(rettv, 2 + (!!(flags & SP_SUBPAT)));
+
+ const int lnum = (n > 0 ? match_pos.lnum : 0);
+ const int col = (n > 0 ? match_pos.col : 0);
+
+ tv_list_append_number(rettv->vval.v_list, (varnumber_T)lnum);
+ tv_list_append_number(rettv->vval.v_list, (varnumber_T)col);
+ if (flags & SP_SUBPAT) {
+ tv_list_append_number(rettv->vval.v_list, (varnumber_T)n);
+ }
+}
+
+/// "serverlist()" function
+static void f_serverlist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ size_t n;
+ char **addrs = server_address_list(&n);
+
+ // Copy addrs into a linked list.
+ list_T *const l = tv_list_alloc_ret(rettv, n);
+ for (size_t i = 0; i < n; i++) {
+ tv_list_append_allocated_string(l, addrs[i]);
+ }
+ xfree(addrs);
+}
+
+/// "serverstart()" function
+static void f_serverstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL; // Address of the new server
+
+ if (check_restricted() || check_secure()) {
+ return;
+ }
+
+ char *address;
+ // If the user supplied an address, use it, otherwise use a temp.
+ if (argvars[0].v_type != VAR_UNKNOWN) {
+ if (argvars[0].v_type != VAR_STRING) {
+ EMSG(_(e_invarg));
+ return;
+ } else {
+ address = xstrdup(tv_get_string(argvars));
+ }
+ } else {
+ address = server_address_new();
+ }
+
+ int result = server_start(address);
+ xfree(address);
+
+ if (result != 0) {
+ EMSG2("Failed to start server: %s",
+ result > 0 ? "Unknown system error" : uv_strerror(result));
+ return;
+ }
+
+ // Since it's possible server_start adjusted the given {address} (e.g.,
+ // "localhost:" will now have a port), return the final value to the user.
+ size_t n;
+ char **addrs = server_address_list(&n);
+ rettv->vval.v_string = (char_u *)addrs[n - 1];
+
+ n--;
+ for (size_t i = 0; i < n; i++) {
+ xfree(addrs[i]);
+ }
+ xfree(addrs);
+}
+
+/// "serverstop()" function
+static void f_serverstop(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (check_restricted() || check_secure()) {
+ return;
+ }
+
+ if (argvars[0].v_type != VAR_STRING) {
+ EMSG(_(e_invarg));
+ return;
+ }
+
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = 0;
+ if (argvars[0].vval.v_string) {
+ bool rv = server_stop((char *)argvars[0].vval.v_string);
+ rettv->vval.v_number = (rv ? 1 : 0);
+ }
+}
+
+/// "setbufline()" function
+static void f_setbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ linenr_T lnum;
+ buf_T *buf;
+
+ buf = tv_get_buf(&argvars[0], false);
+ if (buf == NULL) {
+ rettv->vval.v_number = 1; // FAIL
+ } else {
+ lnum = tv_get_lnum_buf(&argvars[1], buf);
+ set_buffer_lines(buf, lnum, false, &argvars[2], rettv);
+ }
+}
+
+/*
+ * "setbufvar()" function
+ */
+static void f_setbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (check_secure()
+ || !tv_check_str_or_nr(&argvars[0])) {
+ return;
+ }
+ const char *varname = tv_get_string_chk(&argvars[1]);
+ buf_T *const buf = tv_get_buf(&argvars[0], false);
+ typval_T *varp = &argvars[2];
+
+ if (buf != NULL && varname != NULL) {
+ if (*varname == '&') {
+ long numval;
+ bool error = false;
+ aco_save_T aco;
+
+ // set curbuf to be our buf, temporarily
+ aucmd_prepbuf(&aco, buf);
+
+ varname++;
+ numval = tv_get_number_chk(varp, &error);
+ char nbuf[NUMBUFLEN];
+ const char *const strval = tv_get_string_buf_chk(varp, nbuf);
+ if (!error && strval != NULL) {
+ set_option_value(varname, numval, strval, OPT_LOCAL);
+ }
+
+ // reset notion of buffer
+ aucmd_restbuf(&aco);
+ } else {
+ const size_t varname_len = STRLEN(varname);
+ char *const bufvarname = xmalloc(varname_len + 3);
+ buf_T *const save_curbuf = curbuf;
+ curbuf = buf;
+ memcpy(bufvarname, "b:", 2);
+ memcpy(bufvarname + 2, varname, varname_len + 1);
+ set_var(bufvarname, varname_len + 2, varp, true);
+ xfree(bufvarname);
+ curbuf = save_curbuf;
+ }
+ }
+}
+
+static void f_setcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ dict_T *d;
+ dictitem_T *di;
+
+ if (argvars[0].v_type != VAR_DICT) {
+ EMSG(_(e_dictreq));
+ return;
+ }
+
+ if ((d = argvars[0].vval.v_dict) != NULL) {
+ char_u *const csearch = (char_u *)tv_dict_get_string(d, "char", false);
+ if (csearch != NULL) {
+ if (enc_utf8) {
+ int pcc[MAX_MCO];
+ int c = utfc_ptr2char(csearch, pcc);
+ set_last_csearch(c, csearch, utfc_ptr2len(csearch));
+ }
+ else
+ set_last_csearch(PTR2CHAR(csearch),
+ csearch, utfc_ptr2len(csearch));
+ }
+
+ di = tv_dict_find(d, S_LEN("forward"));
+ if (di != NULL) {
+ set_csearch_direction(tv_get_number(&di->di_tv) ? FORWARD : BACKWARD);
+ }
+
+ di = tv_dict_find(d, S_LEN("until"));
+ if (di != NULL) {
+ set_csearch_until(!!tv_get_number(&di->di_tv));
+ }
+ }
+}
+
+/*
+ * "setcmdpos()" function
+ */
+static void f_setcmdpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const int pos = (int)tv_get_number(&argvars[0]) - 1;
+
+ if (pos >= 0) {
+ rettv->vval.v_number = set_cmdline_pos(pos);
+ }
+}
+
+/// "setenv()" function
+static void f_setenv(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char namebuf[NUMBUFLEN];
+ char valbuf[NUMBUFLEN];
+ const char *name = tv_get_string_buf(&argvars[0], namebuf);
+
+ if (argvars[1].v_type == VAR_SPECIAL
+ && argvars[1].vval.v_special == kSpecialVarNull) {
+ os_unsetenv(name);
+ } else {
+ os_setenv(name, tv_get_string_buf(&argvars[1], valbuf), 1);
+ }
+}
+
+/// "setfperm({fname}, {mode})" function
+static void f_setfperm(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = 0;
+
+ const char *const fname = tv_get_string_chk(&argvars[0]);
+ if (fname == NULL) {
+ return;
+ }
+
+ char modebuf[NUMBUFLEN];
+ const char *const mode_str = tv_get_string_buf_chk(&argvars[1], modebuf);
+ if (mode_str == NULL) {
+ return;
+ }
+ if (strlen(mode_str) != 9) {
+ EMSG2(_(e_invarg2), mode_str);
+ return;
+ }
+
+ int mask = 1;
+ int mode = 0;
+ for (int i = 8; i >= 0; i--) {
+ if (mode_str[i] != '-') {
+ mode |= mask;
+ }
+ mask = mask << 1;
+ }
+ rettv->vval.v_number = os_setperm(fname, mode) == OK;
+}
+
+/*
+ * "setline()" function
+ */
+static void f_setline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ linenr_T lnum = tv_get_lnum(&argvars[0]);
+ set_buffer_lines(curbuf, lnum, false, &argvars[1], rettv);
+}
+
+/// Create quickfix/location list from VimL values
+///
+/// Used by `setqflist()` and `setloclist()` functions. Accepts invalid
+/// list_arg, action_arg and what_arg arguments in which case errors out,
+/// including VAR_UNKNOWN parameters.
+///
+/// @param[in,out] wp Window to create location list for. May be NULL in
+/// which case quickfix list will be created.
+/// @param[in] list_arg Quickfix list contents.
+/// @param[in] action_arg Action to perform: append to an existing list,
+/// replace its content or create a new one.
+/// @param[in] title_arg New list title. Defaults to caller function name.
+/// @param[out] rettv Return value: 0 in case of success, -1 otherwise.
+static void set_qf_ll_list(win_T *wp, typval_T *args, typval_T *rettv)
+ FUNC_ATTR_NONNULL_ARG(2, 3)
+{
+ static char *e_invact = N_("E927: Invalid action: '%s'");
+ const char *title = NULL;
+ int action = ' ';
+ static int recursive = 0;
+ rettv->vval.v_number = -1;
+ dict_T *d = NULL;
+
+ typval_T *list_arg = &args[0];
+ if (list_arg->v_type != VAR_LIST) {
+ EMSG(_(e_listreq));
+ return;
+ } else if (recursive != 0) {
+ EMSG(_(e_au_recursive));
+ return;
+ }
+
+ typval_T *action_arg = &args[1];
+ if (action_arg->v_type == VAR_UNKNOWN) {
+ // Option argument was not given.
+ goto skip_args;
+ } else if (action_arg->v_type != VAR_STRING) {
+ EMSG(_(e_stringreq));
+ return;
+ }
+ const char *const act = tv_get_string_chk(action_arg);
+ if ((*act == 'a' || *act == 'r' || *act == ' ' || *act == 'f')
+ && act[1] == NUL) {
+ action = *act;
+ } else {
+ EMSG2(_(e_invact), act);
+ return;
+ }
+
+ typval_T *title_arg = &args[2];
+ if (title_arg->v_type == VAR_UNKNOWN) {
+ // Option argument was not given.
+ goto skip_args;
+ } else if (title_arg->v_type == VAR_STRING) {
+ title = tv_get_string_chk(title_arg);
+ if (!title) {
+ // Type error. Error already printed by tv_get_string_chk().
+ return;
+ }
+ } else if (title_arg->v_type == VAR_DICT) {
+ d = title_arg->vval.v_dict;
+ } else {
+ EMSG(_(e_dictreq));
+ return;
+ }
+
+skip_args:
+ if (!title) {
+ title = (wp ? ":setloclist()" : ":setqflist()");
+ }
+
+ recursive++;
+ list_T *const l = list_arg->vval.v_list;
+ if (set_errorlist(wp, l, action, (char_u *)title, d) == OK) {
+ rettv->vval.v_number = 0;
+ }
+ recursive--;
+}
+
+/*
+ * "setloclist()" function
+ */
+static void f_setloclist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ win_T *win;
+
+ rettv->vval.v_number = -1;
+
+ win = find_win_by_nr_or_id(&argvars[0]);
+ if (win != NULL) {
+ set_qf_ll_list(win, &argvars[1], rettv);
+ }
+}
+
+/*
+ * "setmatches()" function
+ */
+static void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ dict_T *d;
+ list_T *s = NULL;
+
+ rettv->vval.v_number = -1;
+ if (argvars[0].v_type != VAR_LIST) {
+ EMSG(_(e_listreq));
+ 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) {
+ emsgf(_("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)) {
+ emsgf(_("E474: List item %d is missing one of the required keys"),
+ li_idx);
+ return;
+ }
+ li_idx++;
+ });
+
+ clear_matches(curwin);
+ 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(curwin, group,
+ tv_dict_get_string(d, "pattern", false),
+ priority, id, NULL, conceal) != id) {
+ match_add_failed = true;
+ }
+ } else {
+ if (match_add(curwin, 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;
+ }
+}
+
+/*
+ * "setpos()" function
+ */
+static void f_setpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ pos_T pos;
+ int fnum;
+ colnr_T curswant = -1;
+
+ rettv->vval.v_number = -1;
+ const char *const name = tv_get_string_chk(argvars);
+ if (name != NULL) {
+ if (list2fpos(&argvars[1], &pos, &fnum, &curswant) == OK) {
+ if (pos.col != MAXCOL && --pos.col < 0) {
+ pos.col = 0;
+ }
+ if (name[0] == '.' && name[1] == NUL) {
+ // set cursor; "fnum" is ignored
+ curwin->w_cursor = pos;
+ if (curswant >= 0) {
+ curwin->w_curswant = curswant - 1;
+ curwin->w_set_curswant = false;
+ }
+ check_cursor();
+ rettv->vval.v_number = 0;
+ } else if (name[0] == '\'' && name[1] != NUL && name[2] == NUL) {
+ // set mark
+ if (setmark_pos((uint8_t)name[1], &pos, fnum) == OK) {
+ rettv->vval.v_number = 0;
+ }
+ } else {
+ EMSG(_(e_invarg));
+ }
+ }
+ }
+}
+
+/*
+ * "setqflist()" function
+ */
+static void f_setqflist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ set_qf_ll_list(NULL, argvars, rettv);
+}
+
+/*
+ * "setreg()" function
+ */
+static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int regname;
+ bool append = false;
+ MotionType yank_type;
+ long block_len;
+
+ block_len = -1;
+ yank_type = kMTUnknown;
+
+ rettv->vval.v_number = 1; // FAIL is default.
+
+ const char *const strregname = tv_get_string_chk(argvars);
+ if (strregname == NULL) {
+ return; // Type error; errmsg already given.
+ }
+ regname = (uint8_t)(*strregname);
+ if (regname == 0 || regname == '@') {
+ regname = '"';
+ }
+
+ bool set_unnamed = false;
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ const char *stropt = tv_get_string_chk(&argvars[2]);
+ if (stropt == NULL) {
+ return; // Type error.
+ }
+ for (; *stropt != NUL; stropt++) {
+ switch (*stropt) {
+ case 'a': case 'A': { // append
+ append = true;
+ break;
+ }
+ case 'v': case 'c': { // character-wise selection
+ yank_type = kMTCharWise;
+ break;
+ }
+ case 'V': case 'l': { // line-wise selection
+ yank_type = kMTLineWise;
+ break;
+ }
+ case 'b': case Ctrl_V: { // block-wise selection
+ yank_type = kMTBlockWise;
+ if (ascii_isdigit(stropt[1])) {
+ stropt++;
+ block_len = getdigits_long((char_u **)&stropt, true, 0) - 1;
+ stropt--;
+ }
+ break;
+ }
+ case 'u': case '"': { // unnamed register
+ set_unnamed = true;
+ break;
+ }
+ }
+ }
+ }
+
+ if (argvars[1].v_type == VAR_LIST) {
+ list_T *ll = argvars[1].vval.v_list;
+ // If the list is NULL handle like an empty list.
+ const int len = tv_list_len(ll);
+
+ // First half: use for pointers to result lines; second half: use for
+ // pointers to allocated copies.
+ char **lstval = xmalloc(sizeof(char *) * ((len + 1) * 2));
+ const char **curval = (const char **)lstval;
+ char **allocval = lstval + len + 2;
+ char **curallocval = allocval;
+
+ TV_LIST_ITER_CONST(ll, li, {
+ char buf[NUMBUFLEN];
+ *curval = tv_get_string_buf_chk(TV_LIST_ITEM_TV(li), buf);
+ if (*curval == NULL) {
+ goto free_lstval;
+ }
+ if (*curval == buf) {
+ // Need to make a copy,
+ // next tv_get_string_buf_chk() will overwrite the string.
+ *curallocval = xstrdup(*curval);
+ *curval = *curallocval;
+ curallocval++;
+ }
+ curval++;
+ });
+ *curval++ = NULL;
+
+ write_reg_contents_lst(regname, (char_u **)lstval, append, yank_type,
+ block_len);
+
+free_lstval:
+ while (curallocval > allocval) {
+ xfree(*--curallocval);
+ }
+ xfree(lstval);
+ } else {
+ const char *strval = tv_get_string_chk(&argvars[1]);
+ if (strval == NULL) {
+ return;
+ }
+ write_reg_contents_ex(regname, (const char_u *)strval, STRLEN(strval),
+ append, yank_type, block_len);
+ }
+ rettv->vval.v_number = 0;
+
+ if (set_unnamed) {
+ // Discard the result. We already handle the error case.
+ if (op_reg_set_previous(regname)) { }
+ }
+}
+
+/*
+ * "settabvar()" function
+ */
+static void f_settabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = 0;
+
+ if (check_secure()) {
+ return;
+ }
+
+ tabpage_T *const tp = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL));
+ const char *const varname = tv_get_string_chk(&argvars[1]);
+ typval_T *const varp = &argvars[2];
+
+ if (varname != NULL && tp != NULL) {
+ tabpage_T *const save_curtab = curtab;
+ goto_tabpage_tp(tp, false, false);
+
+ const size_t varname_len = strlen(varname);
+ char *const tabvarname = xmalloc(varname_len + 3);
+ memcpy(tabvarname, "t:", 2);
+ memcpy(tabvarname + 2, varname, varname_len + 1);
+ set_var(tabvarname, varname_len + 2, varp, true);
+ xfree(tabvarname);
+
+ // Restore current tabpage.
+ if (valid_tabpage(save_curtab)) {
+ goto_tabpage_tp(save_curtab, false, false);
+ }
+ }
+}
+
+/*
+ * "settabwinvar()" function
+ */
+static void f_settabwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ setwinvar(argvars, rettv, 1);
+}
+
+// "settagstack()" function
+static void f_settagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ static char *e_invact2 = N_("E962: Invalid action: '%s'");
+ win_T *wp;
+ dict_T *d;
+ int action = 'r';
+
+ rettv->vval.v_number = -1;
+
+ // first argument: window number or id
+ wp = find_win_by_nr_or_id(&argvars[0]);
+ if (wp == NULL) {
+ return;
+ }
+
+ // second argument: dict with items to set in the tag stack
+ if (argvars[1].v_type != VAR_DICT) {
+ EMSG(_(e_dictreq));
+ return;
+ }
+ d = argvars[1].vval.v_dict;
+ if (d == NULL) {
+ return;
+ }
+
+ // third argument: action - 'a' for append and 'r' for replace.
+ // default is to replace the stack.
+ if (argvars[2].v_type == VAR_UNKNOWN) {
+ action = 'r';
+ } else if (argvars[2].v_type == VAR_STRING) {
+ const char *actstr;
+ actstr = tv_get_string_chk(&argvars[2]);
+ if (actstr == NULL) {
+ return;
+ }
+ if ((*actstr == 'r' || *actstr == 'a' || *actstr == 't')
+ && actstr[1] == NUL) {
+ action = *actstr;
+ } else {
+ EMSG2(_(e_invact2), actstr);
+ return;
+ }
+ } else {
+ EMSG(_(e_stringreq));
+ return;
+ }
+
+ if (set_tagstack(wp, d, action) == OK) {
+ rettv->vval.v_number = 0;
+ } else {
+ EMSG(_(e_listreq));
+ }
+}
+
+/*
+ * "setwinvar()" function
+ */
+static void f_setwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ setwinvar(argvars, rettv, 0);
+}
+
+/// f_sha256 - sha256({string}) function
+static void f_sha256(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *p = tv_get_string(&argvars[0]);
+ const char *hash = sha256_bytes((const uint8_t *)p, strlen(p) , NULL, 0);
+
+ // make a copy of the hash (sha256_bytes returns a static buffer)
+ rettv->vval.v_string = (char_u *)xstrdup(hash);
+ rettv->v_type = VAR_STRING;
+}
+
+/*
+ * "shellescape({string})" function
+ */
+static void f_shellescape(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const bool do_special = non_zero_arg(&argvars[1]);
+
+ rettv->vval.v_string = vim_strsave_shellescape(
+ (const char_u *)tv_get_string(&argvars[0]), do_special, do_special);
+ rettv->v_type = VAR_STRING;
+}
+
+/*
+ * shiftwidth() function
+ */
+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;
+ dict_T *dict;
+ char *icon = NULL;
+ char *linehl = NULL;
+ char *text = NULL;
+ char *texthl = NULL;
+ char *numhl = NULL;
+
+ rettv->vval.v_number = -1;
+
+ name = tv_get_string_chk(&argvars[0]);
+ if (name == NULL) {
+ return;
+ }
+
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ if (argvars[1].v_type != VAR_DICT) {
+ EMSG(_(e_dictreq));
+ return;
+ }
+
+ // sign attributes
+ dict = argvars[1].vval.v_dict;
+ if (tv_dict_find(dict, "icon", -1) != NULL) {
+ icon = tv_dict_get_string(dict, "icon", true);
+ }
+ if (tv_dict_find(dict, "linehl", -1) != NULL) {
+ linehl = tv_dict_get_string(dict, "linehl", true);
+ }
+ if (tv_dict_find(dict, "text", -1) != NULL) {
+ text = tv_dict_get_string(dict, "text", true);
+ }
+ if (tv_dict_find(dict, "texthl", -1) != NULL) {
+ texthl = tv_dict_get_string(dict, "texthl", true);
+ }
+ if (tv_dict_find(dict, "numhl", -1) != NULL) {
+ numhl = tv_dict_get_string(dict, "numhl", true);
+ }
+ }
+
+ if (sign_define_by_name((char_u *)name, (char_u *)icon, (char_u *)linehl,
+ (char_u *)text, (char_u *)texthl, (char_u *)numhl)
+ == OK) {
+ rettv->vval.v_number = 0;
+ }
+
+ xfree(icon);
+ xfree(linehl);
+ xfree(text);
+ xfree(texthl);
+ xfree(numhl);
+}
+
+/// "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 identifer
+ 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)
+{
+ int sign_id;
+ char_u *group = NULL;
+ const char *sign_name;
+ buf_T *buf;
+ dict_T *dict;
+ dictitem_T *di;
+ linenr_T lnum = 0;
+ int prio = SIGN_DEF_PRIO;
+ bool notanum = false;
+
+ rettv->vval.v_number = -1;
+
+ // Sign identifer
+ 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 *group_chk = tv_get_string_chk(&argvars[1]);
+ if (group_chk == NULL) {
+ return;
+ }
+ if (group_chk[0] == '\0') {
+ group = NULL; // global sign group
+ } else {
+ group = vim_strsave((const char_u *)group_chk);
+ }
+
+ // Sign name
+ sign_name = tv_get_string_chk(&argvars[2]);
+ if (sign_name == NULL) {
+ goto cleanup;
+ }
+
+ // Buffer to place the sign
+ buf = get_buf_arg(&argvars[3]);
+ if (buf == NULL) {
+ goto cleanup;
+ }
+
+ if (argvars[4].v_type != VAR_UNKNOWN) {
+ if (argvars[4].v_type != VAR_DICT
+ || ((dict = argvars[4].vval.v_dict) == NULL)) {
+ EMSG(_(e_dictreq));
+ goto cleanup;
+ }
+
+ // Line number where the sign is to be placed
+ if ((di = tv_dict_find(dict, "lnum", -1)) != NULL) {
+ lnum = (linenr_T)tv_get_number_chk(&di->di_tv, &notanum);
+ if (notanum) {
+ goto cleanup;
+ }
+ (void)lnum;
+ lnum = tv_get_lnum(&di->di_tv);
+ }
+ if ((di = tv_dict_find(dict, "priority", -1)) != NULL) {
+ // Sign priority
+ prio = (int)tv_get_number_chk(&di->di_tv, &notanum);
+ if (notanum) {
+ goto cleanup;
+ }
+ }
+ }
+
+ if (sign_place(&sign_id, group, (const char_u *)sign_name, buf, lnum, prio)
+ == OK) {
+ rettv->vval.v_number = sign_id;
+ }
+
+cleanup:
+ xfree(group);
+}
+
+/// "sign_undefine()" function
+static void f_sign_undefine(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *name;
+
+ 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;
+ dictitem_T *di;
+ int sign_id = 0;
+ buf_T *buf = NULL;
+ char_u *group = NULL;
+
+ rettv->vval.v_number = -1;
+
+ if (argvars[0].v_type != VAR_STRING) {
+ EMSG(_(e_invarg));
+ return;
+ }
+
+ const char *group_chk = tv_get_string(&argvars[0]);
+ if (group_chk[0] == '\0') {
+ group = NULL; // global sign group
+ } else {
+ group = vim_strsave((const char_u *)group_chk);
+ }
+
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ if (argvars[1].v_type != VAR_DICT) {
+ EMSG(_(e_dictreq));
+ goto cleanup;
+ }
+ dict = argvars[1].vval.v_dict;
+
+ if ((di = tv_dict_find(dict, "buffer", -1)) != NULL) {
+ buf = get_buf_arg(&di->di_tv);
+ if (buf == NULL) {
+ goto cleanup;
+ }
+ }
+ if (tv_dict_find(dict, "id", -1) != NULL) {
+ sign_id = tv_dict_get_number(dict, "id");
+ }
+ }
+
+ if (buf == NULL) {
+ // Delete the sign in all the buffers
+ FOR_ALL_BUFFERS(cbuf) {
+ if (sign_unplace(sign_id, group, cbuf, 0) == OK) {
+ rettv->vval.v_number = 0;
+ }
+ }
+ } else {
+ if (sign_unplace(sign_id, group, buf, 0) == OK) {
+ rettv->vval.v_number = 0;
+ }
+ }
+
+cleanup:
+ xfree(group);
+}
+
+/*
+ * "simplify()" function
+ */
+static void f_simplify(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *const p = tv_get_string(&argvars[0]);
+ rettv->vval.v_string = (char_u *)xstrdup(p);
+ simplify_filename(rettv->vval.v_string); // Simplify in place.
+ rettv->v_type = VAR_STRING;
+}
+
+/// "sockconnect()" function
+static void f_sockconnect(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (argvars[0].v_type != VAR_STRING || argvars[1].v_type != VAR_STRING) {
+ EMSG(_(e_invarg));
+ return;
+ }
+ if (argvars[2].v_type != VAR_DICT && argvars[2].v_type != VAR_UNKNOWN) {
+ // Wrong argument types
+ EMSG2(_(e_invarg2), "expected dictionary");
+ return;
+ }
+
+ const char *mode = tv_get_string(&argvars[0]);
+ const char *address = tv_get_string(&argvars[1]);
+
+ bool tcp;
+ if (strcmp(mode, "tcp") == 0) {
+ tcp = true;
+ } else if (strcmp(mode, "pipe") == 0) {
+ tcp = false;
+ } else {
+ EMSG2(_(e_invarg2), "invalid mode");
+ return;
+ }
+
+ bool rpc = false;
+ CallbackReader on_data = CALLBACK_READER_INIT;
+ if (argvars[2].v_type == VAR_DICT) {
+ dict_T *opts = argvars[2].vval.v_dict;
+ rpc = tv_dict_get_number(opts, "rpc") != 0;
+
+ if (!tv_dict_get_callback(opts, S_LEN("on_data"), &on_data.cb)) {
+ return;
+ }
+ on_data.buffered = tv_dict_get_number(opts, "data_buffered");
+ if (on_data.buffered && on_data.cb.type == kCallbackNone) {
+ on_data.self = opts;
+ }
+ }
+
+ const char *error = NULL;
+ uint64_t id = channel_connect(tcp, address, rpc, on_data, 50, &error);
+
+ if (error) {
+ EMSG2(_("connection failed: %s"), error);
+ }
+
+ rettv->vval.v_number = (varnumber_T)id;
+ rettv->v_type = VAR_NUMBER;
+}
+
+/// struct storing information about current sort
+typedef struct {
+ int item_compare_ic;
+ bool item_compare_numeric;
+ bool item_compare_numbers;
+ bool item_compare_float;
+ const char *item_compare_func;
+ partial_T *item_compare_partial;
+ dict_T *item_compare_selfdict;
+ bool item_compare_func_err;
+} sortinfo_T;
+static sortinfo_T *sortinfo = NULL;
+
+#define ITEM_COMPARE_FAIL 999
+
+/*
+ * Compare functions for f_sort() and f_uniq() below.
+ */
+static int item_compare(const void *s1, const void *s2, bool keep_zero)
+{
+ ListSortItem *const si1 = (ListSortItem *)s1;
+ ListSortItem *const si2 = (ListSortItem *)s2;
+
+ typval_T *const tv1 = TV_LIST_ITEM_TV(si1->item);
+ typval_T *const tv2 = TV_LIST_ITEM_TV(si2->item);
+
+ int res;
+
+ if (sortinfo->item_compare_numbers) {
+ const varnumber_T v1 = tv_get_number(tv1);
+ const varnumber_T v2 = tv_get_number(tv2);
+
+ res = v1 == v2 ? 0 : v1 > v2 ? 1 : -1;
+ goto item_compare_end;
+ }
+
+ if (sortinfo->item_compare_float) {
+ const float_T v1 = tv_get_float(tv1);
+ const float_T v2 = tv_get_float(tv2);
+
+ res = v1 == v2 ? 0 : v1 > v2 ? 1 : -1;
+ goto item_compare_end;
+ }
+
+ char *tofree1 = NULL;
+ char *tofree2 = NULL;
+ char *p1;
+ char *p2;
+
+ // encode_tv2string() puts quotes around a string and allocates memory. Don't
+ // do that for string variables. Use a single quote when comparing with
+ // a non-string to do what the docs promise.
+ if (tv1->v_type == VAR_STRING) {
+ if (tv2->v_type != VAR_STRING || sortinfo->item_compare_numeric) {
+ p1 = "'";
+ } else {
+ p1 = (char *)tv1->vval.v_string;
+ }
+ } else {
+ tofree1 = p1 = encode_tv2string(tv1, NULL);
+ }
+ if (tv2->v_type == VAR_STRING) {
+ if (tv1->v_type != VAR_STRING || sortinfo->item_compare_numeric) {
+ p2 = "'";
+ } else {
+ p2 = (char *)tv2->vval.v_string;
+ }
+ } else {
+ tofree2 = p2 = encode_tv2string(tv2, NULL);
+ }
+ if (p1 == NULL) {
+ p1 = "";
+ }
+ if (p2 == NULL) {
+ p2 = "";
+ }
+ if (!sortinfo->item_compare_numeric) {
+ if (sortinfo->item_compare_ic) {
+ res = STRICMP(p1, p2);
+ } else {
+ res = STRCMP(p1, p2);
+ }
+ } else {
+ double n1, n2;
+ n1 = strtod(p1, &p1);
+ n2 = strtod(p2, &p2);
+ res = n1 == n2 ? 0 : n1 > n2 ? 1 : -1;
+ }
+
+ xfree(tofree1);
+ xfree(tofree2);
+
+item_compare_end:
+ // When the result would be zero, compare the item indexes. Makes the
+ // sort stable.
+ if (res == 0 && !keep_zero) {
+ // WARNING: When using uniq si1 and si2 are actually listitem_T **, no
+ // indexes are there.
+ res = si1->idx > si2->idx ? 1 : -1;
+ }
+ return res;
+}
+
+static int item_compare_keeping_zero(const void *s1, const void *s2)
+{
+ return item_compare(s1, s2, true);
+}
+
+static int item_compare_not_keeping_zero(const void *s1, const void *s2)
+{
+ return item_compare(s1, s2, false);
+}
+
+static int item_compare2(const void *s1, const void *s2, bool keep_zero)
+{
+ ListSortItem *si1, *si2;
+ int res;
+ typval_T rettv;
+ typval_T argv[3];
+ int dummy;
+ const char *func_name;
+ partial_T *partial = sortinfo->item_compare_partial;
+
+ // shortcut after failure in previous call; compare all items equal
+ if (sortinfo->item_compare_func_err) {
+ return 0;
+ }
+
+ si1 = (ListSortItem *)s1;
+ si2 = (ListSortItem *)s2;
+
+ if (partial == NULL) {
+ func_name = sortinfo->item_compare_func;
+ } else {
+ func_name = (const char *)partial_name(partial);
+ }
+
+ // Copy the values. This is needed to be able to set v_lock to VAR_FIXED
+ // in the copy without changing the original list items.
+ tv_copy(TV_LIST_ITEM_TV(si1->item), &argv[0]);
+ tv_copy(TV_LIST_ITEM_TV(si2->item), &argv[1]);
+
+ rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this
+ res = call_func((const char_u *)func_name,
+ (int)STRLEN(func_name),
+ &rettv, 2, argv, NULL, 0L, 0L, &dummy, true,
+ partial, sortinfo->item_compare_selfdict);
+ tv_clear(&argv[0]);
+ tv_clear(&argv[1]);
+
+ if (res == FAIL) {
+ res = ITEM_COMPARE_FAIL;
+ } else {
+ res = tv_get_number_chk(&rettv, &sortinfo->item_compare_func_err);
+ }
+ if (sortinfo->item_compare_func_err) {
+ res = ITEM_COMPARE_FAIL; // return value has wrong type
+ }
+ tv_clear(&rettv);
+
+ // When the result would be zero, compare the pointers themselves. Makes
+ // the sort stable.
+ if (res == 0 && !keep_zero) {
+ // WARNING: When using uniq si1 and si2 are actually listitem_T **, no
+ // indexes are there.
+ res = si1->idx > si2->idx ? 1 : -1;
+ }
+
+ return res;
+}
+
+static int item_compare2_keeping_zero(const void *s1, const void *s2)
+{
+ return item_compare2(s1, s2, true);
+}
+
+static int item_compare2_not_keeping_zero(const void *s1, const void *s2)
+{
+ return item_compare2(s1, s2, false);
+}
+
+/*
+ * "sort({list})" function
+ */
+static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort)
+{
+ ListSortItem *ptrs;
+ long len;
+ long i;
+
+ // Pointer to current info struct used in compare function. Save and restore
+ // the current one for nested calls.
+ sortinfo_T info;
+ sortinfo_T *old_sortinfo = sortinfo;
+ sortinfo = &info;
+
+ const char *const arg_errmsg = (sort
+ ? N_("sort() argument")
+ : N_("uniq() argument"));
+
+ if (argvars[0].v_type != VAR_LIST) {
+ EMSG2(_(e_listarg), sort ? "sort()" : "uniq()");
+ } else {
+ list_T *const l = argvars[0].vval.v_list;
+ if (tv_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE)) {
+ goto theend;
+ }
+ tv_list_set_ret(rettv, l);
+
+ len = tv_list_len(l);
+ if (len <= 1) {
+ goto theend; // short list sorts pretty quickly
+ }
+
+ info.item_compare_ic = false;
+ info.item_compare_numeric = false;
+ info.item_compare_numbers = false;
+ info.item_compare_float = false;
+ info.item_compare_func = NULL;
+ info.item_compare_partial = NULL;
+ info.item_compare_selfdict = NULL;
+
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ // optional second argument: {func}
+ if (argvars[1].v_type == VAR_FUNC) {
+ info.item_compare_func = (const char *)argvars[1].vval.v_string;
+ } else if (argvars[1].v_type == VAR_PARTIAL) {
+ info.item_compare_partial = argvars[1].vval.v_partial;
+ } else {
+ bool error = false;
+
+ i = tv_get_number_chk(&argvars[1], &error);
+ if (error) {
+ goto theend; // type error; errmsg already given
+ }
+ if (i == 1) {
+ info.item_compare_ic = true;
+ } else if (argvars[1].v_type != VAR_NUMBER) {
+ info.item_compare_func = tv_get_string(&argvars[1]);
+ } else if (i != 0) {
+ EMSG(_(e_invarg));
+ goto theend;
+ }
+ if (info.item_compare_func != NULL) {
+ if (*info.item_compare_func == NUL) {
+ // empty string means default sort
+ info.item_compare_func = NULL;
+ } else if (strcmp(info.item_compare_func, "n") == 0) {
+ info.item_compare_func = NULL;
+ info.item_compare_numeric = true;
+ } else if (strcmp(info.item_compare_func, "N") == 0) {
+ info.item_compare_func = NULL;
+ info.item_compare_numbers = true;
+ } else if (strcmp(info.item_compare_func, "f") == 0) {
+ info.item_compare_func = NULL;
+ info.item_compare_float = true;
+ } else if (strcmp(info.item_compare_func, "i") == 0) {
+ info.item_compare_func = NULL;
+ info.item_compare_ic = true;
+ }
+ }
+ }
+
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ // optional third argument: {dict}
+ if (argvars[2].v_type != VAR_DICT) {
+ EMSG(_(e_dictreq));
+ goto theend;
+ }
+ info.item_compare_selfdict = argvars[2].vval.v_dict;
+ }
+ }
+
+ // Make an array with each entry pointing to an item in the List.
+ ptrs = xmalloc((size_t)(len * sizeof(ListSortItem)));
+
+ if (sort) {
+ info.item_compare_func_err = false;
+ tv_list_item_sort(l, ptrs,
+ ((info.item_compare_func == NULL
+ && info.item_compare_partial == NULL)
+ ? item_compare_not_keeping_zero
+ : item_compare2_not_keeping_zero),
+ &info.item_compare_func_err);
+ if (info.item_compare_func_err) {
+ EMSG(_("E702: Sort compare function failed"));
+ }
+ } else {
+ ListSorter item_compare_func_ptr;
+
+ // f_uniq(): ptrs will be a stack of items to remove.
+ info.item_compare_func_err = false;
+ if (info.item_compare_func != NULL
+ || info.item_compare_partial != NULL) {
+ item_compare_func_ptr = item_compare2_keeping_zero;
+ } else {
+ item_compare_func_ptr = item_compare_keeping_zero;
+ }
+
+ int idx = 0;
+ for (listitem_T *li = TV_LIST_ITEM_NEXT(l, tv_list_first(l))
+ ; li != NULL;) {
+ listitem_T *const prev_li = TV_LIST_ITEM_PREV(l, li);
+ if (item_compare_func_ptr(&prev_li, &li) == 0) {
+ if (info.item_compare_func_err) { // -V547
+ EMSG(_("E882: Uniq compare function failed"));
+ break;
+ }
+ li = tv_list_item_remove(l, li);
+ } else {
+ idx++;
+ li = TV_LIST_ITEM_NEXT(l, li);
+ }
+ }
+ }
+
+ xfree(ptrs);
+ }
+
+theend:
+ sortinfo = old_sortinfo;
+}
+
+/// "sort"({list})" function
+static void f_sort(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ do_sort_uniq(argvars, rettv, true);
+}
+
+/// "stdioopen()" function
+static void f_stdioopen(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (argvars[0].v_type != VAR_DICT) {
+ EMSG(_(e_invarg));
+ return;
+ }
+
+
+ bool rpc = false;
+ CallbackReader on_stdin = CALLBACK_READER_INIT;
+ dict_T *opts = argvars[0].vval.v_dict;
+ rpc = tv_dict_get_number(opts, "rpc") != 0;
+
+ if (!tv_dict_get_callback(opts, S_LEN("on_stdin"), &on_stdin.cb)) {
+ return;
+ }
+ on_stdin.buffered = tv_dict_get_number(opts, "stdin_buffered");
+ if (on_stdin.buffered && on_stdin.cb.type == kCallbackNone) {
+ on_stdin.self = opts;
+ }
+
+ const char *error;
+ uint64_t id = channel_from_stdio(rpc, on_stdin, &error);
+ if (!id) {
+ EMSG2(e_stdiochan2, error);
+ }
+
+
+ rettv->vval.v_number = (varnumber_T)id;
+ rettv->v_type = VAR_NUMBER;
+}
+
+/// "uniq({list})" function
+static void f_uniq(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ do_sort_uniq(argvars, rettv, false);
+}
+
+// "reltimefloat()" function
+static void f_reltimefloat(typval_T *argvars , typval_T *rettv, FunPtr fptr)
+ FUNC_ATTR_NONNULL_ALL
+{
+ proftime_T tm;
+
+ rettv->v_type = VAR_FLOAT;
+ rettv->vval.v_float = 0;
+ if (list2proftime(&argvars[0], &tm) == OK) {
+ rettv->vval.v_float = (float_T)profile_signed(tm) / 1000000000.0;
+ }
+}
+
+/*
+ * "soundfold({word})" function
+ */
+static void f_soundfold(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_STRING;
+ const char *const s = tv_get_string(&argvars[0]);
+ rettv->vval.v_string = (char_u *)eval_soundfold(s);
+}
+
+/*
+ * "spellbadword()" function
+ */
+static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *word = "";
+ hlf_T attr = HLF_COUNT;
+ size_t len = 0;
+
+ if (argvars[0].v_type == VAR_UNKNOWN) {
+ // Find the start and length of the badly spelled word.
+ len = spell_move_to(curwin, FORWARD, true, true, &attr);
+ if (len != 0) {
+ word = (char *)get_cursor_pos_ptr();
+ curwin->w_set_curswant = true;
+ }
+ } else if (curwin->w_p_spell && *curbuf->b_s.b_p_spl != NUL) {
+ const char *str = tv_get_string_chk(&argvars[0]);
+ int capcol = -1;
+
+ if (str != NULL) {
+ // Check the argument for spelling.
+ while (*str != NUL) {
+ len = spell_check(curwin, (char_u *)str, &attr, &capcol, false);
+ if (attr != HLF_COUNT) {
+ word = str;
+ break;
+ }
+ str += len;
+ capcol -= len;
+ len = 0;
+ }
+ }
+ }
+
+ assert(len <= INT_MAX);
+ tv_list_alloc_ret(rettv, 2);
+ tv_list_append_string(rettv->vval.v_list, word, len);
+ tv_list_append_string(rettv->vval.v_list,
+ (attr == HLF_SPB ? "bad"
+ : attr == HLF_SPR ? "rare"
+ : attr == HLF_SPL ? "local"
+ : attr == HLF_SPC ? "caps"
+ : NULL), -1);
+}
+
+/*
+ * "spellsuggest()" function
+ */
+static void f_spellsuggest(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ bool typeerr = false;
+ int maxcount;
+ garray_T ga = GA_EMPTY_INIT_VALUE;
+ bool need_capital = false;
+
+ if (curwin->w_p_spell && *curwin->w_s->b_p_spl != NUL) {
+ const char *const str = tv_get_string(&argvars[0]);
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ maxcount = tv_get_number_chk(&argvars[1], &typeerr);
+ if (maxcount <= 0) {
+ goto f_spellsuggest_return;
+ }
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ need_capital = tv_get_number_chk(&argvars[2], &typeerr);
+ if (typeerr) {
+ goto f_spellsuggest_return;
+ }
+ }
+ } else {
+ maxcount = 25;
+ }
+
+ spell_suggest_list(&ga, (char_u *)str, maxcount, need_capital, false);
+ }
+
+f_spellsuggest_return:
+ tv_list_alloc_ret(rettv, (ptrdiff_t)ga.ga_len);
+ for (int i = 0; i < ga.ga_len; i++) {
+ char *const p = ((char **)ga.ga_data)[i];
+ tv_list_append_allocated_string(rettv->vval.v_list, p);
+ }
+ ga_clear(&ga);
+}
+
+static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char_u *save_cpo;
+ int match;
+ colnr_T col = 0;
+ bool keepempty = false;
+ bool typeerr = false;
+
+ // Make 'cpoptions' empty, the 'l' flag should not be used here.
+ save_cpo = p_cpo;
+ p_cpo = (char_u *)"";
+
+ const char *str = tv_get_string(&argvars[0]);
+ const char *pat = NULL;
+ char patbuf[NUMBUFLEN];
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ pat = tv_get_string_buf_chk(&argvars[1], patbuf);
+ if (pat == NULL) {
+ typeerr = true;
+ }
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ keepempty = (bool)tv_get_number_chk(&argvars[2], &typeerr);
+ }
+ }
+ if (pat == NULL || *pat == NUL) {
+ pat = "[\\x01- ]\\+";
+ }
+
+ tv_list_alloc_ret(rettv, kListLenMayKnow);
+
+ if (typeerr) {
+ return;
+ }
+
+ regmatch_T regmatch = {
+ .regprog = vim_regcomp((char_u *)pat, RE_MAGIC + RE_STRING),
+ .startp = { NULL },
+ .endp = { NULL },
+ .rm_ic = false,
+ };
+ if (regmatch.regprog != NULL) {
+ while (*str != NUL || keepempty) {
+ if (*str == NUL) {
+ match = false; // Empty item at the end.
+ } else {
+ match = vim_regexec_nl(&regmatch, (char_u *)str, col);
+ }
+ const char *end;
+ if (match) {
+ end = (const char *)regmatch.startp[0];
+ } else {
+ end = str + strlen(str);
+ }
+ if (keepempty || end > str || (tv_list_len(rettv->vval.v_list) > 0
+ && *str != NUL
+ && match
+ && end < (const char *)regmatch.endp[0])) {
+ tv_list_append_string(rettv->vval.v_list, str, end - str);
+ }
+ if (!match) {
+ break;
+ }
+ // Advance to just after the match.
+ if (regmatch.endp[0] > (char_u *)str) {
+ col = 0;
+ } else {
+ // Don't get stuck at the same match.
+ col = (*mb_ptr2len)(regmatch.endp[0]);
+ }
+ str = (const char *)regmatch.endp[0];
+ }
+
+ vim_regfree(regmatch.regprog);
+ }
+
+ p_cpo = save_cpo;
+}
+
+/// "stdpath(type)" function
+static void f_stdpath(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+
+ const char *const p = tv_get_string_chk(&argvars[0]);
+ if (p == NULL) {
+ return; // Type error; errmsg already given.
+ }
+
+ if (strequal(p, "config")) {
+ rettv->vval.v_string = (char_u *)get_xdg_home(kXDGConfigHome);
+ } else if (strequal(p, "data")) {
+ rettv->vval.v_string = (char_u *)get_xdg_home(kXDGDataHome);
+ } else if (strequal(p, "cache")) {
+ rettv->vval.v_string = (char_u *)get_xdg_home(kXDGCacheHome);
+ } else if (strequal(p, "config_dirs")) {
+ get_xdg_var_list(kXDGConfigDirs, rettv);
+ } else if (strequal(p, "data_dirs")) {
+ get_xdg_var_list(kXDGDataDirs, rettv);
+ } else {
+ EMSG2(_("E6100: \"%s\" is not a valid stdpath"), p);
+ }
+}
+
+/*
+ * "str2float()" function
+ */
+static void f_str2float(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char_u *p = skipwhite((const char_u *)tv_get_string(&argvars[0]));
+ bool isneg = (*p == '-');
+
+ if (*p == '+' || *p == '-') {
+ p = skipwhite(p + 1);
+ }
+ (void)string2float((char *)p, &rettv->vval.v_float);
+ if (isneg) {
+ rettv->vval.v_float *= -1;
+ }
+ rettv->v_type = VAR_FLOAT;
+}
+
+// "str2list()" function
+static void f_str2list(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ tv_list_alloc_ret(rettv, kListLenUnknown);
+ const char_u *p = (const char_u *)tv_get_string(&argvars[0]);
+
+ for (; *p != NUL; p += utf_ptr2len(p)) {
+ tv_list_append_number(rettv->vval.v_list, utf_ptr2char(p));
+ }
+}
+
+// "str2nr()" function
+static void f_str2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int base = 10;
+ varnumber_T n;
+ int what;
+
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ base = tv_get_number(&argvars[1]);
+ if (base != 2 && base != 8 && base != 10 && base != 16) {
+ EMSG(_(e_invarg));
+ return;
+ }
+ }
+
+ char_u *p = skipwhite((const char_u *)tv_get_string(&argvars[0]));
+ bool isneg = (*p == '-');
+ if (*p == '+' || *p == '-') {
+ p = skipwhite(p + 1);
+ }
+ switch (base) {
+ case 2: {
+ what = STR2NR_BIN | STR2NR_FORCE;
+ break;
+ }
+ case 8: {
+ what = STR2NR_OCT | STR2NR_FORCE;
+ break;
+ }
+ case 16: {
+ what = STR2NR_HEX | STR2NR_FORCE;
+ break;
+ }
+ default: {
+ what = 0;
+ }
+ }
+ vim_str2nr(p, NULL, NULL, what, &n, NULL, 0);
+ if (isneg) {
+ rettv->vval.v_number = -n;
+ } else {
+ rettv->vval.v_number = n;
+ }
+}
+
+/*
+ * "strftime({format}[, {time}])" function
+ */
+static void f_strftime(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ time_t seconds;
+
+ rettv->v_type = VAR_STRING;
+
+ char *p = (char *)tv_get_string(&argvars[0]);
+ if (argvars[1].v_type == VAR_UNKNOWN) {
+ seconds = time(NULL);
+ } else {
+ seconds = (time_t)tv_get_number(&argvars[1]);
+ }
+
+ struct tm curtime;
+ struct tm *curtime_ptr = os_localtime_r(&seconds, &curtime);
+ // MSVC returns NULL for an invalid value of seconds.
+ if (curtime_ptr == NULL) {
+ rettv->vval.v_string = vim_strsave((char_u *)_("(Invalid)"));
+ } else {
+ vimconv_T conv;
+ char_u *enc;
+
+ conv.vc_type = CONV_NONE;
+ enc = enc_locale();
+ convert_setup(&conv, p_enc, enc);
+ if (conv.vc_type != CONV_NONE) {
+ p = (char *)string_convert(&conv, (char_u *)p, NULL);
+ }
+ char result_buf[256];
+ if (p != NULL) {
+ (void)strftime(result_buf, sizeof(result_buf), p, curtime_ptr);
+ } else {
+ result_buf[0] = NUL;
+ }
+
+ if (conv.vc_type != CONV_NONE) {
+ xfree(p);
+ }
+ convert_setup(&conv, enc, p_enc);
+ if (conv.vc_type != CONV_NONE) {
+ rettv->vval.v_string = string_convert(&conv, (char_u *)result_buf, NULL);
+ } else {
+ rettv->vval.v_string = (char_u *)xstrdup(result_buf);
+ }
+
+ // Release conversion descriptors.
+ convert_setup(&conv, NULL, NULL);
+ xfree(enc);
+ }
+}
+
+// "strgetchar()" function
+static void f_strgetchar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = -1;
+
+ const char *const str = tv_get_string_chk(&argvars[0]);
+ if (str == NULL) {
+ return;
+ }
+ bool error = false;
+ varnumber_T charidx = tv_get_number_chk(&argvars[1], &error);
+ if (error) {
+ return;
+ }
+
+ const size_t len = STRLEN(str);
+ size_t byteidx = 0;
+
+ while (charidx >= 0 && byteidx < len) {
+ if (charidx == 0) {
+ rettv->vval.v_number = utf_ptr2char((const char_u *)str + byteidx);
+ break;
+ }
+ charidx--;
+ byteidx += MB_CPTR2LEN((const char_u *)str + byteidx);
+ }
+}
+
+/*
+ * "stridx()" function
+ */
+static void f_stridx(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = -1;
+
+ char buf[NUMBUFLEN];
+ const char *const needle = tv_get_string_chk(&argvars[1]);
+ const char *haystack = tv_get_string_buf_chk(&argvars[0], buf);
+ const char *const haystack_start = haystack;
+ if (needle == NULL || haystack == NULL) {
+ return; // Type error; errmsg already given.
+ }
+
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ bool error = false;
+
+ const ptrdiff_t start_idx = (ptrdiff_t)tv_get_number_chk(&argvars[2],
+ &error);
+ if (error || start_idx >= (ptrdiff_t)strlen(haystack)) {
+ return;
+ }
+ if (start_idx >= 0) {
+ haystack += start_idx;
+ }
+ }
+
+ const char *pos = strstr(haystack, needle);
+ if (pos != NULL) {
+ rettv->vval.v_number = (varnumber_T)(pos - haystack_start);
+ }
+}
+
+/*
+ * "string()" function
+ */
+void f_string(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = (char_u *)encode_tv2string(&argvars[0], NULL);
+}
+
+/*
+ * "strlen()" function
+ */
+static void f_strlen(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = (varnumber_T)strlen(tv_get_string(&argvars[0]));
+}
+
+/*
+ * "strchars()" function
+ */
+static void f_strchars(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *s = tv_get_string(&argvars[0]);
+ int skipcc = 0;
+ varnumber_T len = 0;
+ int (*func_mb_ptr2char_adv)(const char_u **pp);
+
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ skipcc = tv_get_number_chk(&argvars[1], NULL);
+ }
+ if (skipcc < 0 || skipcc > 1) {
+ EMSG(_(e_invarg));
+ } else {
+ func_mb_ptr2char_adv = skipcc ? mb_ptr2char_adv : mb_cptr2char_adv;
+ while (*s != NUL) {
+ func_mb_ptr2char_adv((const char_u **)&s);
+ len++;
+ }
+ rettv->vval.v_number = len;
+ }
+}
+
+/*
+ * "strdisplaywidth()" function
+ */
+static void f_strdisplaywidth(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *const s = tv_get_string(&argvars[0]);
+ int col = 0;
+
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ col = tv_get_number(&argvars[1]);
+ }
+
+ rettv->vval.v_number = (varnumber_T)(linetabsize_col(col, (char_u *)s) - col);
+}
+
+/*
+ * "strwidth()" function
+ */
+static void f_strwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *const s = tv_get_string(&argvars[0]);
+
+ rettv->vval.v_number = (varnumber_T)mb_string2cells((const char_u *)s);
+}
+
+// "strcharpart()" function
+static void f_strcharpart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *const p = tv_get_string(&argvars[0]);
+ const size_t slen = STRLEN(p);
+
+ int nbyte = 0;
+ bool error = false;
+ varnumber_T nchar = tv_get_number_chk(&argvars[1], &error);
+ if (!error) {
+ if (nchar > 0) {
+ while (nchar > 0 && (size_t)nbyte < slen) {
+ nbyte += MB_CPTR2LEN((const char_u *)p + nbyte);
+ nchar--;
+ }
+ } else {
+ nbyte = nchar;
+ }
+ }
+ int len = 0;
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ int charlen = tv_get_number(&argvars[2]);
+ while (charlen > 0 && nbyte + len < (int)slen) {
+ int off = nbyte + len;
+
+ if (off < 0) {
+ len += 1;
+ } else {
+ len += (size_t)MB_CPTR2LEN((const char_u *)p + off);
+ }
+ charlen--;
+ }
+ } else {
+ len = slen - nbyte; // default: all bytes that are available.
+ }
+
+ // Only return the overlap between the specified part and the actual
+ // string.
+ if (nbyte < 0) {
+ len += nbyte;
+ nbyte = 0;
+ } else if ((size_t)nbyte > slen) {
+ nbyte = slen;
+ }
+ if (len < 0) {
+ len = 0;
+ } else if (nbyte + len > (int)slen) {
+ len = slen - nbyte;
+ }
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = (char_u *)xstrndup(p + nbyte, (size_t)len);
+}
+
+/*
+ * "strpart()" function
+ */
+static void f_strpart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ bool error = false;
+
+ const char *const p = tv_get_string(&argvars[0]);
+ const size_t slen = strlen(p);
+
+ varnumber_T n = tv_get_number_chk(&argvars[1], &error);
+ varnumber_T len;
+ if (error) {
+ len = 0;
+ } else if (argvars[2].v_type != VAR_UNKNOWN) {
+ len = tv_get_number(&argvars[2]);
+ } else {
+ len = slen - n; // Default len: all bytes that are available.
+ }
+
+ // Only return the overlap between the specified part and the actual
+ // string.
+ if (n < 0) {
+ len += n;
+ n = 0;
+ } else if (n > (varnumber_T)slen) {
+ n = slen;
+ }
+ if (len < 0) {
+ len = 0;
+ } else if (n + len > (varnumber_T)slen) {
+ len = slen - n;
+ }
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = (char_u *)xmemdupz(p + n, (size_t)len);
+}
+
+/*
+ * "strridx()" function
+ */
+static void f_strridx(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char buf[NUMBUFLEN];
+ const char *const needle = tv_get_string_chk(&argvars[1]);
+ const char *const haystack = tv_get_string_buf_chk(&argvars[0], buf);
+
+ rettv->vval.v_number = -1;
+ if (needle == NULL || haystack == NULL) {
+ return; // Type error; errmsg already given.
+ }
+
+ const size_t haystack_len = STRLEN(haystack);
+ ptrdiff_t end_idx;
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ // Third argument: upper limit for index.
+ end_idx = (ptrdiff_t)tv_get_number_chk(&argvars[2], NULL);
+ if (end_idx < 0) {
+ return; // Can never find a match.
+ }
+ } else {
+ end_idx = (ptrdiff_t)haystack_len;
+ }
+
+ const char *lastmatch = NULL;
+ if (*needle == NUL) {
+ // Empty string matches past the end.
+ lastmatch = haystack + end_idx;
+ } else {
+ for (const char *rest = haystack; *rest != NUL; rest++) {
+ rest = strstr(rest, needle);
+ if (rest == NULL || rest > haystack + end_idx) {
+ break;
+ }
+ lastmatch = rest;
+ }
+ }
+
+ if (lastmatch != NULL) {
+ rettv->vval.v_number = (varnumber_T)(lastmatch - haystack);
+ }
+}
+
+/*
+ * "strtrans()" function
+ */
+static void f_strtrans(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = (char_u *)transstr(tv_get_string(&argvars[0]));
+}
+
+/*
+ * "submatch()" function
+ */
+static void f_submatch(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ bool error = false;
+ int no = (int)tv_get_number_chk(&argvars[0], &error);
+ if (error) {
+ return;
+ }
+
+ if (no < 0 || no >= NSUBEXP) {
+ emsgf(_("E935: invalid submatch number: %d"), no);
+ return;
+ }
+ int retList = 0;
+
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ retList = tv_get_number_chk(&argvars[1], &error);
+ if (error) {
+ return;
+ }
+ }
+
+ if (retList == 0) {
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = reg_submatch(no);
+ } else {
+ rettv->v_type = VAR_LIST;
+ rettv->vval.v_list = reg_submatch_list(no);
+ }
+}
+
+/*
+ * "substitute()" function
+ */
+static void f_substitute(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char patbuf[NUMBUFLEN];
+ char subbuf[NUMBUFLEN];
+ char flagsbuf[NUMBUFLEN];
+
+ const char *const str = tv_get_string_chk(&argvars[0]);
+ const char *const pat = tv_get_string_buf_chk(&argvars[1], patbuf);
+ const char *sub = NULL;
+ const char *const flg = tv_get_string_buf_chk(&argvars[3], flagsbuf);
+
+ typval_T *expr = NULL;
+ if (tv_is_func(argvars[2])) {
+ expr = &argvars[2];
+ } else {
+ sub = tv_get_string_buf_chk(&argvars[2], subbuf);
+ }
+
+ rettv->v_type = VAR_STRING;
+ if (str == NULL || pat == NULL || (sub == NULL && expr == NULL)
+ || flg == NULL) {
+ rettv->vval.v_string = NULL;
+ } else {
+ rettv->vval.v_string = do_string_sub((char_u *)str, (char_u *)pat,
+ (char_u *)sub, expr, (char_u *)flg);
+ }
+}
+
+/// "swapinfo(swap_filename)" function
+static void f_swapinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ tv_dict_alloc_ret(rettv);
+ get_b0_dict(tv_get_string(argvars), rettv->vval.v_dict);
+}
+
+/// "swapname(expr)" function
+static void f_swapname(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_STRING;
+ buf_T *buf = tv_get_buf(&argvars[0], false);
+ if (buf == NULL
+ || buf->b_ml.ml_mfp == NULL
+ || buf->b_ml.ml_mfp->mf_fname == NULL) {
+ rettv->vval.v_string = NULL;
+ } else {
+ rettv->vval.v_string = vim_strsave(buf->b_ml.ml_mfp->mf_fname);
+ }
+}
+
+/// "synID(lnum, col, trans)" function
+static void f_synID(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ // -1 on type error (both)
+ const linenr_T lnum = tv_get_lnum(argvars);
+ const colnr_T col = (colnr_T)tv_get_number(&argvars[1]) - 1;
+
+ bool transerr = false;
+ const int trans = tv_get_number_chk(&argvars[2], &transerr);
+
+ int id = 0;
+ if (!transerr && lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count
+ && col >= 0 && (size_t)col < STRLEN(ml_get(lnum))) {
+ id = syn_get_id(curwin, lnum, col, trans, NULL, false);
+ }
+
+ rettv->vval.v_number = id;
+}
+
+/*
+ * "synIDattr(id, what [, mode])" function
+ */
+static void f_synIDattr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const int id = (int)tv_get_number(&argvars[0]);
+ const char *const what = tv_get_string(&argvars[1]);
+ int modec;
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ char modebuf[NUMBUFLEN];
+ const char *const mode = tv_get_string_buf(&argvars[2], modebuf);
+ modec = TOLOWER_ASC(mode[0]);
+ if (modec != 'c' && modec != 'g') {
+ modec = 0; // Replace invalid with current.
+ }
+ } else if (ui_rgb_attached()) {
+ modec = 'g';
+ } else {
+ modec = 'c';
+ }
+
+
+ const char *p = NULL;
+ switch (TOLOWER_ASC(what[0])) {
+ case 'b': {
+ if (TOLOWER_ASC(what[1]) == 'g') { // bg[#]
+ p = highlight_color(id, what, modec);
+ } else { // bold
+ p = highlight_has_attr(id, HL_BOLD, modec);
+ }
+ break;
+ }
+ case 'f': { // fg[#] or font
+ p = highlight_color(id, what, modec);
+ break;
+ }
+ case 'i': {
+ if (TOLOWER_ASC(what[1]) == 'n') { // inverse
+ p = highlight_has_attr(id, HL_INVERSE, modec);
+ } else { // italic
+ p = highlight_has_attr(id, HL_ITALIC, modec);
+ }
+ break;
+ }
+ case 'n': { // name
+ p = get_highlight_name_ext(NULL, id - 1, false);
+ break;
+ }
+ case 'r': { // reverse
+ p = highlight_has_attr(id, HL_INVERSE, modec);
+ break;
+ }
+ case 's': {
+ if (TOLOWER_ASC(what[1]) == 'p') { // sp[#]
+ p = highlight_color(id, what, modec);
+ } else if (TOLOWER_ASC(what[1]) == 't'
+ && TOLOWER_ASC(what[2]) == 'r') { // strikethrough
+ p = highlight_has_attr(id, HL_STRIKETHROUGH, modec);
+ } else { // standout
+ p = highlight_has_attr(id, HL_STANDOUT, modec);
+ }
+ break;
+ }
+ case 'u': {
+ if (STRLEN(what) <= 5 || TOLOWER_ASC(what[5]) != 'c') { // underline
+ p = highlight_has_attr(id, HL_UNDERLINE, modec);
+ } else { // undercurl
+ p = highlight_has_attr(id, HL_UNDERCURL, modec);
+ }
+ break;
+ }
+ }
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = (char_u *)(p == NULL ? p : xstrdup(p));
+}
+
+/*
+ * "synIDtrans(id)" function
+ */
+static void f_synIDtrans(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int id = tv_get_number(&argvars[0]);
+
+ if (id > 0) {
+ id = syn_get_final_id(id);
+ } else {
+ id = 0;
+ }
+
+ rettv->vval.v_number = id;
+}
+
+/*
+ * "synconcealed(lnum, col)" function
+ */
+static void f_synconcealed(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int syntax_flags = 0;
+ int cchar;
+ int matchid = 0;
+ char_u str[NUMBUFLEN];
+
+ tv_list_set_ret(rettv, NULL);
+
+ // -1 on type error (both)
+ const linenr_T lnum = tv_get_lnum(argvars);
+ const colnr_T col = (colnr_T)tv_get_number(&argvars[1]) - 1;
+
+ memset(str, NUL, sizeof(str));
+
+ if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count && col >= 0
+ && (size_t)col <= STRLEN(ml_get(lnum)) && curwin->w_p_cole > 0) {
+ (void)syn_get_id(curwin, lnum, col, false, NULL, false);
+ syntax_flags = get_syntax_info(&matchid);
+
+ // get the conceal character
+ if ((syntax_flags & HL_CONCEAL) && curwin->w_p_cole < 3) {
+ cchar = syn_get_sub_char();
+ if (cchar == NUL && curwin->w_p_cole == 1) {
+ cchar = (curwin->w_p_lcs_chars.conceal == NUL)
+ ? ' '
+ : curwin->w_p_lcs_chars.conceal;
+ }
+ if (cchar != NUL) {
+ utf_char2bytes(cchar, str);
+ }
+ }
+ }
+
+ tv_list_alloc_ret(rettv, 3);
+ tv_list_append_number(rettv->vval.v_list, (syntax_flags & HL_CONCEAL) != 0);
+ // -1 to auto-determine strlen
+ tv_list_append_string(rettv->vval.v_list, (const char *)str, -1);
+ tv_list_append_number(rettv->vval.v_list, matchid);
+}
+
+/*
+ * "synstack(lnum, col)" function
+ */
+static void f_synstack(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ tv_list_set_ret(rettv, NULL);
+
+ // -1 on type error (both)
+ const linenr_T lnum = tv_get_lnum(argvars);
+ const colnr_T col = (colnr_T)tv_get_number(&argvars[1]) - 1;
+
+ if (lnum >= 1
+ && lnum <= curbuf->b_ml.ml_line_count
+ && col >= 0
+ && (size_t)col <= STRLEN(ml_get(lnum))) {
+ tv_list_alloc_ret(rettv, kListLenMayKnow);
+ (void)syn_get_id(curwin, lnum, col, false, NULL, true);
+
+ int id;
+ int i = 0;
+ while ((id = syn_get_stack_item(i++)) >= 0) {
+ tv_list_append_number(rettv->vval.v_list, id);
+ }
+ }
+}
+
+/// f_system - the VimL system() function
+static void f_system(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ get_system_output_as_rettv(argvars, rettv, false);
+}
+
+static void f_systemlist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ get_system_output_as_rettv(argvars, rettv, true);
+}
+
+
+/*
+ * "tabpagebuflist()" function
+ */
+static void f_tabpagebuflist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ win_T *wp = NULL;
+
+ if (argvars[0].v_type == VAR_UNKNOWN) {
+ wp = firstwin;
+ } else {
+ tabpage_T *const tp = find_tabpage((int)tv_get_number(&argvars[0]));
+ if (tp != NULL) {
+ wp = (tp == curtab) ? firstwin : tp->tp_firstwin;
+ }
+ }
+ if (wp != NULL) {
+ tv_list_alloc_ret(rettv, kListLenMayKnow);
+ while (wp != NULL) {
+ tv_list_append_number(rettv->vval.v_list, wp->w_buffer->b_fnum);
+ wp = wp->w_next;
+ }
+ }
+}
+
+/*
+ * "tabpagenr()" function
+ */
+static void f_tabpagenr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int nr = 1;
+
+ if (argvars[0].v_type != VAR_UNKNOWN) {
+ const char *const arg = tv_get_string_chk(&argvars[0]);
+ nr = 0;
+ if (arg != NULL) {
+ if (strcmp(arg, "$") == 0) {
+ nr = tabpage_index(NULL) - 1;
+ } else if (strcmp(arg, "#") == 0) {
+ nr = valid_tabpage(lastused_tabpage)
+ ? tabpage_index(lastused_tabpage)
+ : nr;
+ } else {
+ EMSG2(_(e_invexpr2), arg);
+ }
+ }
+ } else {
+ nr = tabpage_index(curtab);
+ }
+ rettv->vval.v_number = nr;
+}
+
+
+
+/*
+ * Common code for tabpagewinnr() and winnr().
+ */
+static int get_winnr(tabpage_T *tp, typval_T *argvar)
+{
+ win_T *twin;
+ int nr = 1;
+ win_T *wp;
+
+ twin = (tp == curtab) ? curwin : tp->tp_curwin;
+ if (argvar->v_type != VAR_UNKNOWN) {
+ bool invalid_arg = false;
+ const char *const arg = tv_get_string_chk(argvar);
+ if (arg == NULL) {
+ nr = 0; // Type error; errmsg already given.
+ } else if (strcmp(arg, "$") == 0) {
+ twin = (tp == curtab) ? lastwin : tp->tp_lastwin;
+ } else if (strcmp(arg, "#") == 0) {
+ twin = (tp == curtab) ? prevwin : tp->tp_prevwin;
+ if (twin == NULL) {
+ nr = 0;
+ }
+ } else {
+ // Extract the window count (if specified). e.g. winnr('3j')
+ char_u *endp;
+ long count = strtol((char *)arg, (char **)&endp, 10);
+ if (count <= 0) {
+ // if count is not specified, default to 1
+ count = 1;
+ }
+ if (endp != NULL && *endp != '\0') {
+ if (strequal((char *)endp, "j")) {
+ twin = win_vert_neighbor(tp, twin, false, count);
+ } else if (strequal((char *)endp, "k")) {
+ twin = win_vert_neighbor(tp, twin, true, count);
+ } else if (strequal((char *)endp, "h")) {
+ twin = win_horz_neighbor(tp, twin, true, count);
+ } else if (strequal((char *)endp, "l")) {
+ twin = win_horz_neighbor(tp, twin, false, count);
+ } else {
+ invalid_arg = true;
+ }
+ } else {
+ invalid_arg = true;
+ }
+ }
+
+ if (invalid_arg) {
+ EMSG2(_(e_invexpr2), arg);
+ nr = 0;
+ }
+ }
+
+ if (nr > 0)
+ for (wp = (tp == curtab) ? firstwin : tp->tp_firstwin;
+ wp != twin; wp = wp->w_next) {
+ if (wp == NULL) {
+ // didn't find it in this tabpage
+ nr = 0;
+ break;
+ }
+ ++nr;
+ }
+ return nr;
+}
+
+/*
+ * "tabpagewinnr()" function
+ */
+static void f_tabpagewinnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int nr = 1;
+ tabpage_T *const tp = find_tabpage((int)tv_get_number(&argvars[0]));
+ if (tp == NULL) {
+ nr = 0;
+ } else {
+ nr = get_winnr(tp, &argvars[1]);
+ }
+ rettv->vval.v_number = nr;
+}
+
+/*
+ * "tagfiles()" function
+ */
+static void f_tagfiles(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char *fname;
+ tagname_T tn;
+
+ tv_list_alloc_ret(rettv, kListLenUnknown);
+ fname = xmalloc(MAXPATHL);
+
+ bool first = true;
+ while (get_tagfname(&tn, first, (char_u *)fname) == OK) {
+ tv_list_append_string(rettv->vval.v_list, fname, -1);
+ first = false;
+ }
+
+ tagname_free(&tn);
+ xfree(fname);
+}
+
+/*
+ * "taglist()" function
+ */
+static void f_taglist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *const tag_pattern = tv_get_string(&argvars[0]);
+
+ rettv->vval.v_number = false;
+ if (*tag_pattern == NUL) {
+ return;
+ }
+
+ const char *fname = NULL;
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ fname = tv_get_string(&argvars[1]);
+ }
+ (void)get_tags(tv_list_alloc_ret(rettv, kListLenUnknown),
+ (char_u *)tag_pattern, (char_u *)fname);
+}
+
+/*
+ * "tempname()" function
+ */
+static void f_tempname(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = vim_tempname();
+}
+
+// "termopen(cmd[, cwd])" function
+static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (check_restricted() || check_secure()) {
+ return;
+ }
+
+ if (curbuf->b_changed) {
+ EMSG(_("Can only call this function in an unmodified buffer"));
+ return;
+ }
+
+ const char *cmd;
+ bool executable = true;
+ char **argv = tv_to_argv(&argvars[0], &cmd, &executable);
+ if (!argv) {
+ rettv->vval.v_number = executable ? 0 : -1;
+ return; // Did error message in tv_to_argv.
+ }
+
+ if (argvars[1].v_type != VAR_DICT && argvars[1].v_type != VAR_UNKNOWN) {
+ // Wrong argument type
+ EMSG2(_(e_invarg2), "expected dictionary");
+ shell_free_argv(argv);
+ return;
+ }
+
+ CallbackReader on_stdout = CALLBACK_READER_INIT,
+ on_stderr = CALLBACK_READER_INIT;
+ Callback on_exit = CALLBACK_NONE;
+ dict_T *job_opts = NULL;
+ const char *cwd = ".";
+ if (argvars[1].v_type == VAR_DICT) {
+ job_opts = argvars[1].vval.v_dict;
+
+ const char *const new_cwd = tv_dict_get_string(job_opts, "cwd", false);
+ if (new_cwd && *new_cwd != NUL) {
+ cwd = new_cwd;
+ // The new cwd must be a directory.
+ if (!os_isdir_executable((const char *)cwd)) {
+ EMSG2(_(e_invarg2), "expected valid directory");
+ shell_free_argv(argv);
+ return;
+ }
+ }
+
+ if (!common_job_callbacks(job_opts, &on_stdout, &on_stderr, &on_exit)) {
+ shell_free_argv(argv);
+ return;
+ }
+ }
+
+ uint16_t term_width = MAX(0, curwin->w_width_inner - win_col_off(curwin));
+ Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit,
+ true, false, false, cwd,
+ term_width, curwin->w_height_inner,
+ xstrdup("xterm-256color"), NULL,
+ &rettv->vval.v_number);
+ if (rettv->vval.v_number <= 0) {
+ return;
+ }
+
+ int pid = chan->stream.pty.process.pid;
+
+ // "./…" => "/home/foo/…"
+ vim_FullName(cwd, (char *)NameBuff, sizeof(NameBuff), false);
+ // "/home/foo/…" => "~/…"
+ size_t len = home_replace(NULL, NameBuff, IObuff, sizeof(IObuff), true);
+ // Trim slash.
+ if (IObuff[len - 1] == '\\' || IObuff[len - 1] == '/') {
+ IObuff[len - 1] = '\0';
+ }
+
+ // Terminal URI: "term://$CWD//$PID:$CMD"
+ snprintf((char *)NameBuff, sizeof(NameBuff), "term://%s//%d:%s",
+ (char *)IObuff, pid, cmd);
+ // at this point the buffer has no terminal instance associated yet, so unset
+ // the 'swapfile' option to ensure no swap file will be created
+ curbuf->b_p_swf = false;
+ (void)setfname(curbuf, NameBuff, NULL, true);
+ // Save the job id and pid in b:terminal_job_{id,pid}
+ Error err = ERROR_INIT;
+ // deprecated: use 'channel' buffer option
+ dict_set_var(curbuf->b_vars, cstr_as_string("terminal_job_id"),
+ INTEGER_OBJ(chan->id), false, false, &err);
+ api_clear_error(&err);
+ dict_set_var(curbuf->b_vars, cstr_as_string("terminal_job_pid"),
+ INTEGER_OBJ(pid), false, false, &err);
+ api_clear_error(&err);
+
+ channel_terminal_open(chan);
+ channel_create_event(chan, NULL);
+}
+
+// "test_garbagecollect_now()" function
+static void f_test_garbagecollect_now(typval_T *argvars,
+ typval_T *rettv, FunPtr fptr)
+{
+ // This is dangerous, any Lists and Dicts used internally may be freed
+ // while still in use.
+ garbage_collect(true);
+}
+
+// "test_write_list_log()" function
+static void f_test_write_list_log(typval_T *const argvars,
+ typval_T *const rettv,
+ FunPtr fptr)
+{
+ const char *const fname = tv_get_string_chk(&argvars[0]);
+ if (fname == NULL) {
+ return;
+ }
+ list_write_log(fname);
+}
+
+/// "timer_info([timer])" function
+static void f_timer_info(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (argvars[0].v_type != VAR_UNKNOWN) {
+ if (argvars[0].v_type != VAR_NUMBER) {
+ EMSG(_(e_number_exp));
+ return;
+ }
+ tv_list_alloc_ret(rettv, 1);
+ timer_T *timer = find_timer_by_nr(tv_get_number(&argvars[0]));
+ if (timer != NULL && !timer->stopped) {
+ add_timer_info(rettv, timer);
+ }
+ } else {
+ add_timer_info_all(rettv);
+ }
+}
+
+/// "timer_pause(timer, paused)" function
+static void f_timer_pause(typval_T *argvars, typval_T *unused, FunPtr fptr)
+{
+ if (argvars[0].v_type != VAR_NUMBER) {
+ EMSG(_(e_number_exp));
+ return;
+ }
+ int paused = (bool)tv_get_number(&argvars[1]);
+ timer_T *timer = find_timer_by_nr(tv_get_number(&argvars[0]));
+ if (timer != NULL) {
+ if (!timer->paused && paused) {
+ time_watcher_stop(&timer->tw);
+ } else if (timer->paused && !paused) {
+ time_watcher_start(&timer->tw, timer_due_cb, timer->timeout,
+ timer->timeout);
+ }
+ timer->paused = paused;
+ }
+}
+
+/// "timer_start(timeout, callback, opts)" function
+static void f_timer_start(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int repeat = 1;
+ dict_T *dict;
+
+ rettv->vval.v_number = -1;
+
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ if (argvars[2].v_type != VAR_DICT
+ || (dict = argvars[2].vval.v_dict) == NULL) {
+ EMSG2(_(e_invarg2), tv_get_string(&argvars[2]));
+ return;
+ }
+ dictitem_T *const di = tv_dict_find(dict, S_LEN("repeat"));
+ if (di != NULL) {
+ repeat = tv_get_number(&di->di_tv);
+ if (repeat == 0) {
+ repeat = 1;
+ }
+ }
+ }
+
+ Callback callback;
+ if (!callback_from_typval(&callback, &argvars[1])) {
+ return;
+ }
+ rettv->vval.v_number =
+ timer_start(tv_get_number(&argvars[0]), repeat, &callback);
+}
+
+
+// "timer_stop(timerid)" function
+static void f_timer_stop(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (argvars[0].v_type != VAR_NUMBER) {
+ EMSG(_(e_number_exp));
+ return;
+ }
+
+ timer_T *timer = find_timer_by_nr(tv_get_number(&argvars[0]));
+ if (timer == NULL) {
+ return;
+ }
+
+ timer_stop(timer);
+}
+
+static void f_timer_stopall(typval_T *argvars, typval_T *unused, FunPtr fptr)
+{
+ timer_stop_all();
+}
+
+/*
+ * "tolower(string)" function
+ */
+static void f_tolower(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = (char_u *)strcase_save(tv_get_string(&argvars[0]),
+ false);
+}
+
+/*
+ * "toupper(string)" function
+ */
+static void f_toupper(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = (char_u *)strcase_save(tv_get_string(&argvars[0]),
+ true);
+}
+
+/*
+ * "tr(string, fromstr, tostr)" function
+ */
+static void f_tr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char buf[NUMBUFLEN];
+ char buf2[NUMBUFLEN];
+
+ const char *in_str = tv_get_string(&argvars[0]);
+ const char *fromstr = tv_get_string_buf_chk(&argvars[1], buf);
+ const char *tostr = tv_get_string_buf_chk(&argvars[2], buf2);
+
+ // Default return value: empty string.
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+ if (fromstr == NULL || tostr == NULL) {
+ return; // Type error; errmsg already given.
+ }
+ garray_T ga;
+ ga_init(&ga, (int)sizeof(char), 80);
+
+ if (!has_mbyte) {
+ // Not multi-byte: fromstr and tostr must be the same length.
+ if (strlen(fromstr) != strlen(tostr)) {
+ goto error;
+ }
+ }
+
+ // fromstr and tostr have to contain the same number of chars.
+ bool first = true;
+ while (*in_str != NUL) {
+ if (has_mbyte) {
+ const char *cpstr = in_str;
+ const int inlen = (*mb_ptr2len)((const char_u *)in_str);
+ int cplen = inlen;
+ int idx = 0;
+ int fromlen;
+ for (const char *p = fromstr; *p != NUL; p += fromlen) {
+ fromlen = (*mb_ptr2len)((const char_u *)p);
+ if (fromlen == inlen && STRNCMP(in_str, p, inlen) == 0) {
+ int tolen;
+ for (p = tostr; *p != NUL; p += tolen) {
+ tolen = (*mb_ptr2len)((const char_u *)p);
+ if (idx-- == 0) {
+ cplen = tolen;
+ cpstr = (char *)p;
+ break;
+ }
+ }
+ if (*p == NUL) { // tostr is shorter than fromstr.
+ goto error;
+ }
+ break;
+ }
+ idx++;
+ }
+
+ if (first && cpstr == in_str) {
+ // Check that fromstr and tostr have the same number of
+ // (multi-byte) characters. Done only once when a character
+ // of in_str doesn't appear in fromstr.
+ first = false;
+ int tolen;
+ for (const char *p = tostr; *p != NUL; p += tolen) {
+ tolen = (*mb_ptr2len)((const char_u *)p);
+ idx--;
+ }
+ if (idx != 0) {
+ goto error;
+ }
+ }
+
+ ga_grow(&ga, cplen);
+ memmove((char *)ga.ga_data + ga.ga_len, cpstr, (size_t)cplen);
+ ga.ga_len += cplen;
+
+ in_str += inlen;
+ } else {
+ // When not using multi-byte chars we can do it faster.
+ const char *const p = strchr(fromstr, *in_str);
+ if (p != NULL) {
+ ga_append(&ga, tostr[p - fromstr]);
+ } else {
+ ga_append(&ga, *in_str);
+ }
+ in_str++;
+ }
+ }
+
+ // add a terminating NUL
+ ga_append(&ga, NUL);
+
+ rettv->vval.v_string = ga.ga_data;
+ return;
+error:
+ EMSG2(_(e_invarg2), fromstr);
+ ga_clear(&ga);
+ return;
+}
+
+// "trim({expr})" function
+static void f_trim(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char buf1[NUMBUFLEN];
+ char buf2[NUMBUFLEN];
+ const char_u *head = (const char_u *)tv_get_string_buf_chk(&argvars[0], buf1);
+ const char_u *mask = NULL;
+ const char_u *tail;
+ const char_u *prev;
+ const char_u *p;
+ int c1;
+
+ rettv->v_type = VAR_STRING;
+ if (head == NULL) {
+ rettv->vval.v_string = NULL;
+ return;
+ }
+
+ if (argvars[1].v_type == VAR_STRING) {
+ mask = (const char_u *)tv_get_string_buf_chk(&argvars[1], buf2);
+ }
+
+ while (*head != NUL) {
+ c1 = PTR2CHAR(head);
+ if (mask == NULL) {
+ if (c1 > ' ' && c1 != 0xa0) {
+ break;
+ }
+ } else {
+ for (p = mask; *p != NUL; MB_PTR_ADV(p)) {
+ if (c1 == PTR2CHAR(p)) {
+ break;
+ }
+ }
+ if (*p == NUL) {
+ break;
+ }
+ }
+ MB_PTR_ADV(head);
+ }
+
+ for (tail = head + STRLEN(head); tail > head; tail = prev) {
+ prev = tail;
+ MB_PTR_BACK(head, prev);
+ c1 = PTR2CHAR(prev);
+ if (mask == NULL) {
+ if (c1 > ' ' && c1 != 0xa0) {
+ break;
+ }
+ } else {
+ for (p = mask; *p != NUL; MB_PTR_ADV(p)) {
+ if (c1 == PTR2CHAR(p)) {
+ break;
+ }
+ }
+ if (*p == NUL) {
+ break;
+ }
+ }
+ }
+ rettv->vval.v_string = vim_strnsave(head, (int)(tail - head));
+}
+
+/*
+ * "type(expr)" function
+ */
+static void f_type(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int n = -1;
+
+ switch (argvars[0].v_type) {
+ case VAR_NUMBER: n = VAR_TYPE_NUMBER; break;
+ case VAR_STRING: n = VAR_TYPE_STRING; break;
+ case VAR_PARTIAL:
+ case VAR_FUNC: n = VAR_TYPE_FUNC; break;
+ case VAR_LIST: n = VAR_TYPE_LIST; break;
+ case VAR_DICT: n = VAR_TYPE_DICT; break;
+ case VAR_FLOAT: n = VAR_TYPE_FLOAT; break;
+ case VAR_SPECIAL: {
+ switch (argvars[0].vval.v_special) {
+ case kSpecialVarTrue:
+ case kSpecialVarFalse: {
+ n = VAR_TYPE_BOOL;
+ break;
+ }
+ case kSpecialVarNull: {
+ n = 7;
+ break;
+ }
+ }
+ break;
+ }
+ case VAR_UNKNOWN: {
+ internal_error("f_type(UNKNOWN)");
+ break;
+ }
+ }
+ rettv->vval.v_number = n;
+}
+
+/*
+ * "undofile(name)" function
+ */
+static void f_undofile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_STRING;
+ const char *const fname = tv_get_string(&argvars[0]);
+
+ if (*fname == NUL) {
+ // If there is no file name there will be no undo file.
+ rettv->vval.v_string = NULL;
+ } else {
+ char *ffname = FullName_save(fname, true);
+
+ if (ffname != NULL) {
+ rettv->vval.v_string = (char_u *)u_get_undo_file_name(ffname, false);
+ }
+ xfree(ffname);
+ }
+}
+
+/*
+ * "undotree()" function
+ */
+static void f_undotree(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ tv_dict_alloc_ret(rettv);
+
+ dict_T *dict = rettv->vval.v_dict;
+
+ tv_dict_add_nr(dict, S_LEN("synced"), (varnumber_T)curbuf->b_u_synced);
+ tv_dict_add_nr(dict, S_LEN("seq_last"), (varnumber_T)curbuf->b_u_seq_last);
+ tv_dict_add_nr(dict, S_LEN("save_last"),
+ (varnumber_T)curbuf->b_u_save_nr_last);
+ tv_dict_add_nr(dict, S_LEN("seq_cur"), (varnumber_T)curbuf->b_u_seq_cur);
+ tv_dict_add_nr(dict, S_LEN("time_cur"), (varnumber_T)curbuf->b_u_time_cur);
+ tv_dict_add_nr(dict, S_LEN("save_cur"), (varnumber_T)curbuf->b_u_save_nr_cur);
+
+ tv_dict_add_list(dict, S_LEN("entries"), u_eval_tree(curbuf->b_u_oldhead));
+}
+
+/*
+ * "values(dict)" function
+ */
+static void f_values(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ dict_list(argvars, rettv, 1);
+}
+
+/*
+ * "virtcol(string)" function
+ */
+static void f_virtcol(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ colnr_T vcol = 0;
+ pos_T *fp;
+ int fnum = curbuf->b_fnum;
+
+ fp = var2fpos(&argvars[0], FALSE, &fnum);
+ if (fp != NULL && fp->lnum <= curbuf->b_ml.ml_line_count
+ && fnum == curbuf->b_fnum) {
+ // Limit the column to a valid value, getvvcol() doesn't check.
+ if (fp->col < 0) {
+ fp->col = 0;
+ } else {
+ const size_t len = STRLEN(ml_get(fp->lnum));
+ if (fp->col > (colnr_T)len) {
+ fp->col = (colnr_T)len;
+ }
+ }
+ getvvcol(curwin, fp, NULL, NULL, &vcol);
+ ++vcol;
+ }
+
+ rettv->vval.v_number = vcol;
+}
+
+/*
+ * "visualmode()" function
+ */
+static void f_visualmode(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ char_u str[2];
+
+ rettv->v_type = VAR_STRING;
+ str[0] = curbuf->b_visual_mode_eval;
+ str[1] = NUL;
+ rettv->vval.v_string = vim_strsave(str);
+
+ // A non-zero number or non-empty string argument: reset mode.
+ if (non_zero_arg(&argvars[0])) {
+ curbuf->b_visual_mode_eval = NUL;
+ }
+}
+
+/*
+ * "wildmenumode()" function
+ */
+static void f_wildmenumode(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (wild_menu_showing || ((State & CMDLINE) && pum_visible())) {
+ rettv->vval.v_number = 1;
+ }
+}
+
+/// "win_findbuf()" function
+static void f_win_findbuf(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ tv_list_alloc_ret(rettv, kListLenMayKnow);
+ win_findbuf(argvars, rettv->vval.v_list);
+}
+
+/// "win_getid()" function
+static void f_win_getid(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = win_getid(argvars);
+}
+
+/// "win_gotoid()" function
+static void f_win_gotoid(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = win_gotoid(argvars);
+}
+
+/// "win_id2tabwin()" function
+static void f_win_id2tabwin(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ win_id2tabwin(argvars, rettv);
+}
+
+/// "win_id2win()" function
+static void f_win_id2win(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = win_id2win(argvars);
+}
+
+/// "winbufnr(nr)" function
+static void f_winbufnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ win_T *wp = find_win_by_nr_or_id(&argvars[0]);
+ if (wp == NULL) {
+ rettv->vval.v_number = -1;
+ } else {
+ rettv->vval.v_number = wp->w_buffer->b_fnum;
+ }
+}
+
+/*
+ * "wincol()" function
+ */
+static void f_wincol(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ validate_cursor();
+ rettv->vval.v_number = curwin->w_wcol + 1;
+}
+
+/// "winheight(nr)" function
+static void f_winheight(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ win_T *wp = find_win_by_nr_or_id(&argvars[0]);
+ if (wp == NULL) {
+ rettv->vval.v_number = -1;
+ } else {
+ rettv->vval.v_number = wp->w_height;
+ }
+}
+
+// "winlayout()" function
+static void f_winlayout(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ tabpage_T *tp;
+
+ tv_list_alloc_ret(rettv, 2);
+
+ if (argvars[0].v_type == VAR_UNKNOWN) {
+ tp = curtab;
+ } else {
+ tp = find_tabpage((int)tv_get_number(&argvars[0]));
+ if (tp == NULL) {
+ return;
+ }
+ }
+
+ get_framelayout(tp->tp_topframe, rettv->vval.v_list, true);
+}
+
+/*
+ * "winline()" function
+ */
+static void f_winline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ validate_cursor();
+ rettv->vval.v_number = curwin->w_wrow + 1;
+}
+
+/*
+ * "winnr()" function
+ */
+static void f_winnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int nr = 1;
+
+ nr = get_winnr(curtab, &argvars[0]);
+ rettv->vval.v_number = nr;
+}
+
+/*
+ * "winrestcmd()" function
+ */
+static void f_winrestcmd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int winnr = 1;
+ garray_T ga;
+ char_u buf[50];
+
+ ga_init(&ga, (int)sizeof(char), 70);
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ sprintf((char *)buf, "%dresize %d|", winnr, wp->w_height);
+ ga_concat(&ga, buf);
+ sprintf((char *)buf, "vert %dresize %d|", winnr, wp->w_width);
+ ga_concat(&ga, buf);
+ ++winnr;
+ }
+ ga_append(&ga, NUL);
+
+ rettv->vval.v_string = ga.ga_data;
+ rettv->v_type = VAR_STRING;
+}
+
+/*
+ * "winrestview()" function
+ */
+static void f_winrestview(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ dict_T *dict;
+
+ if (argvars[0].v_type != VAR_DICT
+ || (dict = argvars[0].vval.v_dict) == NULL) {
+ EMSG(_(e_invarg));
+ } else {
+ dictitem_T *di;
+ if ((di = tv_dict_find(dict, S_LEN("lnum"))) != NULL) {
+ curwin->w_cursor.lnum = tv_get_number(&di->di_tv);
+ }
+ if ((di = tv_dict_find(dict, S_LEN("col"))) != NULL) {
+ curwin->w_cursor.col = tv_get_number(&di->di_tv);
+ }
+ if ((di = tv_dict_find(dict, S_LEN("coladd"))) != NULL) {
+ curwin->w_cursor.coladd = tv_get_number(&di->di_tv);
+ }
+ if ((di = tv_dict_find(dict, S_LEN("curswant"))) != NULL) {
+ curwin->w_curswant = tv_get_number(&di->di_tv);
+ curwin->w_set_curswant = false;
+ }
+ if ((di = tv_dict_find(dict, S_LEN("topline"))) != NULL) {
+ set_topline(curwin, tv_get_number(&di->di_tv));
+ }
+ if ((di = tv_dict_find(dict, S_LEN("topfill"))) != NULL) {
+ curwin->w_topfill = tv_get_number(&di->di_tv);
+ }
+ if ((di = tv_dict_find(dict, S_LEN("leftcol"))) != NULL) {
+ curwin->w_leftcol = tv_get_number(&di->di_tv);
+ }
+ if ((di = tv_dict_find(dict, S_LEN("skipcol"))) != NULL) {
+ curwin->w_skipcol = tv_get_number(&di->di_tv);
+ }
+
+ check_cursor();
+ win_new_height(curwin, curwin->w_height);
+ win_new_width(curwin, curwin->w_width);
+ changed_window_setting();
+
+ if (curwin->w_topline <= 0)
+ curwin->w_topline = 1;
+ if (curwin->w_topline > curbuf->b_ml.ml_line_count)
+ curwin->w_topline = curbuf->b_ml.ml_line_count;
+ check_topfill(curwin, true);
+ }
+}
+
+/*
+ * "winsaveview()" function
+ */
+static void f_winsaveview(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ dict_T *dict;
+
+ tv_dict_alloc_ret(rettv);
+ dict = rettv->vval.v_dict;
+
+ tv_dict_add_nr(dict, S_LEN("lnum"), (varnumber_T)curwin->w_cursor.lnum);
+ tv_dict_add_nr(dict, S_LEN("col"), (varnumber_T)curwin->w_cursor.col);
+ tv_dict_add_nr(dict, S_LEN("coladd"), (varnumber_T)curwin->w_cursor.coladd);
+ update_curswant();
+ tv_dict_add_nr(dict, S_LEN("curswant"), (varnumber_T)curwin->w_curswant);
+
+ tv_dict_add_nr(dict, S_LEN("topline"), (varnumber_T)curwin->w_topline);
+ tv_dict_add_nr(dict, S_LEN("topfill"), (varnumber_T)curwin->w_topfill);
+ tv_dict_add_nr(dict, S_LEN("leftcol"), (varnumber_T)curwin->w_leftcol);
+ tv_dict_add_nr(dict, S_LEN("skipcol"), (varnumber_T)curwin->w_skipcol);
+}
+
+/// "winwidth(nr)" function
+static void f_winwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ win_T *wp = find_win_by_nr_or_id(&argvars[0]);
+ if (wp == NULL) {
+ rettv->vval.v_number = -1;
+ } else {
+ rettv->vval.v_number = wp->w_width;
+ }
+}
+
+/// "wordcount()" function
+static void f_wordcount(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ tv_dict_alloc_ret(rettv);
+ cursor_pos_info(rettv->vval.v_dict);
+}
+
+/// "writefile()" function
+static void f_writefile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = -1;
+
+ if (check_secure()) {
+ return;
+ }
+
+ if (argvars[0].v_type != VAR_LIST) {
+ EMSG2(_(e_listarg), "writefile()");
+ return;
+ }
+ const list_T *const list = argvars[0].vval.v_list;
+ TV_LIST_ITER_CONST(list, li, {
+ if (!tv_check_str_or_nr(TV_LIST_ITEM_TV(li))) {
+ return;
+ }
+ });
+
+ bool binary = false;
+ bool append = false;
+ bool do_fsync = !!p_fs;
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ const char *const flags = tv_get_string_chk(&argvars[2]);
+ if (flags == NULL) {
+ return;
+ }
+ for (const char *p = flags; *p; p++) {
+ switch (*p) {
+ case 'b': { binary = true; break; }
+ case 'a': { append = true; break; }
+ case 's': { do_fsync = true; break; }
+ case 'S': { do_fsync = false; break; }
+ default: {
+ // Using %s, p and not %c, *p to preserve multibyte characters
+ emsgf(_("E5060: Unknown flag: %s"), p);
+ return;
+ }
+ }
+ }
+ }
+
+ char buf[NUMBUFLEN];
+ const char *const fname = tv_get_string_buf_chk(&argvars[1], buf);
+ if (fname == NULL) {
+ return;
+ }
+ FileDescriptor fp;
+ int error;
+ if (*fname == NUL) {
+ EMSG(_("E482: Can't open file with an empty name"));
+ } else if ((error = file_open(&fp, fname,
+ ((append ? kFileAppend : kFileTruncate)
+ | kFileCreate), 0666)) != 0) {
+ emsgf(_("E482: Can't open file %s for writing: %s"),
+ fname, os_strerror(error));
+ } else {
+ if (write_list(&fp, list, binary)) {
+ rettv->vval.v_number = 0;
+ }
+ if ((error = file_close(&fp, do_fsync)) != 0) {
+ emsgf(_("E80: Error when closing file %s: %s"),
+ fname, os_strerror(error));
+ }
+ }
+}
+/*
+ * "xor(expr, expr)" function
+ */
+static void f_xor(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL)
+ ^ tv_get_number_chk(&argvars[1], NULL);
+}
diff --git a/src/nvim/eval/funcs.h b/src/nvim/eval/funcs.h
new file mode 100644
index 0000000000..a343290734
--- /dev/null
+++ b/src/nvim/eval/funcs.h
@@ -0,0 +1,24 @@
+#ifndef NVIM_EVAL_FUNCS_H
+#define NVIM_EVAL_FUNCS_H
+
+#include "nvim/buffer_defs.h"
+#include "nvim/eval/typval.h"
+
+typedef void (*FunPtr)(void);
+
+/// Prototype of C function that implements VimL function
+typedef void (*VimLFunc)(typval_T *args, typval_T *rvar, FunPtr data);
+
+/// Structure holding VimL function definition
+typedef struct fst {
+ char *name; ///< Name of the function.
+ uint8_t min_argc; ///< Minimal number of arguments.
+ uint8_t max_argc; ///< Maximal number of arguments.
+ VimLFunc func; ///< Function implementation.
+ FunPtr data; ///< Userdata for function implementation.
+} VimLFuncDef;
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "eval/funcs.h.generated.h"
+#endif
+#endif // NVIM_EVAL_FUNCS_H
diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c
index da97eccc65..35130f6f40 100644
--- a/src/nvim/eval/typval.c
+++ b/src/nvim/eval/typval.c
@@ -15,6 +15,7 @@
#include "nvim/eval/encode.h"
#include "nvim/eval/typval_encode.h"
#include "nvim/eval.h"
+#include "nvim/eval/userfunc.h"
#include "nvim/types.h"
#include "nvim/assert.h"
#include "nvim/memory.h"
@@ -1428,6 +1429,23 @@ dictitem_T *tv_dict_find(const dict_T *const d, const char *const key,
return TV_DICT_HI2DI(hi);
}
+/// Get a typval item from a dictionary and copy it into "rettv".
+///
+/// @param[in] d Dictionary to check.
+/// @param[in] key Dictionary key.
+/// @param[in] rettv Return value.
+/// @return OK in case of success or FAIL if nothing was found.
+int tv_dict_get_tv(dict_T *d, const char *const key, typval_T *rettv)
+{
+ dictitem_T *const di = tv_dict_find(d, key, -1);
+ if (di == NULL) {
+ return FAIL;
+ }
+
+ tv_copy(&di->di_tv, rettv);
+ return OK;
+}
+
/// Get a number item from a dictionary
///
/// Returns 0 if the entry does not exist.
@@ -1587,6 +1605,26 @@ int tv_dict_add_list(dict_T *const d, const char *const key,
return OK;
}
+/// Add a typval entry to dictionary.
+///
+/// @param[out] d Dictionary to add entry to.
+/// @param[in] key Key to add.
+/// @param[in] key_len Key length.
+///
+/// @return FAIL if out of memory or key already exists.
+int tv_dict_add_tv(dict_T *d, const char *key, const size_t key_len,
+ typval_T *tv)
+{
+ dictitem_T *const item = tv_dict_item_alloc_len(key, key_len);
+
+ tv_copy(tv, &item->di_tv);
+ if (tv_dict_add(d, item) == FAIL) {
+ tv_dict_item_free(item);
+ return FAIL;
+ }
+ return OK;
+}
+
/// Add a dictionary entry to dictionary
///
/// @param[out] d Dictionary to add entry to.
@@ -1633,6 +1671,28 @@ int tv_dict_add_nr(dict_T *const d, const char *const key,
return OK;
}
+/// Add a floating point number entry to dictionary
+///
+/// @param[out] d Dictionary to add entry to.
+/// @param[in] key Key to add.
+/// @param[in] key_len Key length.
+/// @param[in] nr Floating point number to add.
+///
+/// @return OK in case of success, FAIL when key already exists.
+int tv_dict_add_float(dict_T *const d, const char *const key,
+ const size_t key_len, const float_T nr)
+{
+ dictitem_T *const item = tv_dict_item_alloc_len(key, key_len);
+
+ item->di_tv.v_type = VAR_FLOAT;
+ item->di_tv.vval.v_float = nr;
+ if (tv_dict_add(d, item) == FAIL) {
+ tv_dict_item_free(item);
+ return FAIL;
+ }
+ return OK;
+}
+
/// Add a special entry to dictionary
///
/// @param[out] d Dictionary to add entry to.
diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h
index 008453b87f..4390db1b71 100644
--- a/src/nvim/eval/typval.h
+++ b/src/nvim/eval/typval.h
@@ -33,7 +33,7 @@ typedef double float_T;
enum { DO_NOT_FREE_CNT = (INT_MAX / 2) };
/// Additional values for tv_list_alloc() len argument
-enum {
+enum ListLenSpecials {
/// List length is not known in advance
///
/// To be used when there is neither a way to know how many elements will be
@@ -49,7 +49,7 @@ enum {
///
/// To be used when it looks impractical to determine list length.
kListLenMayKnow = -3,
-} ListLenSpecials;
+};
/// Maximal possible value of varnumber_T variable
#define VARNUMBER_MAX INT64_MAX
@@ -258,9 +258,39 @@ typedef struct {
linenr_T sc_lnum; // line number
} sctx_T;
+/// Maximum number of function arguments
+#define MAX_FUNC_ARGS 20
+/// Short variable name length
+#define VAR_SHORT_LEN 20
+/// Number of fixed variables used for arguments
+#define FIXVAR_CNT 12
+
// Structure to hold info for a function that is currently being executed.
typedef struct funccall_S funccall_T;
+struct funccall_S {
+ ufunc_T *func; ///< Function being called.
+ int linenr; ///< Next line to be executed.
+ int returned; ///< ":return" used.
+ /// Fixed variables for arguments.
+ TV_DICTITEM_STRUCT(VAR_SHORT_LEN + 1) fixvar[FIXVAR_CNT];
+ dict_T l_vars; ///< l: local function variables.
+ ScopeDictDictItem l_vars_var; ///< Variable for l: scope.
+ dict_T l_avars; ///< a: argument variables.
+ ScopeDictDictItem l_avars_var; ///< Variable for a: scope.
+ list_T l_varlist; ///< List for a:000.
+ listitem_T l_listitems[MAX_FUNC_ARGS]; ///< List items for a:000.
+ typval_T *rettv; ///< Return value.
+ linenr_T breakpoint; ///< Next line with breakpoint or zero.
+ int dbg_tick; ///< Debug_tick when breakpoint was set.
+ int level; ///< Top nesting level of executed function.
+ proftime_T prof_child; ///< Time spent in a child.
+ funccall_T *caller; ///< Calling function or NULL.
+ int fc_refcount; ///< Number of user functions that reference this funccall.
+ int fc_copyID; ///< CopyID used for garbage collection.
+ garray_T fc_funcs; ///< List of ufunc_T* which keep a reference to "func".
+};
+
/// Structure to hold info for a user function.
struct ufunc {
int uf_varargs; ///< variable nr of arguments
@@ -293,9 +323,6 @@ struct ufunc {
///< (<SNR> is K_SPECIAL KS_EXTRA KE_SNR)
};
-/// Maximum number of function arguments
-#define MAX_FUNC_ARGS 20
-
struct partial_S {
int pt_refcount; ///< Reference count.
char_u *pt_name; ///< Function name; when NULL use pt_func->name.
diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h
index 289c3ee99c..af21a6fbe3 100644
--- a/src/nvim/eval/typval_encode.c.h
+++ b/src/nvim/eval/typval_encode.c.h
@@ -173,7 +173,7 @@
/// @def TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK
/// @brief Macros used to check special dictionary key
///
-/// @param label Label for goto in case check was not successfull.
+/// @param label Label for goto in case check was not successful.
/// @param key typval_T key to check.
/// @def TYPVAL_ENCODE_CONV_DICT_AFTER_KEY
diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c
new file mode 100644
index 0000000000..ae8557a8bc
--- /dev/null
+++ b/src/nvim/eval/userfunc.c
@@ -0,0 +1,3385 @@
+// 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
+
+// User defined function support
+
+#include "nvim/ascii.h"
+#include "nvim/charset.h"
+#include "nvim/edit.h"
+#include "nvim/eval.h"
+#include "nvim/eval/encode.h"
+#include "nvim/eval/userfunc.h"
+#include "nvim/ex_cmds2.h"
+#include "nvim/ex_docmd.h"
+#include "nvim/ex_getln.h"
+#include "nvim/ex_getln.h"
+#include "nvim/fileio.h"
+#include "nvim/getchar.h"
+#include "nvim/globals.h"
+#include "nvim/lua/executor.h"
+#include "nvim/misc1.h"
+#include "nvim/os/input.h"
+#include "nvim/regexp.h"
+#include "nvim/search.h"
+#include "nvim/ui.h"
+#include "nvim/vim.h"
+
+// flags used in uf_flags
+#define FC_ABORT 0x01 // abort function on error
+#define FC_RANGE 0x02 // function accepts range
+#define FC_DICT 0x04 // Dict function, uses "self"
+#define FC_CLOSURE 0x08 // closure, uses outer scope variables
+#define FC_DELETED 0x10 // :delfunction used while uf_refcount > 0
+#define FC_REMOVED 0x20 // function redefined while uf_refcount > 0
+#define FC_SANDBOX 0x40 // function defined in the sandbox
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+#include "eval/userfunc.c.generated.h"
+#endif
+
+hashtab_T func_hashtab;
+
+// Used by get_func_tv()
+static garray_T funcargs = GA_EMPTY_INIT_VALUE;
+
+// pointer to funccal for currently active function
+funccall_T *current_funccal = NULL;
+
+// Pointer to list of previously used funccal, still around because some
+// item in it is still being used.
+funccall_T *previous_funccal = NULL;
+
+static char *e_funcexts = N_(
+ "E122: Function %s already exists, add ! to replace it");
+static char *e_funcdict = N_("E717: Dictionary entry already exists");
+static char *e_funcref = N_("E718: Funcref required");
+static char *e_nofunc = N_("E130: Unknown function: %s");
+
+void func_init(void)
+{
+ hash_init(&func_hashtab);
+}
+
+/// Get function arguments.
+static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs,
+ int *varargs, bool skip)
+{
+ bool mustend = false;
+ char_u *arg = *argp;
+ char_u *p = arg;
+ int c;
+ int i;
+
+ if (newargs != NULL) {
+ ga_init(newargs, (int)sizeof(char_u *), 3);
+ }
+
+ if (varargs != NULL) {
+ *varargs = false;
+ }
+
+ // Isolate the arguments: "arg1, arg2, ...)"
+ while (*p != endchar) {
+ if (p[0] == '.' && p[1] == '.' && p[2] == '.') {
+ if (varargs != NULL) {
+ *varargs = true;
+ }
+ p += 3;
+ mustend = true;
+ } else {
+ arg = p;
+ while (ASCII_ISALNUM(*p) || *p == '_') {
+ p++;
+ }
+ if (arg == p || isdigit(*arg)
+ || (p - arg == 9 && STRNCMP(arg, "firstline", 9) == 0)
+ || (p - arg == 8 && STRNCMP(arg, "lastline", 8) == 0)) {
+ if (!skip) {
+ EMSG2(_("E125: Illegal argument: %s"), arg);
+ }
+ break;
+ }
+ if (newargs != NULL) {
+ ga_grow(newargs, 1);
+ c = *p;
+ *p = NUL;
+ arg = vim_strsave(arg);
+
+ // Check for duplicate argument name.
+ for (i = 0; i < newargs->ga_len; i++) {
+ if (STRCMP(((char_u **)(newargs->ga_data))[i], arg) == 0) {
+ EMSG2(_("E853: Duplicate argument name: %s"), arg);
+ xfree(arg);
+ goto err_ret;
+ }
+ }
+ ((char_u **)(newargs->ga_data))[newargs->ga_len] = arg;
+ newargs->ga_len++;
+
+ *p = c;
+ }
+ if (*p == ',') {
+ p++;
+ } else {
+ mustend = true;
+ }
+ }
+ p = skipwhite(p);
+ if (mustend && *p != endchar) {
+ if (!skip) {
+ EMSG2(_(e_invarg2), *argp);
+ }
+ break;
+ }
+ }
+ if (*p != endchar) {
+ goto err_ret;
+ }
+ p++; // skip "endchar"
+
+ *argp = p;
+ return OK;
+
+err_ret:
+ if (newargs != NULL) {
+ ga_clear_strings(newargs);
+ }
+ return FAIL;
+}
+
+/// Register function "fp" as using "current_funccal" as its scope.
+static void register_closure(ufunc_T *fp)
+{
+ if (fp->uf_scoped == current_funccal) {
+ // no change
+ return;
+ }
+ funccal_unref(fp->uf_scoped, fp, false);
+ fp->uf_scoped = current_funccal;
+ current_funccal->fc_refcount++;
+ ga_grow(&current_funccal->fc_funcs, 1);
+ ((ufunc_T **)current_funccal->fc_funcs.ga_data)
+ [current_funccal->fc_funcs.ga_len++] = fp;
+}
+
+/// Parse a lambda expression and get a Funcref from "*arg".
+///
+/// @return OK or FAIL. Returns NOTDONE for dict or {expr}.
+int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate)
+{
+ garray_T newargs = GA_EMPTY_INIT_VALUE;
+ garray_T *pnewargs;
+ ufunc_T *fp = NULL;
+ int varargs;
+ int ret;
+ char_u *start = skipwhite(*arg + 1);
+ char_u *s, *e;
+ static int lambda_no = 0;
+ bool *old_eval_lavars = eval_lavars_used;
+ bool eval_lavars = false;
+
+ // First, check if this is a lambda expression. "->" must exists.
+ ret = get_function_args(&start, '-', NULL, NULL, true);
+ if (ret == FAIL || *start != '>') {
+ return NOTDONE;
+ }
+
+ // Parse the arguments again.
+ if (evaluate) {
+ pnewargs = &newargs;
+ } else {
+ pnewargs = NULL;
+ }
+ *arg = skipwhite(*arg + 1);
+ ret = get_function_args(arg, '-', pnewargs, &varargs, false);
+ if (ret == FAIL || **arg != '>') {
+ goto errret;
+ }
+
+ // Set up a flag for checking local variables and arguments.
+ if (evaluate) {
+ eval_lavars_used = &eval_lavars;
+ }
+
+ // Get the start and the end of the expression.
+ *arg = skipwhite(*arg + 1);
+ s = *arg;
+ ret = skip_expr(arg);
+ if (ret == FAIL) {
+ goto errret;
+ }
+ e = *arg;
+ *arg = skipwhite(*arg);
+ if (**arg != '}') {
+ goto errret;
+ }
+ (*arg)++;
+
+ if (evaluate) {
+ int len, flags = 0;
+ char_u *p;
+ char_u name[20];
+ partial_T *pt;
+ garray_T newlines;
+
+ lambda_no++;
+ snprintf((char *)name, sizeof(name), "<lambda>%d", lambda_no);
+
+ fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1);
+ pt = xcalloc(1, sizeof(partial_T));
+
+ ga_init(&newlines, (int)sizeof(char_u *), 1);
+ ga_grow(&newlines, 1);
+
+ // Add "return " before the expression.
+ len = 7 + e - s + 1;
+ p = (char_u *)xmalloc(len);
+ ((char_u **)(newlines.ga_data))[newlines.ga_len++] = p;
+ STRCPY(p, "return ");
+ STRLCPY(p + 7, s, e - s + 1);
+
+ fp->uf_refcount = 1;
+ STRCPY(fp->uf_name, name);
+ hash_add(&func_hashtab, UF2HIKEY(fp));
+ fp->uf_args = newargs;
+ fp->uf_lines = newlines;
+ if (current_funccal != NULL && eval_lavars) {
+ flags |= FC_CLOSURE;
+ register_closure(fp);
+ } else {
+ fp->uf_scoped = NULL;
+ }
+
+ if (prof_def_func()) {
+ func_do_profile(fp);
+ }
+ if (sandbox) {
+ flags |= FC_SANDBOX;
+ }
+ fp->uf_varargs = true;
+ fp->uf_flags = flags;
+ fp->uf_calls = 0;
+ fp->uf_script_ctx = current_sctx;
+ fp->uf_script_ctx.sc_lnum += sourcing_lnum - newlines.ga_len;
+
+ pt->pt_func = fp;
+ pt->pt_refcount = 1;
+ rettv->vval.v_partial = pt;
+ rettv->v_type = VAR_PARTIAL;
+ }
+
+ eval_lavars_used = old_eval_lavars;
+ return OK;
+
+errret:
+ ga_clear_strings(&newargs);
+ xfree(fp);
+ eval_lavars_used = old_eval_lavars;
+ return FAIL;
+}
+
+/// Return name of the function corresponding to `name`
+///
+/// If `name` points to variable that is either a function or partial then
+/// corresponding function name is returned. Otherwise it returns `name` itself.
+///
+/// @param[in] name Function name to check.
+/// @param[in,out] lenp Location where length of the returned name is stored.
+/// Must be set to the length of the `name` argument.
+/// @param[out] partialp Location where partial will be stored if found
+/// function appears to be a partial. May be NULL if this
+/// is not needed.
+/// @param[in] no_autoload If true, do not source autoload scripts if function
+/// was not found.
+///
+/// @return name of the function.
+char_u *deref_func_name(const char *name, int *lenp,
+ partial_T **const partialp, bool no_autoload)
+ FUNC_ATTR_NONNULL_ARG(1, 2)
+{
+ if (partialp != NULL) {
+ *partialp = NULL;
+ }
+
+ dictitem_T *const v = find_var(name, (size_t)(*lenp), NULL, no_autoload);
+ if (v != NULL && v->di_tv.v_type == VAR_FUNC) {
+ if (v->di_tv.vval.v_string == NULL) { // just in case
+ *lenp = 0;
+ return (char_u *)"";
+ }
+ *lenp = (int)STRLEN(v->di_tv.vval.v_string);
+ return v->di_tv.vval.v_string;
+ }
+
+ if (v != NULL && v->di_tv.v_type == VAR_PARTIAL) {
+ partial_T *const pt = v->di_tv.vval.v_partial;
+
+ if (pt == NULL) { // just in case
+ *lenp = 0;
+ return (char_u *)"";
+ }
+ if (partialp != NULL) {
+ *partialp = pt;
+ }
+ char_u *s = partial_name(pt);
+ *lenp = (int)STRLEN(s);
+ return s;
+ }
+
+ return (char_u *)name;
+}
+
+/// Give an error message with a function name. Handle <SNR> things.
+///
+/// @param ermsg must be passed without translation (use N_() instead of _()).
+/// @param name function name
+void emsg_funcname(char *ermsg, const char_u *name)
+{
+ char_u *p;
+
+ if (*name == K_SPECIAL) {
+ p = concat_str((char_u *)"<SNR>", name + 3);
+ } else {
+ p = (char_u *)name;
+ }
+
+ EMSG2(_(ermsg), p);
+
+ if (p != name) {
+ xfree(p);
+ }
+}
+
+/*
+ * Allocate a variable for the result of a function.
+ * Return OK or FAIL.
+ */
+int
+get_func_tv(
+ const char_u *name, // name of the function
+ int len, // length of "name"
+ typval_T *rettv,
+ char_u **arg, // argument, pointing to the '('
+ linenr_T firstline, // first line of range
+ linenr_T lastline, // last line of range
+ int *doesrange, // return: function handled range
+ int evaluate,
+ partial_T *partial, // for extra arguments
+ dict_T *selfdict // Dictionary for "self"
+)
+{
+ char_u *argp;
+ int ret = OK;
+ typval_T argvars[MAX_FUNC_ARGS + 1]; /* vars for arguments */
+ int argcount = 0; /* number of arguments found */
+
+ /*
+ * Get the arguments.
+ */
+ argp = *arg;
+ while (argcount < MAX_FUNC_ARGS - (partial == NULL ? 0 : partial->pt_argc)) {
+ argp = skipwhite(argp + 1); // skip the '(' or ','
+ if (*argp == ')' || *argp == ',' || *argp == NUL) {
+ break;
+ }
+ if (eval1(&argp, &argvars[argcount], evaluate) == FAIL) {
+ ret = FAIL;
+ break;
+ }
+ ++argcount;
+ if (*argp != ',')
+ break;
+ }
+ if (*argp == ')')
+ ++argp;
+ else
+ ret = FAIL;
+
+ if (ret == OK) {
+ int i = 0;
+
+ if (get_vim_var_nr(VV_TESTING)) {
+ // Prepare for calling garbagecollect_for_testing(), need to know
+ // what variables are used on the call stack.
+ if (funcargs.ga_itemsize == 0) {
+ ga_init(&funcargs, (int)sizeof(typval_T *), 50);
+ }
+ for (i = 0; i < argcount; i++) {
+ ga_grow(&funcargs, 1);
+ ((typval_T **)funcargs.ga_data)[funcargs.ga_len++] = &argvars[i];
+ }
+ }
+ ret = call_func(name, len, rettv, argcount, argvars, NULL,
+ firstline, lastline, doesrange, evaluate,
+ partial, selfdict);
+
+ funcargs.ga_len -= i;
+ } else if (!aborting()) {
+ if (argcount == MAX_FUNC_ARGS) {
+ emsg_funcname(N_("E740: Too many arguments for function %s"), name);
+ } else {
+ emsg_funcname(N_("E116: Invalid arguments for function %s"), name);
+ }
+ }
+
+ while (--argcount >= 0) {
+ tv_clear(&argvars[argcount]);
+ }
+
+ *arg = skipwhite(argp);
+ return ret;
+}
+
+#define FLEN_FIXED 40
+
+/// Check whether function name starts with <SID> or s:
+///
+/// @warning Only works for names previously checked by eval_fname_script(), if
+/// it returned non-zero.
+///
+/// @param[in] name Name to check.
+///
+/// @return true if it starts with <SID> or s:, false otherwise.
+static inline bool eval_fname_sid(const char *const name)
+ FUNC_ATTR_PURE FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_WARN_UNUSED_RESULT
+ FUNC_ATTR_NONNULL_ALL
+{
+ return *name == 's' || TOUPPER_ASC(name[2]) == 'I';
+}
+
+/// In a script transform script-local names into actually used names
+///
+/// Transforms "<SID>" and "s:" prefixes to `K_SNR {N}` (e.g. K_SNR "123") and
+/// "<SNR>" prefix to `K_SNR`. Uses `fname_buf` buffer that is supposed to have
+/// #FLEN_FIXED + 1 length when it fits, otherwise it allocates memory.
+///
+/// @param[in] name Name to transform.
+/// @param fname_buf Buffer to save resulting function name to, if it fits.
+/// Must have at least #FLEN_FIXED + 1 length.
+/// @param[out] tofree Location where pointer to an allocated memory is saved
+/// in case result does not fit into fname_buf.
+/// @param[out] error Location where error type is saved, @see
+/// FnameTransError.
+///
+/// @return transformed name: either `fname_buf` or a pointer to an allocated
+/// memory.
+static char_u *fname_trans_sid(const char_u *const name,
+ char_u *const fname_buf,
+ char_u **const tofree, int *const error)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ char_u *fname;
+ const int llen = eval_fname_script((const char *)name);
+ if (llen > 0) {
+ fname_buf[0] = K_SPECIAL;
+ fname_buf[1] = KS_EXTRA;
+ fname_buf[2] = (int)KE_SNR;
+ int i = 3;
+ if (eval_fname_sid((const char *)name)) { // "<SID>" or "s:"
+ if (current_sctx.sc_sid <= 0) {
+ *error = ERROR_SCRIPT;
+ } else {
+ snprintf((char *)fname_buf + 3, FLEN_FIXED + 1, "%" PRId64 "_",
+ (int64_t)current_sctx.sc_sid);
+ i = (int)STRLEN(fname_buf);
+ }
+ }
+ if (i + STRLEN(name + llen) < FLEN_FIXED) {
+ STRCPY(fname_buf + i, name + llen);
+ fname = fname_buf;
+ } else {
+ fname = xmalloc(i + STRLEN(name + llen) + 1);
+ *tofree = fname;
+ memmove(fname, fname_buf, (size_t)i);
+ STRCPY(fname + i, name + llen);
+ }
+ } else {
+ fname = (char_u *)name;
+ }
+
+ return fname;
+}
+
+/// Find a function by name, return pointer to it in ufuncs.
+/// @return NULL for unknown function.
+ufunc_T *find_func(const char_u *name)
+{
+ hashitem_T *hi;
+
+ hi = hash_find(&func_hashtab, name);
+ if (!HASHITEM_EMPTY(hi))
+ return HI2UF(hi);
+ 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.
+ */
+static void cat_func_name(char_u *buf, ufunc_T *fp)
+{
+ if (fp->uf_name[0] == K_SPECIAL) {
+ STRCPY(buf, "<SNR>");
+ STRCAT(buf, fp->uf_name + 3);
+ } else
+ STRCPY(buf, fp->uf_name);
+}
+
+/*
+ * 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__
+ STRCPY(v->di_key, name);
+#endif
+ v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
+ tv_dict_add(dp, v);
+ v->di_tv.v_type = VAR_NUMBER;
+ v->di_tv.v_lock = VAR_FIXED;
+ v->di_tv.vval.v_number = nr;
+}
+
+/*
+ * Free "fc" and what it contains.
+ */
+static void
+free_funccal(
+ funccall_T *fc,
+ int free_val // a: vars were allocated
+)
+{
+ for (int i = 0; i < fc->fc_funcs.ga_len; i++) {
+ ufunc_T *fp = ((ufunc_T **)(fc->fc_funcs.ga_data))[i];
+
+ // When garbage collecting a funccall_T may be freed before the
+ // function that references it, clear its uf_scoped field.
+ // The function may have been redefined and point to another
+ // funccal_T, don't clear it then.
+ if (fp != NULL && fp->uf_scoped == fc) {
+ fp->uf_scoped = NULL;
+ }
+ }
+ ga_clear(&fc->fc_funcs);
+
+ // The a: variables typevals may not have been allocated, only free the
+ // allocated variables.
+ vars_clear_ext(&fc->l_avars.dv_hashtab, free_val);
+
+ // Free all l: variables.
+ vars_clear(&fc->l_vars.dv_hashtab);
+
+ // Free the a:000 variables if they were allocated.
+ if (free_val) {
+ TV_LIST_ITER(&fc->l_varlist, li, {
+ tv_clear(TV_LIST_ITEM_TV(li));
+ });
+ }
+
+ func_ptr_unref(fc->func);
+ xfree(fc);
+}
+
+/// Handle the last part of returning from a function: free the local hashtable.
+/// Unless it is still in use by a closure.
+static void cleanup_function_call(funccall_T *fc)
+{
+ current_funccal = fc->caller;
+
+ // If the a:000 list and the l: and a: dicts are not referenced and there
+ // is no closure using it, we can free the funccall_T and what's in it.
+ if (!fc_referenced(fc)) {
+ free_funccal(fc, false);
+ } else {
+ static int made_copy = 0;
+
+ // "fc" is still in use. This can happen when returning "a:000",
+ // assigning "l:" to a global variable or defining a closure.
+ // Link "fc" in the list for garbage collection later.
+ fc->caller = previous_funccal;
+ previous_funccal = fc;
+
+ // Make a copy of the a: variables, since we didn't do that above.
+ TV_DICT_ITER(&fc->l_avars, di, {
+ tv_copy(&di->di_tv, &di->di_tv);
+ });
+
+ // Make a copy of the a:000 items, since we didn't do that above.
+ TV_LIST_ITER(&fc->l_varlist, li, {
+ tv_copy(TV_LIST_ITEM_TV(li), TV_LIST_ITEM_TV(li));
+ });
+
+ if (++made_copy == 10000) {
+ // We have made a lot of copies. This can happen when
+ // repetitively calling a function that creates a reference to
+ // itself somehow. Call the garbage collector soon to avoid using
+ // too much memory.
+ made_copy = 0;
+ want_garbage_collect = true;
+ }
+ }
+}
+
+/// Unreference "fc": decrement the reference count and free it when it
+/// becomes zero. "fp" is detached from "fc".
+///
+/// @param[in] force When true, we are exiting.
+static void funccal_unref(funccall_T *fc, ufunc_T *fp, bool force)
+{
+ funccall_T **pfc;
+ int i;
+
+ if (fc == NULL) {
+ return;
+ }
+
+ fc->fc_refcount--;
+ if (force ? fc->fc_refcount <= 0 : !fc_referenced(fc)) {
+ for (pfc = &previous_funccal; *pfc != NULL; pfc = &(*pfc)->caller) {
+ if (fc == *pfc) {
+ *pfc = fc->caller;
+ free_funccal(fc, true);
+ return;
+ }
+ }
+ }
+ for (i = 0; i < fc->fc_funcs.ga_len; i++) {
+ if (((ufunc_T **)(fc->fc_funcs.ga_data))[i] == fp) {
+ ((ufunc_T **)(fc->fc_funcs.ga_data))[i] = NULL;
+ }
+ }
+}
+
+/// Remove the function from the function hashtable. If the function was
+/// deleted while it still has references this was already done.
+///
+/// @return true if the entry was deleted, false if it wasn't found.
+static bool func_remove(ufunc_T *fp)
+{
+ hashitem_T *hi = hash_find(&func_hashtab, UF2HIKEY(fp));
+
+ if (!HASHITEM_EMPTY(hi)) {
+ hash_remove(&func_hashtab, hi);
+ return true;
+ }
+
+ return false;
+}
+
+static void func_clear_items(ufunc_T *fp)
+{
+ ga_clear_strings(&(fp->uf_args));
+ ga_clear_strings(&(fp->uf_lines));
+
+ XFREE_CLEAR(fp->uf_tml_count);
+ XFREE_CLEAR(fp->uf_tml_total);
+ XFREE_CLEAR(fp->uf_tml_self);
+}
+
+/// 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.
+static void func_clear(ufunc_T *fp, bool force)
+{
+ if (fp->uf_cleared) {
+ return;
+ }
+ fp->uf_cleared = true;
+
+ // clear this function
+ func_clear_items(fp);
+ funccal_unref(fp->uf_scoped, fp, 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.
+static void func_free(ufunc_T *fp)
+{
+ // only remove it when not done already, otherwise we would remove a newer
+ // version of the function
+ if ((fp->uf_flags & (FC_DELETED | FC_REMOVED)) == 0) {
+ func_remove(fp);
+ }
+ xfree(fp);
+}
+
+/// Free all things that a function contains and free the function itself.
+///
+/// param[in] force When true, we are exiting.
+static void func_clear_free(ufunc_T *fp, bool force)
+{
+ func_clear(fp, force);
+ func_free(fp);
+}
+
+/// 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.
+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)
+{
+ char_u *save_sourcing_name;
+ linenr_T save_sourcing_lnum;
+ bool using_sandbox = false;
+ funccall_T *fc;
+ int save_did_emsg;
+ static int depth = 0;
+ dictitem_T *v;
+ int fixvar_idx = 0; // index in fixvar[]
+ int ai;
+ bool islambda = false;
+ char_u numbuf[NUMBUFLEN];
+ char_u *name;
+ proftime_T wait_start;
+ proftime_T call_start;
+ int started_profiling = false;
+ bool did_save_redo = false;
+ save_redo_T save_redo;
+
+ // If depth of calling is getting too high, don't execute the function
+ if (depth >= p_mfd) {
+ EMSG(_("E132: Function call depth is higher than 'maxfuncdepth'"));
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = -1;
+ return;
+ }
+ ++depth;
+ // Save search patterns and redo buffer.
+ save_search_patterns();
+ if (!ins_compl_active()) {
+ saveRedobuff(&save_redo);
+ did_save_redo = true;
+ }
+ ++fp->uf_calls;
+ // check for CTRL-C hit
+ line_breakcheck();
+ // prepare the funccall_T structure
+ fc = xmalloc(sizeof(funccall_T));
+ fc->caller = current_funccal;
+ current_funccal = fc;
+ fc->func = fp;
+ fc->rettv = rettv;
+ rettv->vval.v_number = 0;
+ fc->linenr = 0;
+ fc->returned = FALSE;
+ fc->level = ex_nesting_level;
+ // Check if this function has a breakpoint.
+ fc->breakpoint = dbg_find_breakpoint(false, fp->uf_name, (linenr_T)0);
+ fc->dbg_tick = debug_tick;
+
+ // Set up fields for closure.
+ fc->fc_refcount = 0;
+ fc->fc_copyID = 0;
+ ga_init(&fc->fc_funcs, sizeof(ufunc_T *), 1);
+ func_ptr_ref(fp);
+
+ if (STRNCMP(fp->uf_name, "<lambda>", 8) == 0) {
+ islambda = true;
+ }
+
+ // Note about using fc->fixvar[]: This is an array of FIXVAR_CNT variables
+ // with names up to VAR_SHORT_LEN long. This avoids having to alloc/free
+ // each argument variable and saves a lot of time.
+ //
+ // Init l: variables.
+ init_var_dict(&fc->l_vars, &fc->l_vars_var, VAR_DEF_SCOPE);
+ if (selfdict != NULL) {
+ // Set l:self to "selfdict". Use "name" to avoid a warning from
+ // some compiler that checks the destination size.
+ v = (dictitem_T *)&fc->fixvar[fixvar_idx++];
+#ifndef __clang_analyzer__
+ name = v->di_key;
+ STRCPY(name, "self");
+#endif
+ v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
+ tv_dict_add(&fc->l_vars, v);
+ v->di_tv.v_type = VAR_DICT;
+ v->di_tv.v_lock = 0;
+ v->di_tv.vval.v_dict = selfdict;
+ ++selfdict->dv_refcount;
+ }
+
+ /*
+ * Init a: variables.
+ * Set a:0 to "argcount".
+ * Set a:000 to a list with room for the "..." arguments.
+ */
+ init_var_dict(&fc->l_avars, &fc->l_avars_var, VAR_SCOPE);
+ add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++], "0",
+ (varnumber_T)(argcount - fp->uf_args.ga_len));
+ fc->l_avars.dv_lock = VAR_FIXED;
+ // Use "name" to avoid a warning from some compiler that checks the
+ // destination size.
+ v = (dictitem_T *)&fc->fixvar[fixvar_idx++];
+#ifndef __clang_analyzer__
+ name = v->di_key;
+ STRCPY(name, "000");
+#endif
+ v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
+ tv_dict_add(&fc->l_avars, v);
+ v->di_tv.v_type = VAR_LIST;
+ v->di_tv.v_lock = VAR_FIXED;
+ v->di_tv.vval.v_list = &fc->l_varlist;
+ tv_list_init_static(&fc->l_varlist);
+ tv_list_set_lock(&fc->l_varlist, VAR_FIXED);
+
+ // Set a:firstline to "firstline" and a:lastline to "lastline".
+ // Set a:name to named arguments.
+ // Set a:N to the "..." arguments.
+ add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++],
+ "firstline", (varnumber_T)firstline);
+ add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++],
+ "lastline", (varnumber_T)lastline);
+ for (int i = 0; i < argcount; i++) {
+ bool addlocal = false;
+
+ ai = i - fp->uf_args.ga_len;
+ if (ai < 0) {
+ // named argument a:name
+ name = FUNCARG(fp, i);
+ if (islambda) {
+ addlocal = true;
+ }
+ } else {
+ // "..." argument a:1, a:2, etc.
+ snprintf((char *)numbuf, sizeof(numbuf), "%d", ai + 1);
+ name = numbuf;
+ }
+ if (fixvar_idx < FIXVAR_CNT && STRLEN(name) <= VAR_SHORT_LEN) {
+ v = (dictitem_T *)&fc->fixvar[fixvar_idx++];
+ v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
+ } else {
+ v = xmalloc(sizeof(dictitem_T) + STRLEN(name));
+ v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX | DI_FLAGS_ALLOC;
+ }
+ STRCPY(v->di_key, name);
+
+ // Note: the values are copied directly to avoid alloc/free.
+ // "argvars" must have VAR_FIXED for v_lock.
+ v->di_tv = argvars[i];
+ v->di_tv.v_lock = VAR_FIXED;
+
+ if (addlocal) {
+ // Named arguments can be accessed without the "a:" prefix in lambda
+ // expressions. Add to the l: dict.
+ tv_copy(&v->di_tv, &v->di_tv);
+ tv_dict_add(&fc->l_vars, v);
+ } else {
+ tv_dict_add(&fc->l_avars, v);
+ }
+
+ if (ai >= 0 && ai < MAX_FUNC_ARGS) {
+ tv_list_append(&fc->l_varlist, &fc->l_listitems[ai]);
+ *TV_LIST_ITEM_TV(&fc->l_listitems[ai]) = argvars[i];
+ TV_LIST_ITEM_TV(&fc->l_listitems[ai])->v_lock = VAR_FIXED;
+ }
+ }
+
+ // Don't redraw while executing the function.
+ RedrawingDisabled++;
+ save_sourcing_name = sourcing_name;
+ save_sourcing_lnum = sourcing_lnum;
+ sourcing_lnum = 1;
+
+ if (fp->uf_flags & FC_SANDBOX) {
+ using_sandbox = true;
+ sandbox++;
+ }
+
+ // need space for new sourcing_name:
+ // * save_sourcing_name
+ // * "["number"].." or "function "
+ // * "<SNR>" + fp->uf_name - 3
+ // * terminating NUL
+ size_t len = (save_sourcing_name == NULL ? 0 : STRLEN(save_sourcing_name))
+ + STRLEN(fp->uf_name) + 27;
+ sourcing_name = xmalloc(len);
+ {
+ if (save_sourcing_name != NULL
+ && STRNCMP(save_sourcing_name, "function ", 9) == 0) {
+ vim_snprintf((char *)sourcing_name,
+ len,
+ "%s[%" PRId64 "]..",
+ save_sourcing_name,
+ (int64_t)save_sourcing_lnum);
+ } else {
+ STRCPY(sourcing_name, "function ");
+ }
+ cat_func_name(sourcing_name + STRLEN(sourcing_name), fp);
+
+ if (p_verbose >= 12) {
+ ++no_wait_return;
+ verbose_enter_scroll();
+
+ smsg(_("calling %s"), sourcing_name);
+ if (p_verbose >= 14) {
+ msg_puts("(");
+ for (int i = 0; i < argcount; i++) {
+ if (i > 0) {
+ msg_puts(", ");
+ }
+ if (argvars[i].v_type == VAR_NUMBER) {
+ msg_outnum((long)argvars[i].vval.v_number);
+ } else {
+ // Do not want errors such as E724 here.
+ emsg_off++;
+ char *tofree = encode_tv2string(&argvars[i], NULL);
+ emsg_off--;
+ if (tofree != NULL) {
+ char *s = tofree;
+ char buf[MSG_BUF_LEN];
+ if (vim_strsize((char_u *)s) > MSG_BUF_CLEN) {
+ trunc_string((char_u *)s, (char_u *)buf, MSG_BUF_CLEN,
+ sizeof(buf));
+ s = buf;
+ }
+ msg_puts(s);
+ xfree(tofree);
+ }
+ }
+ }
+ msg_puts(")");
+ }
+ msg_puts("\n"); // don't overwrite this either
+
+ verbose_leave_scroll();
+ --no_wait_return;
+ }
+ }
+
+ const bool do_profiling_yes = do_profiling == PROF_YES;
+
+ bool func_not_yet_profiling_but_should =
+ do_profiling_yes
+ && !fp->uf_profiling && has_profiling(false, fp->uf_name, NULL);
+
+ if (func_not_yet_profiling_but_should) {
+ started_profiling = true;
+ func_do_profile(fp);
+ }
+
+ bool func_or_func_caller_profiling =
+ do_profiling_yes
+ && (fp->uf_profiling
+ || (fc->caller != NULL && fc->caller->func->uf_profiling));
+
+ if (func_or_func_caller_profiling) {
+ ++fp->uf_tm_count;
+ call_start = profile_start();
+ fp->uf_tm_children = profile_zero();
+ }
+
+ if (do_profiling_yes) {
+ script_prof_save(&wait_start);
+ }
+
+ const sctx_T save_current_sctx = current_sctx;
+ current_sctx = fp->uf_script_ctx;
+ save_did_emsg = did_emsg;
+ did_emsg = FALSE;
+
+ // call do_cmdline() to execute the lines
+ do_cmdline(NULL, get_func_line, (void *)fc,
+ DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT);
+
+ --RedrawingDisabled;
+
+ // when the function was aborted because of an error, return -1
+ if ((did_emsg
+ && (fp->uf_flags & FC_ABORT)) || rettv->v_type == VAR_UNKNOWN) {
+ tv_clear(rettv);
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = -1;
+ }
+
+ if (func_or_func_caller_profiling) {
+ call_start = profile_end(call_start);
+ call_start = profile_sub_wait(wait_start, call_start); // -V614
+ fp->uf_tm_total = profile_add(fp->uf_tm_total, call_start);
+ fp->uf_tm_self = profile_self(fp->uf_tm_self, call_start,
+ fp->uf_tm_children);
+ if (fc->caller != NULL && fc->caller->func->uf_profiling) {
+ fc->caller->func->uf_tm_children =
+ profile_add(fc->caller->func->uf_tm_children, call_start);
+ fc->caller->func->uf_tml_children =
+ profile_add(fc->caller->func->uf_tml_children, call_start);
+ }
+ if (started_profiling) {
+ // make a ":profdel func" stop profiling the function
+ fp->uf_profiling = false;
+ }
+ }
+
+ // when being verbose, mention the return value
+ if (p_verbose >= 12) {
+ ++no_wait_return;
+ verbose_enter_scroll();
+
+ if (aborting())
+ smsg(_("%s aborted"), sourcing_name);
+ else if (fc->rettv->v_type == VAR_NUMBER)
+ smsg(_("%s returning #%" PRId64 ""),
+ sourcing_name, (int64_t)fc->rettv->vval.v_number);
+ else {
+ char_u buf[MSG_BUF_LEN];
+
+ // The value may be very long. Skip the middle part, so that we
+ // have some idea how it starts and ends. smsg() would always
+ // truncate it at the end. Don't want errors such as E724 here.
+ emsg_off++;
+ char_u *s = (char_u *) encode_tv2string(fc->rettv, NULL);
+ char_u *tofree = s;
+ emsg_off--;
+ if (s != NULL) {
+ if (vim_strsize(s) > MSG_BUF_CLEN) {
+ trunc_string(s, buf, MSG_BUF_CLEN, MSG_BUF_LEN);
+ s = buf;
+ }
+ smsg(_("%s returning %s"), sourcing_name, s);
+ xfree(tofree);
+ }
+ }
+ msg_puts("\n"); // don't overwrite this either
+
+ verbose_leave_scroll();
+ --no_wait_return;
+ }
+
+ xfree(sourcing_name);
+ sourcing_name = save_sourcing_name;
+ sourcing_lnum = save_sourcing_lnum;
+ current_sctx = save_current_sctx;
+ if (do_profiling_yes) {
+ script_prof_restore(&wait_start);
+ }
+ if (using_sandbox) {
+ sandbox--;
+ }
+
+ if (p_verbose >= 12 && sourcing_name != NULL) {
+ ++no_wait_return;
+ verbose_enter_scroll();
+
+ smsg(_("continuing in %s"), sourcing_name);
+ msg_puts("\n"); // don't overwrite this either
+
+ verbose_leave_scroll();
+ --no_wait_return;
+ }
+
+ did_emsg |= save_did_emsg;
+ depth--;
+
+ cleanup_function_call(fc);
+
+ if (--fp->uf_calls <= 0 && fp->uf_refcount <= 0) {
+ // Function was unreferenced while being used, free it now.
+ func_clear_free(fp, false);
+ }
+ // restore search patterns and redo buffer
+ if (did_save_redo) {
+ restoreRedobuff(&save_redo);
+ }
+ restore_search_patterns();
+}
+
+/// There are two kinds of function names:
+/// 1. ordinary names, function defined with :function
+/// 2. numbered functions and lambdas
+/// For the first we only count the name stored in func_hashtab as a reference,
+/// using function() does not count as a reference, because the function is
+/// looked up by name.
+static bool func_name_refcount(char_u *name)
+{
+ return isdigit(*name) || *name == '<';
+}
+
+/*
+ * Save the current function call pointer, and set it to NULL.
+ * Used when executing autocommands and for ":source".
+ */
+void *save_funccal(void)
+{
+ funccall_T *fc = current_funccal;
+
+ current_funccal = NULL;
+ return (void *)fc;
+}
+
+void restore_funccal(void *vfc)
+{
+ current_funccal = (funccall_T *)vfc;
+}
+
+funccall_T *get_current_funccal(void)
+{
+ return current_funccal;
+}
+
+#if defined(EXITFREE)
+void free_all_functions(void)
+{
+ hashitem_T *hi;
+ ufunc_T *fp;
+ uint64_t skipped = 0;
+ uint64_t todo = 1;
+ uint64_t used;
+
+ // Clean up the call stack.
+ while (current_funccal != NULL) {
+ tv_clear(current_funccal->rettv);
+ cleanup_function_call(current_funccal);
+ }
+
+ // First clear what the functions contain. Since this may lower the
+ // reference count of a function, it may also free a function and change
+ // the hash table. Restart if that happens.
+ while (todo > 0) {
+ todo = func_hashtab.ht_used;
+ for (hi = func_hashtab.ht_array; todo > 0; hi++) {
+ if (!HASHITEM_EMPTY(hi)) {
+ // Only free functions that are not refcounted, those are
+ // supposed to be freed when no longer referenced.
+ fp = HI2UF(hi);
+ if (func_name_refcount(fp->uf_name)) {
+ skipped++;
+ } else {
+ used = func_hashtab.ht_used;
+ func_clear(fp, true);
+ if (used != func_hashtab.ht_used) {
+ skipped = 0;
+ break;
+ }
+ }
+ todo--;
+ }
+ }
+ }
+
+ // Now actually free the functions. Need to start all over every time,
+ // because func_free() may change the hash table.
+ skipped = 0;
+ while (func_hashtab.ht_used > skipped) {
+ todo = func_hashtab.ht_used;
+ for (hi = func_hashtab.ht_array; todo > 0; hi++) {
+ if (!HASHITEM_EMPTY(hi)) {
+ todo--;
+ // Only free functions that are not refcounted, those are
+ // supposed to be freed when no longer referenced.
+ fp = HI2UF(hi);
+ if (func_name_refcount(fp->uf_name)) {
+ skipped++;
+ } else {
+ func_free(fp);
+ skipped = 0;
+ break;
+ }
+ }
+ }
+ }
+ if (skipped == 0) {
+ hash_clear(&func_hashtab);
+ }
+}
+
+#endif
+
+/// Checks if a builtin function with the given name exists.
+///
+/// @param[in] name name of the builtin function to check.
+/// @param[in] len length of "name", or -1 for NUL terminated.
+///
+/// @return true if "name" looks like a builtin function name: starts with a
+/// lower case letter and doesn't contain AUTOLOAD_CHAR.
+static bool builtin_function(const char *name, int len)
+{
+ if (!ASCII_ISLOWER(name[0])) {
+ return false;
+ }
+
+ const char *p = (len == -1
+ ? strchr(name, AUTOLOAD_CHAR)
+ : memchr(name, AUTOLOAD_CHAR, (size_t)len));
+
+ return p == NULL;
+}
+
+int func_call(char_u *name, typval_T *args, partial_T *partial,
+ dict_T *selfdict, typval_T *rettv)
+{
+ typval_T argv[MAX_FUNC_ARGS + 1];
+ int argc = 0;
+ int dummy;
+ int r = 0;
+
+ TV_LIST_ITER(args->vval.v_list, item, {
+ if (argc == MAX_FUNC_ARGS - (partial == NULL ? 0 : partial->pt_argc)) {
+ EMSG(_("E699: Too many arguments"));
+ goto func_call_skip_call;
+ }
+ // Make a copy of each argument. This is needed to be able to set
+ // v_lock to VAR_FIXED in the copy without changing the original list.
+ tv_copy(TV_LIST_ITEM_TV(item), &argv[argc++]);
+ });
+
+ r = call_func(name, (int)STRLEN(name), rettv, argc, argv, NULL,
+ curwin->w_cursor.lnum, curwin->w_cursor.lnum,
+ &dummy, true, partial, selfdict);
+
+func_call_skip_call:
+ // Free the arguments.
+ while (argc > 0) {
+ tv_clear(&argv[--argc]);
+ }
+
+ return r;
+}
+
+/// Call a function with its resolved parameters
+///
+/// "argv_func", when not NULL, can be used to fill in arguments only when the
+/// invoked function uses them. It is called like this:
+/// new_argcount = argv_func(current_argcount, argv, called_func_argcount)
+///
+/// @return FAIL if function cannot be called, else OK (even if an error
+/// occurred while executing the function! Set `msg_list` to capture
+/// the error, see do_cmdline()).
+int
+call_func(
+ const char_u *funcname, // name of the function
+ int len, // length of "name"
+ typval_T *rettv, // [out] value goes here
+ int argcount_in, // number of "argvars"
+ typval_T *argvars_in, // vars for arguments, must have "argcount"
+ // PLUS ONE elements!
+ ArgvFunc argv_func, // function to fill in argvars
+ linenr_T firstline, // first line of range
+ linenr_T lastline, // last line of range
+ int *doesrange, // [out] function handled range
+ bool evaluate,
+ partial_T *partial, // optional, can be NULL
+ dict_T *selfdict_in // Dictionary for "self"
+)
+ FUNC_ATTR_NONNULL_ARG(1, 3, 5, 9)
+{
+ int ret = FAIL;
+ int error = ERROR_NONE;
+ ufunc_T *fp;
+ char_u fname_buf[FLEN_FIXED + 1];
+ char_u *tofree = NULL;
+ char_u *fname;
+ char_u *name;
+ int argcount = argcount_in;
+ typval_T *argvars = argvars_in;
+ dict_T *selfdict = selfdict_in;
+ typval_T argv[MAX_FUNC_ARGS + 1]; // used when "partial" is not NULL
+ int argv_clear = 0;
+
+ // Initialize rettv so that it is safe for caller to invoke clear_tv(rettv)
+ // even when call_func() returns FAIL.
+ rettv->v_type = VAR_UNKNOWN;
+
+ // Make a copy of the name, if it comes from a funcref variable it could
+ // be changed or deleted in the called function.
+ name = vim_strnsave(funcname, len);
+
+ fname = fname_trans_sid(name, fname_buf, &tofree, &error);
+
+ *doesrange = false;
+
+ if (partial != NULL) {
+ // When the function has a partial with a dict and there is a dict
+ // argument, use the dict argument. That is backwards compatible.
+ // When the dict was bound explicitly use the one from the partial.
+ if (partial->pt_dict != NULL
+ && (selfdict_in == NULL || !partial->pt_auto)) {
+ selfdict = partial->pt_dict;
+ }
+ if (error == ERROR_NONE && partial->pt_argc > 0) {
+ for (argv_clear = 0; argv_clear < partial->pt_argc; argv_clear++) {
+ if (argv_clear + argcount_in >= MAX_FUNC_ARGS) {
+ error = ERROR_TOOMANY;
+ goto theend;
+ }
+ tv_copy(&partial->pt_argv[argv_clear], &argv[argv_clear]);
+ }
+ for (int i = 0; i < argcount_in; i++) {
+ argv[i + argv_clear] = argvars_in[i];
+ }
+ argvars = argv;
+ argcount = partial->pt_argc + argcount_in;
+ }
+ }
+
+ if (error == ERROR_NONE && evaluate) {
+ char_u *rfname = fname;
+
+ // Ignore "g:" before a function name.
+ if (fname[0] == 'g' && fname[1] == ':') {
+ rfname = fname + 2;
+ }
+
+ rettv->v_type = VAR_NUMBER; // default rettv is number zero
+ rettv->vval.v_number = 0;
+ error = ERROR_UNKNOWN;
+
+ if (is_luafunc(partial)) {
+ if (len > 0) {
+ error = ERROR_NONE;
+ executor_call_lua((const char *)funcname, len,
+ argvars, argcount, rettv);
+ }
+ } else if (!builtin_function((const char *)rfname, -1)) {
+ // User defined function.
+ if (partial != NULL && partial->pt_func != NULL) {
+ fp = partial->pt_func;
+ } else {
+ fp = find_func(rfname);
+ }
+
+ // Trigger FuncUndefined event, may load the function.
+ if (fp == NULL
+ && apply_autocmds(EVENT_FUNCUNDEFINED, rfname, rfname, TRUE, NULL)
+ && !aborting()) {
+ // executed an autocommand, search for the function again
+ fp = find_func(rfname);
+ }
+ // Try loading a package.
+ if (fp == NULL && script_autoload((const char *)rfname, STRLEN(rfname),
+ true) && !aborting()) {
+ // Loaded a package, search for the function again.
+ fp = find_func(rfname);
+ }
+
+ if (fp != NULL && (fp->uf_flags & FC_DELETED)) {
+ error = ERROR_DELETED;
+ } else if (fp != NULL) {
+ if (argv_func != NULL) {
+ // postponed filling in the arguments, do it now
+ argcount = argv_func(argcount, argvars, argv_clear,
+ fp->uf_args.ga_len);
+ }
+ if (fp->uf_flags & FC_RANGE) {
+ *doesrange = true;
+ }
+ if (argcount < fp->uf_args.ga_len) {
+ error = ERROR_TOOFEW;
+ } else if (!fp->uf_varargs && argcount > fp->uf_args.ga_len) {
+ error = ERROR_TOOMANY;
+ } else if ((fp->uf_flags & FC_DICT) && selfdict == NULL) {
+ error = ERROR_DICT;
+ } else {
+ // Call the user function.
+ call_user_func(fp, argcount, argvars, rettv, firstline, lastline,
+ (fp->uf_flags & FC_DICT) ? selfdict : NULL);
+ error = ERROR_NONE;
+ }
+ }
+ } else {
+ // Find the function name in the table, call its implementation.
+ const VimLFuncDef *const fdef = find_internal_func((const char *)fname);
+ if (fdef != NULL) {
+ if (argcount < fdef->min_argc) {
+ error = ERROR_TOOFEW;
+ } else if (argcount > fdef->max_argc) {
+ error = ERROR_TOOMANY;
+ } else {
+ argvars[argcount].v_type = VAR_UNKNOWN;
+ fdef->func(argvars, rettv, fdef->data);
+ error = ERROR_NONE;
+ }
+ }
+ }
+ /*
+ * The function call (or "FuncUndefined" autocommand sequence) might
+ * have been aborted by an error, an interrupt, or an explicitly thrown
+ * exception that has not been caught so far. This situation can be
+ * tested for by calling aborting(). For an error in an internal
+ * function or for the "E132" error in call_user_func(), however, the
+ * throw point at which the "force_abort" flag (temporarily reset by
+ * emsg()) is normally updated has not been reached yet. We need to
+ * update that flag first to make aborting() reliable.
+ */
+ update_force_abort();
+ }
+ if (error == ERROR_NONE)
+ ret = OK;
+
+theend:
+ // Report an error unless the argument evaluation or function call has been
+ // cancelled due to an aborting error, an interrupt, or an exception.
+ if (!aborting()) {
+ switch (error) {
+ case ERROR_UNKNOWN:
+ emsg_funcname(N_("E117: Unknown function: %s"), name);
+ break;
+ case ERROR_DELETED:
+ emsg_funcname(N_("E933: Function was deleted: %s"), name);
+ break;
+ case ERROR_TOOMANY:
+ emsg_funcname(_(e_toomanyarg), name);
+ break;
+ case ERROR_TOOFEW:
+ emsg_funcname(N_("E119: Not enough arguments for function: %s"),
+ name);
+ break;
+ case ERROR_SCRIPT:
+ emsg_funcname(N_("E120: Using <SID> not in a script context: %s"),
+ name);
+ break;
+ case ERROR_DICT:
+ emsg_funcname(N_("E725: Calling dict function without Dictionary: %s"),
+ name);
+ break;
+ }
+ }
+
+ while (argv_clear > 0) {
+ tv_clear(&argv[--argv_clear]);
+ }
+ xfree(tofree);
+ xfree(name);
+
+ return ret;
+}
+
+/// List the head of the function: "name(arg1, arg2)".
+///
+/// @param[in] fp Function pointer.
+/// @param[in] indent Indent line.
+/// @param[in] force Include bang "!" (i.e.: "function!").
+static void list_func_head(ufunc_T *fp, int indent, bool force)
+{
+ msg_start();
+ if (indent)
+ MSG_PUTS(" ");
+ MSG_PUTS(force ? "function! " : "function ");
+ if (fp->uf_name[0] == K_SPECIAL) {
+ MSG_PUTS_ATTR("<SNR>", HL_ATTR(HLF_8));
+ msg_puts((const char *)fp->uf_name + 3);
+ } else {
+ msg_puts((const char *)fp->uf_name);
+ }
+ msg_putchar('(');
+ int j;
+ for (j = 0; j < fp->uf_args.ga_len; j++) {
+ if (j) {
+ msg_puts(", ");
+ }
+ msg_puts((const char *)FUNCARG(fp, j));
+ }
+ if (fp->uf_varargs) {
+ if (j) {
+ msg_puts(", ");
+ }
+ msg_puts("...");
+ }
+ msg_putchar(')');
+ if (fp->uf_flags & FC_ABORT) {
+ msg_puts(" abort");
+ }
+ if (fp->uf_flags & FC_RANGE) {
+ msg_puts(" range");
+ }
+ if (fp->uf_flags & FC_DICT) {
+ msg_puts(" dict");
+ }
+ if (fp->uf_flags & FC_CLOSURE) {
+ msg_puts(" closure");
+ }
+ msg_clr_eos();
+ if (p_verbose > 0) {
+ last_set_msg(fp->uf_script_ctx);
+ }
+}
+
+/// Get a function name, translating "<SID>" and "<SNR>".
+/// Also handles a Funcref in a List or Dictionary.
+/// flags:
+/// TFN_INT: internal function name OK
+/// TFN_QUIET: be quiet
+/// TFN_NO_AUTOLOAD: do not use script autoloading
+/// TFN_NO_DEREF: do not dereference a Funcref
+/// Advances "pp" to just after the function name (if no error).
+///
+/// @return the function name in allocated memory, or NULL for failure.
+char_u *
+trans_function_name(
+ char_u **pp,
+ bool skip, // only find the end, don't evaluate
+ int flags,
+ funcdict_T *fdp, // return: info about dictionary used
+ partial_T **partial // return: partial of a FuncRef
+)
+ FUNC_ATTR_NONNULL_ARG(1)
+{
+ char_u *name = NULL;
+ const char_u *start;
+ const char_u *end;
+ int lead;
+ int len;
+ lval_T lv;
+
+ if (fdp != NULL)
+ memset(fdp, 0, sizeof(funcdict_T));
+ start = *pp;
+
+ /* Check for hard coded <SNR>: already translated function ID (from a user
+ * command). */
+ if ((*pp)[0] == K_SPECIAL && (*pp)[1] == KS_EXTRA
+ && (*pp)[2] == (int)KE_SNR) {
+ *pp += 3;
+ len = get_id_len((const char **)pp) + 3;
+ return (char_u *)xmemdupz(start, len);
+ }
+
+ /* A name starting with "<SID>" or "<SNR>" is local to a script. But
+ * don't skip over "s:", get_lval() needs it for "s:dict.func". */
+ lead = eval_fname_script((const char *)start);
+ if (lead > 2) {
+ start += lead;
+ }
+
+ // Note that TFN_ flags use the same values as GLV_ flags.
+ end = get_lval((char_u *)start, NULL, &lv, false, skip, flags | GLV_READ_ONLY,
+ lead > 2 ? 0 : FNE_CHECK_START);
+ if (end == start) {
+ if (!skip)
+ EMSG(_("E129: Function name required"));
+ goto theend;
+ }
+ if (end == NULL || (lv.ll_tv != NULL && (lead > 2 || lv.ll_range))) {
+ /*
+ * Report an invalid expression in braces, unless the expression
+ * evaluation has been cancelled due to an aborting error, an
+ * interrupt, or an exception.
+ */
+ if (!aborting()) {
+ if (end != NULL) {
+ emsgf(_(e_invarg2), start);
+ }
+ } else {
+ *pp = (char_u *)find_name_end(start, NULL, NULL, FNE_INCL_BR);
+ }
+ goto theend;
+ }
+
+ if (lv.ll_tv != NULL) {
+ if (fdp != NULL) {
+ fdp->fd_dict = lv.ll_dict;
+ fdp->fd_newkey = lv.ll_newkey;
+ lv.ll_newkey = NULL;
+ fdp->fd_di = lv.ll_di;
+ }
+ if (lv.ll_tv->v_type == VAR_FUNC && lv.ll_tv->vval.v_string != NULL) {
+ name = vim_strsave(lv.ll_tv->vval.v_string);
+ *pp = (char_u *)end;
+ } else if (lv.ll_tv->v_type == VAR_PARTIAL
+ && lv.ll_tv->vval.v_partial != NULL) {
+ if (is_luafunc(lv.ll_tv->vval.v_partial) && *end == '.') {
+ len = check_luafunc_name((const char *)end+1, true);
+ if (len == 0) {
+ EMSG2(e_invexpr2, "v:lua");
+ goto theend;
+ }
+ name = xmallocz(len);
+ memcpy(name, end+1, len);
+ *pp = (char_u *)end+1+len;
+ } else {
+ name = vim_strsave(partial_name(lv.ll_tv->vval.v_partial));
+ *pp = (char_u *)end;
+ }
+ if (partial != NULL) {
+ *partial = lv.ll_tv->vval.v_partial;
+ }
+ } else {
+ if (!skip && !(flags & TFN_QUIET) && (fdp == NULL
+ || lv.ll_dict == NULL
+ || fdp->fd_newkey == NULL)) {
+ EMSG(_(e_funcref));
+ } else {
+ *pp = (char_u *)end;
+ }
+ name = NULL;
+ }
+ goto theend;
+ }
+
+ if (lv.ll_name == NULL) {
+ // Error found, but continue after the function name.
+ *pp = (char_u *)end;
+ goto theend;
+ }
+
+ /* Check if the name is a Funcref. If so, use the value. */
+ if (lv.ll_exp_name != NULL) {
+ len = (int)strlen(lv.ll_exp_name);
+ name = deref_func_name(lv.ll_exp_name, &len, partial,
+ flags & TFN_NO_AUTOLOAD);
+ if ((const char *)name == lv.ll_exp_name) {
+ name = NULL;
+ }
+ } else if (!(flags & TFN_NO_DEREF)) {
+ len = (int)(end - *pp);
+ name = deref_func_name((const char *)(*pp), &len, partial,
+ flags & TFN_NO_AUTOLOAD);
+ if (name == *pp) {
+ name = NULL;
+ }
+ }
+ if (name != NULL) {
+ name = vim_strsave(name);
+ *pp = (char_u *)end;
+ if (strncmp((char *)name, "<SNR>", 5) == 0) {
+ // Change "<SNR>" to the byte sequence.
+ name[0] = K_SPECIAL;
+ name[1] = KS_EXTRA;
+ name[2] = (int)KE_SNR;
+ memmove(name + 3, name + 5, strlen((char *)name + 5) + 1);
+ }
+ goto theend;
+ }
+
+ if (lv.ll_exp_name != NULL) {
+ len = (int)strlen(lv.ll_exp_name);
+ if (lead <= 2 && lv.ll_name == lv.ll_exp_name
+ && lv.ll_name_len >= 2 && memcmp(lv.ll_name, "s:", 2) == 0) {
+ // When there was "s:" already or the name expanded to get a
+ // leading "s:" then remove it.
+ lv.ll_name += 2;
+ lv.ll_name_len -= 2;
+ len -= 2;
+ lead = 2;
+ }
+ } else {
+ // Skip over "s:" and "g:".
+ if (lead == 2 || (lv.ll_name[0] == 'g' && lv.ll_name[1] == ':')) {
+ lv.ll_name += 2;
+ lv.ll_name_len -= 2;
+ }
+ len = (int)((const char *)end - lv.ll_name);
+ }
+
+ size_t sid_buf_len = 0;
+ char sid_buf[20];
+
+ // Copy the function name to allocated memory.
+ // Accept <SID>name() inside a script, translate into <SNR>123_name().
+ // Accept <SNR>123_name() outside a script.
+ if (skip) {
+ lead = 0; // do nothing
+ } else if (lead > 0) {
+ lead = 3;
+ if ((lv.ll_exp_name != NULL && eval_fname_sid(lv.ll_exp_name))
+ || eval_fname_sid((const char *)(*pp))) {
+ // It's "s:" or "<SID>".
+ if (current_sctx.sc_sid <= 0) {
+ EMSG(_(e_usingsid));
+ goto theend;
+ }
+ sid_buf_len = snprintf(sid_buf, sizeof(sid_buf),
+ "%" PRIdSCID "_", current_sctx.sc_sid);
+ lead += sid_buf_len;
+ }
+ } else if (!(flags & TFN_INT)
+ && builtin_function(lv.ll_name, lv.ll_name_len)) {
+ EMSG2(_("E128: Function name must start with a capital or \"s:\": %s"),
+ start);
+ goto theend;
+ }
+
+ if (!skip && !(flags & TFN_QUIET) && !(flags & TFN_NO_DEREF)) {
+ char_u *cp = xmemrchr(lv.ll_name, ':', lv.ll_name_len);
+
+ if (cp != NULL && cp < end) {
+ EMSG2(_("E884: Function name cannot contain a colon: %s"), start);
+ goto theend;
+ }
+ }
+
+ name = xmalloc(len + lead + 1);
+ if (!skip && lead > 0) {
+ name[0] = K_SPECIAL;
+ name[1] = KS_EXTRA;
+ name[2] = (int)KE_SNR;
+ if (sid_buf_len > 0) { // If it's "<SID>"
+ memcpy(name + 3, sid_buf, sid_buf_len);
+ }
+ }
+ memmove(name + lead, lv.ll_name, len);
+ name[lead + len] = NUL;
+ *pp = (char_u *)end;
+
+theend:
+ clear_lval(&lv);
+ return name;
+}
+
+/*
+ * ":function"
+ */
+void ex_function(exarg_T *eap)
+{
+ char_u *theline;
+ char_u *line_to_free = NULL;
+ int c;
+ int saved_did_emsg;
+ int saved_wait_return = need_wait_return;
+ char_u *name = NULL;
+ char_u *p;
+ char_u *arg;
+ char_u *line_arg = NULL;
+ garray_T newargs;
+ garray_T newlines;
+ int varargs = false;
+ int flags = 0;
+ ufunc_T *fp;
+ bool overwrite = false;
+ int indent;
+ int nesting;
+ dictitem_T *v;
+ funcdict_T fudi;
+ static int func_nr = 0; // number for nameless function
+ int paren;
+ hashtab_T *ht;
+ int todo;
+ hashitem_T *hi;
+ linenr_T sourcing_lnum_off;
+ linenr_T sourcing_lnum_top;
+ bool is_heredoc = false;
+ char_u *skip_until = NULL;
+ char_u *heredoc_trimmed = NULL;
+ bool show_block = false;
+ bool do_concat = true;
+
+ /*
+ * ":function" without argument: list functions.
+ */
+ if (ends_excmd(*eap->arg)) {
+ if (!eap->skip) {
+ todo = (int)func_hashtab.ht_used;
+ for (hi = func_hashtab.ht_array; todo > 0 && !got_int; ++hi) {
+ if (!HASHITEM_EMPTY(hi)) {
+ --todo;
+ fp = HI2UF(hi);
+ if (message_filtered(fp->uf_name)) {
+ continue;
+ }
+ if (!func_name_refcount(fp->uf_name)) {
+ list_func_head(fp, false, false);
+ }
+ }
+ }
+ }
+ eap->nextcmd = check_nextcmd(eap->arg);
+ return;
+ }
+
+ /*
+ * ":function /pat": list functions matching pattern.
+ */
+ if (*eap->arg == '/') {
+ p = skip_regexp(eap->arg + 1, '/', TRUE, NULL);
+ if (!eap->skip) {
+ regmatch_T regmatch;
+
+ c = *p;
+ *p = NUL;
+ regmatch.regprog = vim_regcomp(eap->arg + 1, RE_MAGIC);
+ *p = c;
+ if (regmatch.regprog != NULL) {
+ regmatch.rm_ic = p_ic;
+
+ todo = (int)func_hashtab.ht_used;
+ for (hi = func_hashtab.ht_array; todo > 0 && !got_int; ++hi) {
+ if (!HASHITEM_EMPTY(hi)) {
+ --todo;
+ fp = HI2UF(hi);
+ if (!isdigit(*fp->uf_name)
+ && vim_regexec(&regmatch, fp->uf_name, 0))
+ list_func_head(fp, false, false);
+ }
+ }
+ vim_regfree(regmatch.regprog);
+ }
+ }
+ if (*p == '/')
+ ++p;
+ eap->nextcmd = check_nextcmd(p);
+ return;
+ }
+
+ // Get the function name. There are these situations:
+ // func function name
+ // "name" == func, "fudi.fd_dict" == NULL
+ // dict.func new dictionary entry
+ // "name" == NULL, "fudi.fd_dict" set,
+ // "fudi.fd_di" == NULL, "fudi.fd_newkey" == func
+ // dict.func existing dict entry with a Funcref
+ // "name" == func, "fudi.fd_dict" set,
+ // "fudi.fd_di" set, "fudi.fd_newkey" == NULL
+ // dict.func existing dict entry that's not a Funcref
+ // "name" == NULL, "fudi.fd_dict" set,
+ // "fudi.fd_di" set, "fudi.fd_newkey" == NULL
+ // s:func script-local function name
+ // g:func global function name, same as "func"
+ p = eap->arg;
+ name = trans_function_name(&p, eap->skip, TFN_NO_AUTOLOAD, &fudi, NULL);
+ paren = (vim_strchr(p, '(') != NULL);
+ if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip) {
+ /*
+ * Return on an invalid expression in braces, unless the expression
+ * evaluation has been cancelled due to an aborting error, an
+ * interrupt, or an exception.
+ */
+ if (!aborting()) {
+ if (fudi.fd_newkey != NULL) {
+ EMSG2(_(e_dictkey), fudi.fd_newkey);
+ }
+ xfree(fudi.fd_newkey);
+ return;
+ } else
+ eap->skip = TRUE;
+ }
+
+ /* An error in a function call during evaluation of an expression in magic
+ * braces should not cause the function not to be defined. */
+ saved_did_emsg = did_emsg;
+ did_emsg = FALSE;
+
+ //
+ // ":function func" with only function name: list function.
+ // If bang is given:
+ // - include "!" in function head
+ // - exclude line numbers from function body
+ //
+ if (!paren) {
+ if (!ends_excmd(*skipwhite(p))) {
+ EMSG(_(e_trailing));
+ goto ret_free;
+ }
+ eap->nextcmd = check_nextcmd(p);
+ if (eap->nextcmd != NULL)
+ *p = NUL;
+ if (!eap->skip && !got_int) {
+ fp = find_func(name);
+ if (fp != NULL) {
+ list_func_head(fp, !eap->forceit, eap->forceit);
+ for (int j = 0; j < fp->uf_lines.ga_len && !got_int; j++) {
+ if (FUNCLINE(fp, j) == NULL) {
+ continue;
+ }
+ msg_putchar('\n');
+ if (!eap->forceit) {
+ msg_outnum((long)j + 1);
+ if (j < 9) {
+ msg_putchar(' ');
+ }
+ if (j < 99) {
+ msg_putchar(' ');
+ }
+ }
+ msg_prt_line(FUNCLINE(fp, j), false);
+ ui_flush(); // show a line at a time
+ os_breakcheck();
+ }
+ if (!got_int) {
+ msg_putchar('\n');
+ msg_puts(eap->forceit ? "endfunction" : " endfunction");
+ }
+ } else
+ emsg_funcname(N_("E123: Undefined function: %s"), name);
+ }
+ goto ret_free;
+ }
+
+ /*
+ * ":function name(arg1, arg2)" Define function.
+ */
+ p = skipwhite(p);
+ if (*p != '(') {
+ if (!eap->skip) {
+ EMSG2(_("E124: Missing '(': %s"), eap->arg);
+ goto ret_free;
+ }
+ // attempt to continue by skipping some text
+ if (vim_strchr(p, '(') != NULL) {
+ p = vim_strchr(p, '(');
+ }
+ }
+ p = skipwhite(p + 1);
+
+ ga_init(&newargs, (int)sizeof(char_u *), 3);
+ ga_init(&newlines, (int)sizeof(char_u *), 3);
+
+ if (!eap->skip) {
+ /* Check the name of the function. Unless it's a dictionary function
+ * (that we are overwriting). */
+ if (name != NULL)
+ arg = name;
+ else
+ arg = fudi.fd_newkey;
+ if (arg != NULL && (fudi.fd_di == NULL || !tv_is_func(fudi.fd_di->di_tv))) {
+ int j = (*arg == K_SPECIAL) ? 3 : 0;
+ while (arg[j] != NUL && (j == 0 ? eval_isnamec1(arg[j])
+ : eval_isnamec(arg[j])))
+ ++j;
+ if (arg[j] != NUL)
+ emsg_funcname((char *)e_invarg2, arg);
+ }
+ // Disallow using the g: dict.
+ if (fudi.fd_dict != NULL && fudi.fd_dict->dv_scope == VAR_DEF_SCOPE) {
+ EMSG(_("E862: Cannot use g: here"));
+ }
+ }
+
+ if (get_function_args(&p, ')', &newargs, &varargs, eap->skip) == FAIL) {
+ goto errret_2;
+ }
+
+ if (KeyTyped && ui_has(kUICmdline)) {
+ show_block = true;
+ ui_ext_cmdline_block_append(0, (const char *)eap->cmd);
+ }
+
+ // find extra arguments "range", "dict", "abort" and "closure"
+ for (;; ) {
+ p = skipwhite(p);
+ if (STRNCMP(p, "range", 5) == 0) {
+ flags |= FC_RANGE;
+ p += 5;
+ } else if (STRNCMP(p, "dict", 4) == 0) {
+ flags |= FC_DICT;
+ p += 4;
+ } else if (STRNCMP(p, "abort", 5) == 0) {
+ flags |= FC_ABORT;
+ p += 5;
+ } else if (STRNCMP(p, "closure", 7) == 0) {
+ flags |= FC_CLOSURE;
+ p += 7;
+ if (current_funccal == NULL) {
+ emsg_funcname(N_
+ ("E932: Closure function should not be at top level: %s"),
+ name == NULL ? (char_u *)"" : name);
+ goto erret;
+ }
+ } else {
+ break;
+ }
+ }
+
+ /* When there is a line break use what follows for the function body.
+ * Makes 'exe "func Test()\n...\nendfunc"' work. */
+ if (*p == '\n') {
+ line_arg = p + 1;
+ } else if (*p != NUL && *p != '"' && !eap->skip && !did_emsg) {
+ EMSG(_(e_trailing));
+ }
+
+ /*
+ * Read the body of the function, until ":endfunction" is found.
+ */
+ if (KeyTyped) {
+ /* Check if the function already exists, don't let the user type the
+ * whole function before telling him it doesn't work! For a script we
+ * need to skip the body to be able to find what follows. */
+ if (!eap->skip && !eap->forceit) {
+ if (fudi.fd_dict != NULL && fudi.fd_newkey == NULL)
+ EMSG(_(e_funcdict));
+ else if (name != NULL && find_func(name) != NULL)
+ emsg_funcname(e_funcexts, name);
+ }
+
+ if (!eap->skip && did_emsg)
+ goto erret;
+
+ if (!ui_has(kUICmdline)) {
+ msg_putchar('\n'); // don't overwrite the function name
+ }
+ cmdline_row = msg_row;
+ }
+
+ // Save the starting line number.
+ sourcing_lnum_top = sourcing_lnum;
+
+ indent = 2;
+ nesting = 0;
+ for (;; ) {
+ if (KeyTyped) {
+ msg_scroll = true;
+ saved_wait_return = false;
+ }
+ need_wait_return = false;
+
+ if (line_arg != NULL) {
+ // Use eap->arg, split up in parts by line breaks.
+ theline = line_arg;
+ p = vim_strchr(theline, '\n');
+ if (p == NULL)
+ line_arg += STRLEN(line_arg);
+ else {
+ *p = NUL;
+ line_arg = p + 1;
+ }
+ } else {
+ xfree(line_to_free);
+ if (eap->getline == NULL) {
+ theline = getcmdline(':', 0L, indent, do_concat);
+ } else {
+ theline = eap->getline(':', eap->cookie, indent, do_concat);
+ }
+ line_to_free = theline;
+ }
+ if (KeyTyped) {
+ lines_left = Rows - 1;
+ }
+ if (theline == NULL) {
+ EMSG(_("E126: Missing :endfunction"));
+ goto erret;
+ }
+ if (show_block) {
+ assert(indent >= 0);
+ ui_ext_cmdline_block_append((size_t)indent, (const char *)theline);
+ }
+
+ // Detect line continuation: sourcing_lnum increased more than one.
+ sourcing_lnum_off = get_sourced_lnum(eap->getline, eap->cookie);
+ if (sourcing_lnum < sourcing_lnum_off) {
+ sourcing_lnum_off -= sourcing_lnum;
+ } else {
+ sourcing_lnum_off = 0;
+ }
+
+ if (skip_until != NULL) {
+ // Don't check for ":endfunc" between
+ // * ":append" and "."
+ // * ":python <<EOF" and "EOF"
+ // * ":let {var-name} =<< [trim] {marker}" and "{marker}"
+ if (heredoc_trimmed == NULL
+ || (is_heredoc && skipwhite(theline) == theline)
+ || STRNCMP(theline, heredoc_trimmed,
+ STRLEN(heredoc_trimmed)) == 0) {
+ if (heredoc_trimmed == NULL) {
+ p = theline;
+ } else if (is_heredoc) {
+ p = skipwhite(theline) == theline
+ ? theline : theline + STRLEN(heredoc_trimmed);
+ } else {
+ p = theline + STRLEN(heredoc_trimmed);
+ }
+ if (STRCMP(p, skip_until) == 0) {
+ XFREE_CLEAR(skip_until);
+ XFREE_CLEAR(heredoc_trimmed);
+ do_concat = true;
+ is_heredoc = false;
+ }
+ }
+ } else {
+ // skip ':' and blanks
+ for (p = theline; ascii_iswhite(*p) || *p == ':'; p++) {
+ }
+
+ // Check for "endfunction".
+ if (checkforcmd(&p, "endfunction", 4) && nesting-- == 0) {
+ if (*p == '!') {
+ p++;
+ }
+ char_u *nextcmd = NULL;
+ if (*p == '|') {
+ nextcmd = p + 1;
+ } else if (line_arg != NULL && *skipwhite(line_arg) != NUL) {
+ nextcmd = line_arg;
+ } else if (*p != NUL && *p != '"' && p_verbose > 0) {
+ give_warning2((char_u *)_("W22: Text found after :endfunction: %s"),
+ p, true);
+ }
+ if (nextcmd != NULL) {
+ // Another command follows. If the line came from "eap" we
+ // can simply point into it, otherwise we need to change
+ // "eap->cmdlinep".
+ eap->nextcmd = nextcmd;
+ if (line_to_free != NULL) {
+ xfree(*eap->cmdlinep);
+ *eap->cmdlinep = line_to_free;
+ line_to_free = NULL;
+ }
+ }
+ break;
+ }
+
+ /* Increase indent inside "if", "while", "for" and "try", decrease
+ * at "end". */
+ if (indent > 2 && STRNCMP(p, "end", 3) == 0)
+ indent -= 2;
+ else if (STRNCMP(p, "if", 2) == 0
+ || STRNCMP(p, "wh", 2) == 0
+ || STRNCMP(p, "for", 3) == 0
+ || STRNCMP(p, "try", 3) == 0)
+ indent += 2;
+
+ // Check for defining a function inside this function.
+ if (checkforcmd(&p, "function", 2)) {
+ if (*p == '!') {
+ p = skipwhite(p + 1);
+ }
+ p += eval_fname_script((const char *)p);
+ xfree(trans_function_name(&p, true, 0, NULL, NULL));
+ if (*skipwhite(p) == '(') {
+ nesting++;
+ indent += 2;
+ }
+ }
+
+ // Check for ":append", ":change", ":insert".
+ p = skip_range(p, NULL);
+ if ((p[0] == 'a' && (!ASCII_ISALPHA(p[1]) || p[1] == 'p'))
+ || (p[0] == 'c'
+ && (!ASCII_ISALPHA(p[1])
+ || (p[1] == 'h' && (!ASCII_ISALPHA(p[2])
+ || (p[2] == 'a'
+ && (STRNCMP(&p[3], "nge", 3) != 0
+ || !ASCII_ISALPHA(p[6])))))))
+ || (p[0] == 'i'
+ && (!ASCII_ISALPHA(p[1]) || (p[1] == 'n'
+ && (!ASCII_ISALPHA(p[2])
+ || (p[2] == 's')))))) {
+ skip_until = vim_strsave((char_u *)".");
+ }
+
+ // heredoc: Check for ":python <<EOF", ":lua <<EOF", etc.
+ arg = skipwhite(skiptowhite(p));
+ if (arg[0] == '<' && arg[1] =='<'
+ && ((p[0] == 'p' && p[1] == 'y'
+ && (!ASCII_ISALNUM(p[2]) || p[2] == 't'
+ || ((p[2] == '3' || p[2] == 'x')
+ && !ASCII_ISALPHA(p[3]))))
+ || (p[0] == 'p' && p[1] == 'e'
+ && (!ASCII_ISALPHA(p[2]) || p[2] == 'r'))
+ || (p[0] == 't' && p[1] == 'c'
+ && (!ASCII_ISALPHA(p[2]) || p[2] == 'l'))
+ || (p[0] == 'l' && p[1] == 'u' && p[2] == 'a'
+ && !ASCII_ISALPHA(p[3]))
+ || (p[0] == 'r' && p[1] == 'u' && p[2] == 'b'
+ && (!ASCII_ISALPHA(p[3]) || p[3] == 'y'))
+ || (p[0] == 'm' && p[1] == 'z'
+ && (!ASCII_ISALPHA(p[2]) || p[2] == 's')))) {
+ // ":python <<" continues until a dot, like ":append"
+ p = skipwhite(arg + 2);
+ if (*p == NUL)
+ skip_until = vim_strsave((char_u *)".");
+ else
+ skip_until = vim_strsave(p);
+ }
+
+ // Check for ":let v =<< [trim] EOF"
+ // and ":let [a, b] =<< [trim] EOF"
+ arg = skipwhite(skiptowhite(p));
+ if (*arg == '[') {
+ arg = vim_strchr(arg, ']');
+ }
+ if (arg != NULL) {
+ arg = skipwhite(skiptowhite(arg));
+ if (arg[0] == '='
+ && arg[1] == '<'
+ && arg[2] =='<'
+ && (p[0] == 'l'
+ && p[1] == 'e'
+ && (!ASCII_ISALNUM(p[2])
+ || (p[2] == 't' && !ASCII_ISALNUM(p[3]))))) {
+ p = skipwhite(arg + 3);
+ if (STRNCMP(p, "trim", 4) == 0) {
+ // Ignore leading white space.
+ p = skipwhite(p + 4);
+ heredoc_trimmed =
+ vim_strnsave(theline, (int)(skipwhite(theline) - theline));
+ }
+ skip_until = vim_strnsave(p, (int)(skiptowhite(p) - p));
+ do_concat = false;
+ is_heredoc = true;
+ }
+ }
+ }
+
+ // Add the line to the function.
+ ga_grow(&newlines, 1 + sourcing_lnum_off);
+
+ /* Copy the line to newly allocated memory. get_one_sourceline()
+ * allocates 250 bytes per line, this saves 80% on average. The cost
+ * is an extra alloc/free. */
+ p = vim_strsave(theline);
+ ((char_u **)(newlines.ga_data))[newlines.ga_len++] = p;
+
+ /* Add NULL lines for continuation lines, so that the line count is
+ * equal to the index in the growarray. */
+ while (sourcing_lnum_off-- > 0)
+ ((char_u **)(newlines.ga_data))[newlines.ga_len++] = NULL;
+
+ // Check for end of eap->arg.
+ if (line_arg != NULL && *line_arg == NUL) {
+ line_arg = NULL;
+ }
+ }
+
+ /* Don't define the function when skipping commands or when an error was
+ * detected. */
+ if (eap->skip || did_emsg)
+ goto erret;
+
+ /*
+ * If there are no errors, add the function
+ */
+ if (fudi.fd_dict == NULL) {
+ v = find_var((const char *)name, STRLEN(name), &ht, false);
+ if (v != NULL && v->di_tv.v_type == VAR_FUNC) {
+ emsg_funcname(N_("E707: Function name conflicts with variable: %s"),
+ name);
+ goto erret;
+ }
+
+ fp = find_func(name);
+ if (fp != NULL) {
+ // Function can be replaced with "function!" and when sourcing the
+ // same script again, but only once.
+ if (!eap->forceit
+ && (fp->uf_script_ctx.sc_sid != current_sctx.sc_sid
+ || fp->uf_script_ctx.sc_seq == current_sctx.sc_seq)) {
+ emsg_funcname(e_funcexts, name);
+ goto erret;
+ }
+ if (fp->uf_calls > 0) {
+ emsg_funcname(N_("E127: Cannot redefine function %s: It is in use"),
+ name);
+ goto erret;
+ }
+ if (fp->uf_refcount > 1) {
+ // This function is referenced somewhere, don't redefine it but
+ // create a new one.
+ (fp->uf_refcount)--;
+ fp->uf_flags |= FC_REMOVED;
+ fp = NULL;
+ overwrite = true;
+ } else {
+ // redefine existing function
+ XFREE_CLEAR(name);
+ func_clear_items(fp);
+ fp->uf_profiling = false;
+ fp->uf_prof_initialized = false;
+ }
+ }
+ } else {
+ char numbuf[20];
+
+ fp = NULL;
+ if (fudi.fd_newkey == NULL && !eap->forceit) {
+ EMSG(_(e_funcdict));
+ goto erret;
+ }
+ if (fudi.fd_di == NULL) {
+ if (tv_check_lock(fudi.fd_dict->dv_lock, (const char *)eap->arg,
+ TV_CSTRING)) {
+ // Can't add a function to a locked dictionary
+ goto erret;
+ }
+ } else if (tv_check_lock(fudi.fd_di->di_tv.v_lock, (const char *)eap->arg,
+ TV_CSTRING)) {
+ // Can't change an existing function if it is locked
+ goto erret;
+ }
+
+ /* Give the function a sequential number. Can only be used with a
+ * Funcref! */
+ xfree(name);
+ sprintf(numbuf, "%d", ++func_nr);
+ name = vim_strsave((char_u *)numbuf);
+ }
+
+ if (fp == NULL) {
+ if (fudi.fd_dict == NULL && vim_strchr(name, AUTOLOAD_CHAR) != NULL) {
+ int slen, plen;
+ char_u *scriptname;
+
+ // Check that the autoload name matches the script name.
+ int j = FAIL;
+ if (sourcing_name != NULL) {
+ scriptname = (char_u *)autoload_name((const char *)name, STRLEN(name));
+ p = vim_strchr(scriptname, '/');
+ plen = (int)STRLEN(p);
+ slen = (int)STRLEN(sourcing_name);
+ if (slen > plen && fnamecmp(p,
+ sourcing_name + slen - plen) == 0)
+ j = OK;
+ xfree(scriptname);
+ }
+ if (j == FAIL) {
+ EMSG2(_(
+ "E746: Function name does not match script file name: %s"),
+ name);
+ goto erret;
+ }
+ }
+
+ fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1);
+
+ if (fudi.fd_dict != NULL) {
+ if (fudi.fd_di == NULL) {
+ // Add new dict entry
+ fudi.fd_di = tv_dict_item_alloc((const char *)fudi.fd_newkey);
+ if (tv_dict_add(fudi.fd_dict, fudi.fd_di) == FAIL) {
+ xfree(fudi.fd_di);
+ xfree(fp);
+ goto erret;
+ }
+ } else {
+ // Overwrite existing dict entry.
+ tv_clear(&fudi.fd_di->di_tv);
+ }
+ fudi.fd_di->di_tv.v_type = VAR_FUNC;
+ fudi.fd_di->di_tv.vval.v_string = vim_strsave(name);
+
+ // behave like "dict" was used
+ flags |= FC_DICT;
+ }
+
+ // insert the new function in the function list
+ STRCPY(fp->uf_name, name);
+ if (overwrite) {
+ hi = hash_find(&func_hashtab, name);
+ hi->hi_key = UF2HIKEY(fp);
+ } else if (hash_add(&func_hashtab, UF2HIKEY(fp)) == FAIL) {
+ xfree(fp);
+ goto erret;
+ }
+ fp->uf_refcount = 1;
+ }
+ fp->uf_args = newargs;
+ fp->uf_lines = newlines;
+ if ((flags & FC_CLOSURE) != 0) {
+ register_closure(fp);
+ } else {
+ fp->uf_scoped = NULL;
+ }
+ if (prof_def_func()) {
+ func_do_profile(fp);
+ }
+ fp->uf_varargs = varargs;
+ if (sandbox) {
+ flags |= FC_SANDBOX;
+ }
+ fp->uf_flags = flags;
+ fp->uf_calls = 0;
+ fp->uf_script_ctx = current_sctx;
+ fp->uf_script_ctx.sc_lnum += sourcing_lnum_top;
+
+ goto ret_free;
+
+erret:
+ ga_clear_strings(&newargs);
+errret_2:
+ ga_clear_strings(&newlines);
+ret_free:
+ xfree(skip_until);
+ xfree(line_to_free);
+ xfree(fudi.fd_newkey);
+ xfree(name);
+ did_emsg |= saved_did_emsg;
+ need_wait_return |= saved_wait_return;
+ if (show_block) {
+ ui_ext_cmdline_block_leave();
+ }
+} // 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.
+ */
+int eval_fname_script(const char *const p)
+{
+ // Use mb_strnicmp() because in Turkish comparing the "I" may not work with
+ // the standard library function.
+ if (p[0] == '<'
+ && (mb_strnicmp((char_u *)p + 1, (char_u *)"SID>", 4) == 0
+ || mb_strnicmp((char_u *)p + 1, (char_u *)"SNR>", 4) == 0)) {
+ return 5;
+ }
+ if (p[0] == 's' && p[1] == ':') {
+ return 2;
+ }
+ return 0;
+}
+
+bool translated_function_exists(const char *name)
+{
+ if (builtin_function(name, -1)) {
+ return find_internal_func((char *)name) != NULL;
+ }
+ return find_func((const char_u *)name) != NULL;
+}
+
+/// Check whether function with the given name exists
+///
+/// @param[in] name Function name.
+/// @param[in] no_deref Whether to dereference a Funcref.
+///
+/// @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;
+ bool n = false;
+ int flag = TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD;
+
+ if (no_deref) {
+ flag |= TFN_NO_DEREF;
+ }
+ char *const p = (char *)trans_function_name((char_u **)&nm, false, flag, NULL,
+ NULL);
+ nm = skipwhite(nm);
+
+ /* Only accept "funcname", "funcname ", "funcname (..." and
+ * "funcname(...", not "funcname!...". */
+ if (p != NULL && (*nm == NUL || *nm == '(')) {
+ n = translated_function_exists(p);
+ }
+ xfree(p);
+ return n;
+}
+
+/*
+ * 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;
+ static hashitem_T *hi;
+ ufunc_T *fp;
+
+ if (idx == 0) {
+ done = 0;
+ hi = func_hashtab.ht_array;
+ }
+ assert(hi);
+ if (done < func_hashtab.ht_used) {
+ if (done++ > 0)
+ ++hi;
+ while (HASHITEM_EMPTY(hi))
+ ++hi;
+ fp = HI2UF(hi);
+
+ if ((fp->uf_flags & FC_DICT)
+ || STRNCMP(fp->uf_name, "<lambda>", 8) == 0) {
+ return (char_u *)""; // don't show dict and lambda functions
+ }
+
+ if (STRLEN(fp->uf_name) + 4 >= IOSIZE) {
+ return fp->uf_name; // Prevent overflow.
+ }
+
+ cat_func_name(IObuff, fp);
+ if (xp->xp_context != EXPAND_USER_FUNC) {
+ STRCAT(IObuff, "(");
+ if (!fp->uf_varargs && GA_EMPTY(&fp->uf_args))
+ STRCAT(IObuff, ")");
+ }
+ return IObuff;
+ }
+ return NULL;
+}
+
+/// ":delfunction {name}"
+void ex_delfunction(exarg_T *eap)
+{
+ ufunc_T *fp = NULL;
+ char_u *p;
+ char_u *name;
+ funcdict_T fudi;
+
+ p = eap->arg;
+ name = trans_function_name(&p, eap->skip, 0, &fudi, NULL);
+ xfree(fudi.fd_newkey);
+ if (name == NULL) {
+ if (fudi.fd_dict != NULL && !eap->skip)
+ EMSG(_(e_funcref));
+ return;
+ }
+ if (!ends_excmd(*skipwhite(p))) {
+ xfree(name);
+ EMSG(_(e_trailing));
+ return;
+ }
+ eap->nextcmd = check_nextcmd(p);
+ if (eap->nextcmd != NULL)
+ *p = NUL;
+
+ if (!eap->skip)
+ fp = find_func(name);
+ xfree(name);
+
+ if (!eap->skip) {
+ if (fp == NULL) {
+ if (!eap->forceit) {
+ EMSG2(_(e_nofunc), eap->arg);
+ }
+ return;
+ }
+ if (fp->uf_calls > 0) {
+ EMSG2(_("E131: Cannot delete function %s: It is in use"), eap->arg);
+ return;
+ }
+ // check `uf_refcount > 2` because deleting a function should also reduce
+ // the reference count, and 1 is the initial refcount.
+ if (fp->uf_refcount > 2) {
+ EMSG2(_("Cannot delete function %s: It is being used internally"),
+ eap->arg);
+ return;
+ }
+
+ if (fudi.fd_dict != NULL) {
+ // Delete the dict item that refers to the function, it will
+ // invoke func_unref() and possibly delete the function.
+ tv_dict_item_remove(fudi.fd_dict, fudi.fd_di);
+ } else {
+ // A normal function (not a numbered function or lambda) has a
+ // refcount of 1 for the entry in the hashtable. When deleting
+ // it and the refcount is more than one, it should be kept.
+ // A numbered function or lambda should be kept if the refcount is
+ // one or more.
+ if (fp->uf_refcount > (func_name_refcount(fp->uf_name) ? 0 : 1)) {
+ // Function is still referenced somewhere. Don't free it but
+ // do remove it from the hashtable.
+ if (func_remove(fp)) {
+ fp->uf_refcount--;
+ }
+ fp->uf_flags |= FC_DELETED;
+ } else {
+ func_clear_free(fp, false);
+ }
+ }
+ }
+}
+
+/*
+ * Unreference a Function: decrement the reference count and free it when it
+ * becomes zero.
+ */
+void func_unref(char_u *name)
+{
+ ufunc_T *fp = NULL;
+
+ if (name == NULL || !func_name_refcount(name)) {
+ return;
+ }
+
+ fp = find_func(name);
+ if (fp == NULL && isdigit(*name)) {
+#ifdef EXITFREE
+ if (!entered_free_all_mem) {
+ internal_error("func_unref()");
+ abort();
+ }
+#else
+ internal_error("func_unref()");
+ abort();
+#endif
+ }
+ func_ptr_unref(fp);
+}
+
+/// Unreference a Function: decrement the reference count and free it when it
+/// becomes zero.
+/// Unreference user function, freeing it if needed
+///
+/// Decrements the reference count and frees when it becomes zero.
+///
+/// @param fp Function to unreference.
+void func_ptr_unref(ufunc_T *fp)
+{
+ if (fp != NULL && --fp->uf_refcount <= 0) {
+ // Only delete it when it's not being used. Otherwise it's done
+ // when "uf_calls" becomes zero.
+ if (fp->uf_calls == 0) {
+ func_clear_free(fp, false);
+ }
+ }
+}
+
+/// Count a reference to a Function.
+void func_ref(char_u *name)
+{
+ ufunc_T *fp;
+
+ if (name == NULL || !func_name_refcount(name)) {
+ return;
+ }
+ fp = find_func(name);
+ if (fp != NULL) {
+ (fp->uf_refcount)++;
+ } else if (isdigit(*name)) {
+ // Only give an error for a numbered function.
+ // Fail silently, when named or lambda function isn't found.
+ internal_error("func_ref()");
+ }
+}
+
+/// Count a reference to a Function.
+void func_ptr_ref(ufunc_T *fp)
+{
+ if (fp != NULL) {
+ (fp->uf_refcount)++;
+ }
+}
+
+/// Check whether funccall is still referenced outside
+///
+/// It is supposed to be referenced if either it is referenced itself or if l:,
+/// a: or a:000 are referenced as all these are statically allocated within
+/// funccall structure.
+static inline bool fc_referenced(const funccall_T *const fc)
+ FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+ FUNC_ATTR_NONNULL_ALL
+{
+ return ((fc->l_varlist.lv_refcount // NOLINT(runtime/deprecated)
+ != DO_NOT_FREE_CNT)
+ || fc->l_vars.dv_refcount != DO_NOT_FREE_CNT
+ || fc->l_avars.dv_refcount != DO_NOT_FREE_CNT
+ || fc->fc_refcount > 0);
+}
+
+/// @return true if items in "fc" do not have "copyID". That means they are not
+/// referenced from anywhere that is in use.
+static int can_free_funccal(funccall_T *fc, int copyID)
+{
+ return fc->l_varlist.lv_copyID != copyID
+ && fc->l_vars.dv_copyID != copyID
+ && fc->l_avars.dv_copyID != copyID
+ && fc->fc_copyID != copyID;
+}
+
+/*
+ * ":return [expr]"
+ */
+void ex_return(exarg_T *eap)
+{
+ char_u *arg = eap->arg;
+ typval_T rettv;
+ int returning = FALSE;
+
+ if (current_funccal == NULL) {
+ EMSG(_("E133: :return not inside a function"));
+ return;
+ }
+
+ if (eap->skip)
+ ++emsg_skip;
+
+ eap->nextcmd = NULL;
+ if ((*arg != NUL && *arg != '|' && *arg != '\n')
+ && eval0(arg, &rettv, &eap->nextcmd, !eap->skip) != FAIL) {
+ if (!eap->skip) {
+ returning = do_return(eap, false, true, &rettv);
+ } else {
+ tv_clear(&rettv);
+ }
+ } else if (!eap->skip) { // It's safer to return also on error.
+ // In return statement, cause_abort should be force_abort.
+ update_force_abort();
+
+ // Return unless the expression evaluation has been cancelled due to an
+ // aborting error, an interrupt, or an exception.
+ if (!aborting()) {
+ returning = do_return(eap, false, true, NULL);
+ }
+ }
+
+ /* When skipping or the return gets pending, advance to the next command
+ * in this line (!returning). Otherwise, ignore the rest of the line.
+ * Following lines will be ignored by get_func_line(). */
+ if (returning) {
+ eap->nextcmd = NULL;
+ } else if (eap->nextcmd == NULL) { // no argument
+ eap->nextcmd = check_nextcmd(arg);
+ }
+
+ if (eap->skip)
+ --emsg_skip;
+}
+
+// TODO(ZyX-I): move to eval/ex_cmds
+
+/*
+ * ":1,25call func(arg1, arg2)" function call.
+ */
+void ex_call(exarg_T *eap)
+{
+ char_u *arg = eap->arg;
+ char_u *startarg;
+ char_u *name;
+ char_u *tofree;
+ int len;
+ typval_T rettv;
+ linenr_T lnum;
+ int doesrange;
+ bool failed = false;
+ funcdict_T fudi;
+ partial_T *partial = NULL;
+
+ if (eap->skip) {
+ // trans_function_name() doesn't work well when skipping, use eval0()
+ // instead to skip to any following command, e.g. for:
+ // :if 0 | call dict.foo().bar() | endif.
+ emsg_skip++;
+ if (eval0(eap->arg, &rettv, &eap->nextcmd, false) != FAIL) {
+ tv_clear(&rettv);
+ }
+ emsg_skip--;
+ return;
+ }
+
+ tofree = trans_function_name(&arg, false, TFN_INT, &fudi, &partial);
+ if (fudi.fd_newkey != NULL) {
+ // Still need to give an error message for missing key.
+ EMSG2(_(e_dictkey), fudi.fd_newkey);
+ xfree(fudi.fd_newkey);
+ }
+ if (tofree == NULL) {
+ return;
+ }
+
+ // Increase refcount on dictionary, it could get deleted when evaluating
+ // the arguments.
+ if (fudi.fd_dict != NULL) {
+ fudi.fd_dict->dv_refcount++;
+ }
+
+ // If it is the name of a variable of type VAR_FUNC or VAR_PARTIAL use its
+ // contents. For VAR_PARTIAL get its partial, unless we already have one
+ // from trans_function_name().
+ len = (int)STRLEN(tofree);
+ name = deref_func_name((const char *)tofree, &len,
+ partial != NULL ? NULL : &partial, false);
+
+ // Skip white space to allow ":call func ()". Not good, but required for
+ // backward compatibility.
+ startarg = skipwhite(arg);
+ rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this.
+
+ if (*startarg != '(') {
+ EMSG2(_("E107: Missing parentheses: %s"), eap->arg);
+ goto end;
+ }
+
+ lnum = eap->line1;
+ for (; lnum <= eap->line2; lnum++) {
+ if (eap->addr_count > 0) { // -V560
+ if (lnum > curbuf->b_ml.ml_line_count) {
+ // If the function deleted lines or switched to another buffer
+ // the line number may become invalid.
+ EMSG(_(e_invrange));
+ break;
+ }
+ curwin->w_cursor.lnum = lnum;
+ curwin->w_cursor.col = 0;
+ curwin->w_cursor.coladd = 0;
+ }
+ arg = startarg;
+ if (get_func_tv(name, (int)STRLEN(name), &rettv, &arg,
+ eap->line1, eap->line2, &doesrange,
+ true, partial, fudi.fd_dict) == FAIL) {
+ failed = true;
+ break;
+ }
+
+ // Handle a function returning a Funcref, Dictionary or List.
+ if (handle_subscript((const char **)&arg, &rettv, true, true)
+ == FAIL) {
+ failed = true;
+ break;
+ }
+
+ tv_clear(&rettv);
+ if (doesrange) {
+ break;
+ }
+
+ // Stop when immediately aborting on error, or when an interrupt
+ // occurred or an exception was thrown but not caught.
+ // get_func_tv() returned OK, so that the check for trailing
+ // characters below is executed.
+ if (aborting()) {
+ break;
+ }
+ }
+
+ // When inside :try we need to check for following "| catch".
+ if (!failed || eap->cstack->cs_trylevel > 0) {
+ // Check for trailing illegal characters and a following command.
+ if (!ends_excmd(*arg)) {
+ emsg_severe = TRUE;
+ EMSG(_(e_trailing));
+ } else {
+ eap->nextcmd = check_nextcmd(arg);
+ }
+ }
+
+end:
+ tv_dict_unref(fudi.fd_dict);
+ 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.
+ */
+int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv)
+{
+ int idx;
+ cstack_T *const cstack = eap->cstack;
+
+ if (reanimate) {
+ // Undo the return.
+ current_funccal->returned = false;
+ }
+
+ /*
+ * Cleanup (and inactivate) 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;
+
+ if (!is_cmd && !reanimate)
+ /* A pending return again gets pending. "rettv" points to an
+ * allocated variable with the rettv of the original ":return"'s
+ * argument if present or is NULL else. */
+ cstack->cs_rettv[idx] = rettv;
+ else {
+ /* When undoing a return in order to make it pending, get the stored
+ * return rettv. */
+ if (reanimate) {
+ assert(current_funccal->rettv);
+ rettv = current_funccal->rettv;
+ }
+
+ if (rettv != NULL) {
+ // Store the value of the pending return.
+ cstack->cs_rettv[idx] = xcalloc(1, sizeof(typval_T));
+ *(typval_T *)cstack->cs_rettv[idx] = *(typval_T *)rettv;
+ } else
+ cstack->cs_rettv[idx] = NULL;
+
+ if (reanimate) {
+ /* The pending return value could be overwritten by a ":return"
+ * without argument in a finally clause; reset the default
+ * return value. */
+ current_funccal->rettv->v_type = VAR_NUMBER;
+ current_funccal->rettv->vval.v_number = 0;
+ }
+ }
+ report_make_pending(CSTP_RETURN, rettv);
+ } else {
+ current_funccal->returned = TRUE;
+
+ /* If the return is carried out now, store the return value. For
+ * a return immediately after reanimation, the value is already
+ * there. */
+ if (!reanimate && rettv != NULL) {
+ tv_clear(current_funccal->rettv);
+ *current_funccal->rettv = *(typval_T *)rettv;
+ if (!is_cmd)
+ xfree(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.
+ */
+char_u *get_return_cmd(void *rettv)
+{
+ char_u *s = NULL;
+ char_u *tofree = NULL;
+
+ if (rettv != NULL) {
+ tofree = s = (char_u *) encode_tv2echo((typval_T *) rettv, NULL);
+ }
+ if (s == NULL) {
+ s = (char_u *)"";
+ }
+
+ STRCPY(IObuff, ":return ");
+ STRLCPY(IObuff + 8, s, IOSIZE - 8);
+ if (STRLEN(s) + 8 >= IOSIZE)
+ STRCPY(IObuff + IOSIZE - 4, "...");
+ xfree(tofree);
+ 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.
+ */
+char_u *get_func_line(int c, void *cookie, int indent, bool do_concat)
+{
+ funccall_T *fcp = (funccall_T *)cookie;
+ ufunc_T *fp = fcp->func;
+ char_u *retval;
+ garray_T *gap; // growarray with function lines
+
+ // If breakpoints have been added/deleted need to check for it.
+ if (fcp->dbg_tick != debug_tick) {
+ fcp->breakpoint = dbg_find_breakpoint(FALSE, fp->uf_name,
+ sourcing_lnum);
+ fcp->dbg_tick = debug_tick;
+ }
+ if (do_profiling == PROF_YES)
+ func_line_end(cookie);
+
+ gap = &fp->uf_lines;
+ if (((fp->uf_flags & FC_ABORT) && did_emsg && !aborted_in_try())
+ || fcp->returned) {
+ retval = NULL;
+ } else {
+ // Skip NULL lines (continuation lines).
+ while (fcp->linenr < gap->ga_len
+ && ((char_u **)(gap->ga_data))[fcp->linenr] == NULL) {
+ fcp->linenr++;
+ }
+ if (fcp->linenr >= gap->ga_len) {
+ retval = NULL;
+ } else {
+ retval = vim_strsave(((char_u **)(gap->ga_data))[fcp->linenr++]);
+ sourcing_lnum = fcp->linenr;
+ if (do_profiling == PROF_YES)
+ func_line_start(cookie);
+ }
+ }
+
+ // Did we encounter a breakpoint?
+ if (fcp->breakpoint != 0 && fcp->breakpoint <= sourcing_lnum) {
+ dbg_breakpoint(fp->uf_name, sourcing_lnum);
+ // Find next breakpoint.
+ fcp->breakpoint = dbg_find_breakpoint(false, fp->uf_name,
+ sourcing_lnum);
+ fcp->dbg_tick = debug_tick;
+ }
+
+ 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".
+ */
+int func_has_ended(void *cookie)
+{
+ funccall_T *fcp = (funccall_T *)cookie;
+
+ /* Ignore the "abort" flag if the abortion behavior has been changed due to
+ * an error inside a try conditional. */
+ return ((fcp->func->uf_flags & FC_ABORT) && did_emsg && !aborted_in_try())
+ || fcp->returned;
+}
+
+/*
+ * 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;
+}
+
+/// Turn "dict.Func" into a partial for "Func" bound to "dict".
+/// Changes "rettv" in-place.
+void make_partial(dict_T *const selfdict, typval_T *const rettv)
+{
+ char_u *fname;
+ char_u *tofree = NULL;
+ ufunc_T *fp;
+ char_u fname_buf[FLEN_FIXED + 1];
+ int error;
+
+ if (rettv->v_type == VAR_PARTIAL && rettv->vval.v_partial->pt_func != NULL) {
+ fp = rettv->vval.v_partial->pt_func;
+ } else {
+ fname = rettv->v_type == VAR_FUNC || rettv->v_type == VAR_STRING
+ ? rettv->vval.v_string
+ : rettv->vval.v_partial->pt_name;
+ // Translate "s:func" to the stored function name.
+ fname = fname_trans_sid(fname, fname_buf, &tofree, &error);
+ fp = find_func(fname);
+ xfree(tofree);
+ }
+
+ // Turn "dict.Func" into a partial for "Func" with "dict".
+ if (fp != NULL && (fp->uf_flags & FC_DICT)) {
+ partial_T *pt = (partial_T *)xcalloc(1, sizeof(partial_T));
+ pt->pt_refcount = 1;
+ pt->pt_dict = selfdict;
+ (selfdict->dv_refcount)++;
+ pt->pt_auto = true;
+ if (rettv->v_type == VAR_FUNC || rettv->v_type == VAR_STRING) {
+ // Just a function: Take over the function name and use selfdict.
+ pt->pt_name = rettv->vval.v_string;
+ } else {
+ partial_T *ret_pt = rettv->vval.v_partial;
+ int i;
+
+ // Partial: copy the function name, use selfdict and copy
+ // args. Can't take over name or args, the partial might
+ // be referenced elsewhere.
+ if (ret_pt->pt_name != NULL) {
+ pt->pt_name = vim_strsave(ret_pt->pt_name);
+ func_ref(pt->pt_name);
+ } else {
+ pt->pt_func = ret_pt->pt_func;
+ func_ptr_ref(pt->pt_func);
+ }
+ if (ret_pt->pt_argc > 0) {
+ size_t arg_size = sizeof(typval_T) * ret_pt->pt_argc;
+ pt->pt_argv = (typval_T *)xmalloc(arg_size);
+ pt->pt_argc = ret_pt->pt_argc;
+ for (i = 0; i < pt->pt_argc; i++) {
+ tv_copy(&ret_pt->pt_argv[i], &pt->pt_argv[i]);
+ }
+ }
+ partial_unref(ret_pt);
+ }
+ rettv->v_type = VAR_PARTIAL;
+ rettv->vval.v_partial = pt;
+ }
+}
+
+/*
+ * 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.
+ */
+linenr_T *func_breakpoint(void *cookie)
+{
+ return &((funccall_T *)cookie)->breakpoint;
+}
+
+/*
+ * 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.
+ */
+int func_level(void *cookie)
+{
+ return ((funccall_T *)cookie)->level;
+}
+
+/*
+ * Return TRUE when a function was ended by a ":return" command.
+ */
+int current_func_returned(void)
+{
+ return current_funccal->returned;
+}
+
+bool free_unref_funccal(int copyID, int testing)
+{
+ bool did_free = false;
+ bool did_free_funccal = false;
+
+ for (funccall_T **pfc = &previous_funccal; *pfc != NULL;) {
+ if (can_free_funccal(*pfc, copyID)) {
+ funccall_T *fc = *pfc;
+ *pfc = fc->caller;
+ free_funccal(fc, true);
+ did_free = true;
+ did_free_funccal = true;
+ } else {
+ pfc = &(*pfc)->caller;
+ }
+ }
+ if (did_free_funccal) {
+ // When a funccal was freed some more items might be garbage
+ // collected, so run again.
+ (void)garbage_collect(testing);
+ }
+ return did_free;
+}
+
+// Get function call environment based on backtrace debug level
+funccall_T *get_funccal(void)
+{
+ funccall_T *funccal = current_funccal;
+ if (debug_backtrace_level > 0) {
+ for (int i = 0; i < debug_backtrace_level; i++) {
+ funccall_T *temp_funccal = funccal->caller;
+ if (temp_funccal) {
+ funccal = temp_funccal;
+ } else {
+ // backtrace level overflow. reset to max
+ debug_backtrace_level = i;
+ }
+ }
+ }
+
+ return funccal;
+}
+
+/// Return the hashtable used for local variables in the current funccal.
+/// Return NULL if there is no current funccal.
+hashtab_T *get_funccal_local_ht(void)
+{
+ if (current_funccal == NULL) {
+ return NULL;
+ }
+ return &get_funccal()->l_vars.dv_hashtab;
+}
+
+/// Return the l: scope variable.
+/// Return NULL if there is no current funccal.
+dictitem_T *get_funccal_local_var(void)
+{
+ if (current_funccal == NULL) {
+ return NULL;
+ }
+ 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.
+hashtab_T *get_funccal_args_ht(void)
+{
+ if (current_funccal == NULL) {
+ return NULL;
+ }
+ return &get_funccal()->l_avars.dv_hashtab;
+}
+
+/// Return the a: scope variable.
+/// Return NULL if there is no current funccal.
+dictitem_T *get_funccal_args_var(void)
+{
+ if (current_funccal == NULL) {
+ return NULL;
+ }
+ return (dictitem_T *)&current_funccal->l_avars_var;
+}
+
+/*
+ * List function variables, if there is a function.
+ */
+void list_func_vars(int *first)
+{
+ if (current_funccal != NULL) {
+ list_hashtable_vars(&current_funccal->l_vars.dv_hashtab, "l:", false,
+ first);
+ }
+}
+
+/// 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) {
+ return &current_funccal->l_vars;
+ }
+ return NULL;
+}
+
+/// Search hashitem in parent scope.
+hashitem_T *find_hi_in_scoped_ht(const char *name, hashtab_T **pht)
+{
+ if (current_funccal == NULL || current_funccal->func->uf_scoped == NULL) {
+ return NULL;
+ }
+
+ funccall_T *old_current_funccal = current_funccal;
+ hashitem_T *hi = NULL;
+ const size_t namelen = strlen(name);
+ const char *varname;
+
+ // Search in parent scope which is possible to reference from lambda
+ current_funccal = current_funccal->func->uf_scoped;
+ while (current_funccal != NULL) {
+ hashtab_T *ht = find_var_ht(name, namelen, &varname);
+ if (ht != NULL && *varname != NUL) {
+ hi = hash_find_len(ht, varname, namelen - (varname - name));
+ if (!HASHITEM_EMPTY(hi)) {
+ *pht = ht;
+ break;
+ }
+ }
+ if (current_funccal == current_funccal->func->uf_scoped) {
+ break;
+ }
+ current_funccal = current_funccal->func->uf_scoped;
+ }
+ current_funccal = old_current_funccal;
+
+ return hi;
+}
+
+/// Search variable in parent scope.
+dictitem_T *find_var_in_scoped_ht(const char *name, const size_t namelen,
+ int no_autoload)
+{
+ if (current_funccal == NULL || current_funccal->func->uf_scoped == NULL) {
+ return NULL;
+ }
+
+ dictitem_T *v = NULL;
+ funccall_T *old_current_funccal = current_funccal;
+ const char *varname;
+
+ // Search in parent scope which is possible to reference from lambda
+ current_funccal = current_funccal->func->uf_scoped;
+ while (current_funccal) {
+ hashtab_T *ht = find_var_ht(name, namelen, &varname);
+ if (ht != NULL && *varname != NUL) {
+ v = find_var_in_ht(ht, *name, varname,
+ namelen - (size_t)(varname - name), no_autoload);
+ if (v != NULL) {
+ break;
+ }
+ }
+ if (current_funccal == current_funccal->func->uf_scoped) {
+ break;
+ }
+ current_funccal = current_funccal->func->uf_scoped;
+ }
+ current_funccal = old_current_funccal;
+
+ return v;
+}
+
+/// Set "copyID + 1" in previous_funccal and callers.
+bool set_ref_in_previous_funccal(int copyID)
+{
+ bool abort = false;
+
+ for (funccall_T *fc = previous_funccal; fc != NULL; fc = fc->caller) {
+ abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID + 1, NULL);
+ abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID + 1, NULL);
+ }
+ return abort;
+}
+
+static bool set_ref_in_funccal(funccall_T *fc, int copyID)
+{
+ bool abort = false;
+
+ if (fc->fc_copyID != copyID) {
+ fc->fc_copyID = copyID;
+ abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL);
+ abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL);
+ abort = abort || set_ref_in_func(NULL, fc->func, copyID);
+ }
+ return abort;
+}
+
+/// Set "copyID" in all local vars and arguments in the call stack.
+bool set_ref_in_call_stack(int copyID)
+{
+ bool abort = false;
+
+ for (funccall_T *fc = current_funccal; fc != NULL; fc = fc->caller) {
+ abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL);
+ abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL);
+ }
+ return abort;
+}
+
+/// Set "copyID" in all functions available by name.
+bool set_ref_in_functions(int copyID)
+{
+ int todo;
+ hashitem_T *hi = NULL;
+ bool abort = false;
+ ufunc_T *fp;
+
+ todo = (int)func_hashtab.ht_used;
+ for (hi = func_hashtab.ht_array; todo > 0 && !got_int; hi++) {
+ if (!HASHITEM_EMPTY(hi)) {
+ todo--;
+ fp = HI2UF(hi);
+ if (!func_name_refcount(fp->uf_name)) {
+ abort = abort || set_ref_in_func(NULL, fp, copyID);
+ }
+ }
+ }
+ return abort;
+}
+
+/// Set "copyID" in all function arguments.
+bool set_ref_in_func_args(int copyID)
+{
+ bool abort = false;
+
+ for (int i = 0; i < funcargs.ga_len; i++) {
+ abort = abort || set_ref_in_item(((typval_T **)funcargs.ga_data)[i],
+ copyID, NULL, NULL);
+ }
+ return abort;
+}
+
+/// Mark all lists and dicts referenced through function "name" with "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.
+bool set_ref_in_func(char_u *name, ufunc_T *fp_in, int copyID)
+{
+ ufunc_T *fp = fp_in;
+ funccall_T *fc;
+ int error = ERROR_NONE;
+ char_u fname_buf[FLEN_FIXED + 1];
+ char_u *tofree = NULL;
+ char_u *fname;
+ bool abort = false;
+ if (name == NULL && fp_in == NULL) {
+ return false;
+ }
+
+ if (fp_in == NULL) {
+ fname = fname_trans_sid(name, fname_buf, &tofree, &error);
+ fp = find_func(fname);
+ }
+ if (fp != NULL) {
+ for (fc = fp->uf_scoped; fc != NULL; fc = fc->func->uf_scoped) {
+ abort = abort || set_ref_in_funccal(fc, copyID);
+ }
+ }
+ xfree(tofree);
+ return abort;
+}
diff --git a/src/nvim/eval/userfunc.h b/src/nvim/eval/userfunc.h
new file mode 100644
index 0000000000..ad8e071548
--- /dev/null
+++ b/src/nvim/eval/userfunc.h
@@ -0,0 +1,36 @@
+#ifndef NVIM_EVAL_USERFUNC_H
+#define NVIM_EVAL_USERFUNC_H
+
+#include "nvim/eval/typval.h"
+#include "nvim/ex_cmds_defs.h"
+
+///< Structure used by trans_function_name()
+typedef struct {
+ dict_T *fd_dict; ///< Dictionary used.
+ char_u *fd_newkey; ///< New key in "dict" in allocated memory.
+ dictitem_T *fd_di; ///< Dictionary item used.
+} funcdict_T;
+
+/// errors for when calling a function
+typedef enum {
+ ERROR_UNKNOWN = 0,
+ ERROR_TOOMANY,
+ ERROR_TOOFEW,
+ ERROR_SCRIPT,
+ ERROR_DICT,
+ ERROR_NONE,
+ ERROR_OTHER,
+ ERROR_BOTH,
+ ERROR_DELETED,
+} FnameTransError;
+
+typedef int (*ArgvFunc)(int current_argcount, typval_T *argv, int argskip,
+ int called_func_argcount);
+
+#define FUNCARG(fp, j) ((char_u **)(fp->uf_args.ga_data))[j]
+#define FUNCLINE(fp, j) ((char_u **)(fp->uf_lines.ga_data))[j]
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "eval/userfunc.h.generated.h"
+#endif
+#endif // NVIM_EVAL_USERFUNC_H
diff --git a/src/nvim/event/loop.c b/src/nvim/event/loop.c
index c2be472acd..e341513ae1 100644
--- a/src/nvim/event/loop.c
+++ b/src/nvim/event/loop.c
@@ -116,6 +116,20 @@ void loop_on_put(MultiQueue *queue, void *data)
uv_stop(&loop->uv);
}
+#if !defined(EXITFREE)
+static void loop_walk_cb(uv_handle_t *handle, void *arg)
+{
+ if (!uv_is_closing(handle)) {
+ uv_close(handle, NULL);
+ }
+}
+#endif
+
+/// Closes `loop` and its handles, and frees its structures.
+///
+/// @param loop Loop to destroy
+/// @param wait Wait briefly for handles to deref
+///
/// @returns false if the loop could not be closed gracefully
bool loop_close(Loop *loop, bool wait)
{
@@ -126,18 +140,34 @@ bool loop_close(Loop *loop, bool wait)
uv_close((uv_handle_t *)&loop->poll_timer, timer_close_cb);
uv_close((uv_handle_t *)&loop->async, NULL);
uint64_t start = wait ? os_hrtime() : 0;
+ bool didstop = false;
while (true) {
- uv_run(&loop->uv, wait ? UV_RUN_DEFAULT : UV_RUN_NOWAIT);
- if (!uv_loop_close(&loop->uv) || !wait) {
+ // Run the loop to tickle close-callbacks (which should then free memory).
+ // Use UV_RUN_NOWAIT to avoid a hang. #11820
+ uv_run(&loop->uv, didstop ? UV_RUN_DEFAULT : UV_RUN_NOWAIT);
+ if ((uv_loop_close(&loop->uv) != UV_EBUSY) || !wait) {
break;
}
- if (os_hrtime() - start >= 2 * 1000000000) {
+ uint64_t elapsed_s = (os_hrtime() - start) / 1000000000; // seconds
+ if (elapsed_s >= 2) {
// Some libuv resource was not correctly deref'd. Log and bail.
rv = false;
ELOG("uv_loop_close() hang?");
log_uv_handles(&loop->uv);
break;
}
+#if defined(EXITFREE)
+ (void)didstop;
+#else
+ if (!didstop) {
+ // Loop won’t block for I/O after this.
+ uv_stop(&loop->uv);
+ // XXX: Close all (lua/luv!) handles. But loop_walk_cb() does not call
+ // resource-specific close-callbacks, so this leaks memory...
+ uv_walk(&loop->uv, loop_walk_cb, NULL);
+ didstop = true;
+ }
+#endif
}
multiqueue_free(loop->fast_events);
multiqueue_free(loop->thread_events);
diff --git a/src/nvim/event/stream.c b/src/nvim/event/stream.c
index d1a53fa4b6..0e87f7c6c1 100644
--- a/src/nvim/event/stream.c
+++ b/src/nvim/event/stream.c
@@ -16,6 +16,11 @@
# include "event/stream.c.generated.h"
#endif
+// For compatbility with libuv < 1.19.0 (tested on 1.18.0)
+#if UV_VERSION_MINOR < 19
+#define uv_stream_get_write_queue_size(stream) stream->write_queue_size
+#endif
+
/// Sets the stream associated with `fd` to "blocking" mode.
///
/// @return `0` on success, or libuv error code on failure.
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index bc6821f60f..8a0f2e634a 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -882,7 +882,7 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
mark_adjust_nofold(line2 + 1, dest, -num_lines, 0L, kExtmarkNOOP);
FOR_ALL_TAB_WINDOWS(tab, win) {
if (win->w_buffer == curbuf) {
- foldMoveRange(&win->w_folds, line1, line2, dest);
+ foldMoveRange(win, &win->w_folds, line1, line2, dest);
}
}
curbuf->b_op_start.lnum = dest - num_lines + 1;
@@ -891,7 +891,7 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
mark_adjust_nofold(dest + 1, line1 - 1, num_lines, 0L, kExtmarkNOOP);
FOR_ALL_TAB_WINDOWS(tab, win) {
if (win->w_buffer == curbuf) {
- foldMoveRange(&win->w_folds, dest + 1, line1 - 1, line2);
+ foldMoveRange(win, &win->w_folds, dest + 1, line1 - 1, line2);
}
}
curbuf->b_op_start.lnum = dest + 1;
@@ -2177,6 +2177,7 @@ int do_ecmd(
int did_get_winopts = FALSE;
int readfile_flags = 0;
bool did_inc_redrawing_disabled = false;
+ long *so_ptr = curwin->w_p_so >= 0 ? &curwin->w_p_so : &p_so;
if (eap != NULL)
command = eap->do_ecmd_cmd;
@@ -2669,6 +2670,8 @@ int do_ecmd(
msg_scrolled_ign = FALSE;
}
+ curbuf->b_last_used = time(NULL);
+
if (command != NULL)
do_cmdline(command, NULL, NULL, DOCMD_VERBOSE);
@@ -2678,13 +2681,14 @@ int do_ecmd(
RedrawingDisabled--;
did_inc_redrawing_disabled = false;
if (!skip_redraw) {
- n = p_so;
- if (topline == 0 && command == NULL)
- p_so = 999; // force cursor to be vertically centered in the window
+ n = *so_ptr;
+ if (topline == 0 && command == NULL) {
+ *so_ptr = 999; // force cursor to be vertically centered in the window
+ }
update_topline();
curwin->w_scbind_pos = curwin->w_topline;
- p_so = n;
- redraw_curbuf_later(NOT_VALID); /* redraw this buffer later */
+ *so_ptr = n;
+ redraw_curbuf_later(NOT_VALID); // redraw this buffer later
}
if (p_im)
@@ -3008,18 +3012,18 @@ void ex_z(exarg_T *eap)
ex_no_reprint = true;
}
-/*
- * Check if the restricted flag is set.
- * If so, give an error message and return TRUE.
- * Otherwise, return FALSE.
- */
-int check_restricted(void)
+// Check if the restricted flag is set.
+// If so, give an error message and return true.
+// Otherwise, return false.
+bool check_restricted(void)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
if (restricted) {
- EMSG(_("E145: Shell commands not allowed in restricted mode"));
- return TRUE;
+ EMSG(_("E145: Shell commands and some functionality not allowed"
+ " in restricted mode"));
+ return true;
}
- return FALSE;
+ return false;
}
/*
@@ -4482,8 +4486,9 @@ prepare_tagpreview (
curwin->w_p_wfh = TRUE;
RESET_BINDING(curwin); /* don't take over 'scrollbind'
and 'cursorbind' */
- curwin->w_p_diff = FALSE; /* no 'diff' */
- curwin->w_p_fdc = 0; /* no 'foldcolumn' */
+ curwin->w_p_diff = false; // no 'diff'
+ set_string_option_direct((char_u *)"fdc", -1, // no 'foldcolumn'
+ (char_u *)"0", OPT_FREE, SID_NONE);
return true;
}
}
diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua
index f7aa8a994a..252af409c0 100644
--- a/src/nvim/ex_cmds.lua
+++ b/src/nvim/ex_cmds.lua
@@ -24,6 +24,7 @@ local SBOXOK = 0x80000
local CMDWIN = 0x100000
local MODIFY = 0x200000
local EXFLAGS = 0x400000
+local RESTRICT = 0x800000
local FILES = bit.bor(XFILE, EXTRA)
local WORD1 = bit.bor(EXTRA, NOSPC)
local FILE1 = bit.bor(FILES, NOSPC)
@@ -1582,19 +1583,19 @@ return {
},
{
command='lua',
- flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN),
+ flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN, RESTRICT),
addr_type=ADDR_LINES,
func='ex_lua',
},
{
command='luado',
- flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN),
+ flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN, RESTRICT),
addr_type=ADDR_LINES,
func='ex_luado',
},
{
command='luafile',
- flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN),
+ flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN, RESTRICT),
addr_type=ADDR_LINES,
func='ex_luafile',
},
@@ -1924,13 +1925,13 @@ return {
},
{
command='perl',
- flags=bit.bor(RANGE, EXTRA, DFLALL, NEEDARG, SBOXOK, CMDWIN),
+ flags=bit.bor(RANGE, EXTRA, DFLALL, NEEDARG, SBOXOK, CMDWIN, RESTRICT),
addr_type=ADDR_LINES,
func='ex_script_ni',
},
{
command='perldo',
- flags=bit.bor(RANGE, EXTRA, DFLALL, NEEDARG, CMDWIN),
+ flags=bit.bor(RANGE, EXTRA, DFLALL, NEEDARG, CMDWIN, RESTRICT),
addr_type=ADDR_LINES,
func='ex_ni',
},
@@ -2056,67 +2057,67 @@ return {
},
{
command='python',
- flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN),
+ flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN, RESTRICT),
addr_type=ADDR_LINES,
func='ex_python',
},
{
command='pydo',
- flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN),
+ flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN, RESTRICT),
addr_type=ADDR_LINES,
func='ex_pydo',
},
{
command='pyfile',
- flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN),
+ flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN, RESTRICT),
addr_type=ADDR_LINES,
func='ex_pyfile',
},
{
command='py3',
- flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN),
+ flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN, RESTRICT),
addr_type=ADDR_LINES,
func='ex_python3',
},
{
command='py3do',
- flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN),
+ flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN, RESTRICT),
addr_type=ADDR_LINES,
func='ex_pydo3',
},
{
command='python3',
- flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN),
+ flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN, RESTRICT),
addr_type=ADDR_LINES,
func='ex_python3',
},
{
command='py3file',
- flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN),
+ flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN, RESTRICT),
addr_type=ADDR_LINES,
func='ex_py3file',
},
{
command='pyx',
- flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN),
+ flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN, RESTRICT),
addr_type=ADDR_LINES,
func='ex_pyx',
},
{
command='pyxdo',
- flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN),
+ flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN, RESTRICT),
addr_type=ADDR_LINES,
func='ex_pyxdo',
},
{
command='pythonx',
- flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN),
+ flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN, RESTRICT),
addr_type=ADDR_LINES,
func='ex_pyx',
},
{
command='pyxfile',
- flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN),
+ flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN, RESTRICT),
addr_type=ADDR_LINES,
func='ex_pyxfile',
},
@@ -2242,19 +2243,19 @@ return {
},
{
command='ruby',
- flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN),
+ flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN, RESTRICT),
addr_type=ADDR_LINES,
func='ex_ruby',
},
{
command='rubydo',
- flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN),
+ flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN, RESTRICT),
addr_type=ADDR_LINES,
func='ex_rubydo',
},
{
command='rubyfile',
- flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN),
+ flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN, RESTRICT),
addr_type=ADDR_LINES,
func='ex_rubyfile',
},
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c
index a3d49c682e..9f4055af8d 100644
--- a/src/nvim/ex_cmds2.c
+++ b/src/nvim/ex_cmds2.c
@@ -20,7 +20,7 @@
#include "nvim/buffer.h"
#include "nvim/change.h"
#include "nvim/charset.h"
-#include "nvim/eval.h"
+#include "nvim/eval/userfunc.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_eval.h"
@@ -1038,16 +1038,17 @@ static void profile_reset(void)
if (!HASHITEM_EMPTY(hi)) {
n--;
ufunc_T *uf = HI2UF(hi);
- if (uf->uf_profiling) {
+ if (uf->uf_prof_initialized) {
uf->uf_profiling = 0;
uf->uf_tm_count = 0;
uf->uf_tm_total = profile_zero();
uf->uf_tm_self = profile_zero();
uf->uf_tm_children = profile_zero();
- XFREE_CLEAR(uf->uf_tml_count);
- XFREE_CLEAR(uf->uf_tml_total);
- XFREE_CLEAR(uf->uf_tml_self);
+ for (int i = 0; i < uf->uf_lines.ga_len; i++) {
+ uf->uf_tml_count[i] = 0;
+ uf->uf_tml_total[i] = uf->uf_tml_self[i] = 0;
+ }
uf->uf_tml_start = profile_zero();
uf->uf_tml_children = profile_zero();
@@ -2394,7 +2395,7 @@ int do_in_path(char_u *path, char_u *name, int flags,
char_u *rtp_copy = vim_strsave(path);
char_u *buf = xmallocz(MAXPATHL);
{
- if (p_verbose > 1 && name != NULL) {
+ if (p_verbose > 10 && name != NULL) {
verbose_enter();
smsg(_("Searching for \"%s\" in \"%s\""),
(char *)name, (char *)path);
@@ -2436,7 +2437,7 @@ int do_in_path(char_u *path, char_u *name, int flags,
copy_option_part(&np, tail, (size_t)(MAXPATHL - (tail - buf)),
"\t ");
- if (p_verbose > 2) {
+ if (p_verbose > 10) {
verbose_enter();
smsg(_("Searching for \"%s\""), buf);
verbose_leave();
diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h
index 4a40cc54b4..1f0560ae48 100644
--- a/src/nvim/ex_cmds_defs.h
+++ b/src/nvim/ex_cmds_defs.h
@@ -36,35 +36,36 @@
// 4. Add documentation in ../doc/xxx.txt. Add a tag for both the short and
// long name of the command.
-#define RANGE 0x001 /* allow a linespecs */
-#define BANG 0x002 /* allow a ! after the command name */
-#define EXTRA 0x004 /* allow extra args after command name */
-#define XFILE 0x008 /* expand wildcards in extra part */
-#define NOSPC 0x010 /* no spaces allowed in the extra part */
-#define DFLALL 0x020 /* default file range is 1,$ */
-#define WHOLEFOLD 0x040 /* extend range to include whole fold also
- when less than two numbers given */
-#define NEEDARG 0x080 /* argument required */
-#define TRLBAR 0x100 /* check for trailing vertical bar */
-#define REGSTR 0x200 /* allow "x for register designation */
-#define COUNT 0x400 /* allow count in argument, after command */
-#define NOTRLCOM 0x800 /* no trailing comment allowed */
-#define ZEROR 0x1000 /* zero line number allowed */
-#define USECTRLV 0x2000 /* do not remove CTRL-V from argument */
-#define NOTADR 0x4000 /* number before command is not an address */
-#define EDITCMD 0x8000 /* allow "+command" argument */
-#define BUFNAME 0x10000 /* accepts buffer name */
-#define BUFUNL 0x20000 /* accepts unlisted buffer too */
-#define ARGOPT 0x40000 /* allow "++opt=val" argument */
-#define SBOXOK 0x80000 /* allowed in the sandbox */
-#define CMDWIN 0x100000 /* allowed in cmdline window; when missing
- * disallows editing another buffer when
- * curbuf_lock is set */
-#define MODIFY 0x200000 /* forbidden in non-'modifiable' buffer */
-#define EXFLAGS 0x400000 /* allow flags after count in argument */
-#define FILES (XFILE | EXTRA) /* multiple extra files allowed */
-#define WORD1 (EXTRA | NOSPC) /* one extra word allowed */
-#define FILE1 (FILES | NOSPC) /* 1 file allowed, defaults to current file */
+#define RANGE 0x001 // allow a linespecs
+#define BANG 0x002 // allow a ! after the command name
+#define EXTRA 0x004 // allow extra args after command name
+#define XFILE 0x008 // expand wildcards in extra part
+#define NOSPC 0x010 // no spaces allowed in the extra part
+#define DFLALL 0x020 // default file range is 1,$
+#define WHOLEFOLD 0x040 // extend range to include whole fold also
+ // when less than two numbers given
+#define NEEDARG 0x080 // argument required
+#define TRLBAR 0x100 // check for trailing vertical bar
+#define REGSTR 0x200 // allow "x for register designation
+#define COUNT 0x400 // allow count in argument, after command
+#define NOTRLCOM 0x800 // no trailing comment allowed
+#define ZEROR 0x1000 // zero line number allowed
+#define USECTRLV 0x2000 // do not remove CTRL-V from argument
+#define NOTADR 0x4000 // number before command is not an address
+#define EDITCMD 0x8000 // allow "+command" argument
+#define BUFNAME 0x10000 // accepts buffer name
+#define BUFUNL 0x20000 // accepts unlisted buffer too
+#define ARGOPT 0x40000 // allow "++opt=val" argument
+#define SBOXOK 0x80000 // allowed in the sandbox
+#define CMDWIN 0x100000 // allowed in cmdline window; when missing
+ // disallows editing another buffer when
+ // curbuf_lock is set
+#define MODIFY 0x200000 // forbidden in non-'modifiable' buffer
+#define EXFLAGS 0x400000 // allow flags after count in argument
+#define RESTRICT 0x800000L // forbidden in restricted mode
+#define FILES (XFILE | EXTRA) // multiple extra files allowed
+#define WORD1 (EXTRA | NOSPC) // one extra word allowed
+#define FILE1 (FILES | NOSPC) // 1 file allowed, defaults to current file
// values for cmd_addr_type
#define ADDR_LINES 0
@@ -160,7 +161,7 @@ struct exarg {
int regname; ///< register name (NUL if none)
int force_bin; ///< 0, FORCE_BIN or FORCE_NOBIN
int read_edit; ///< ++edit argument
- int force_ff; ///< ++ff= argument (index in cmd[])
+ int force_ff; ///< ++ff= argument (first char of argument)
int force_enc; ///< ++enc= argument (index in cmd[])
int bad_char; ///< BAD_KEEP, BAD_DROP or replacement byte
int useridx; ///< user command index
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index 02bee838d5..5bf6aa73c6 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -1,9 +1,7 @@
// 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
-/*
- * ex_docmd.c: functions for executing an Ex command line.
- */
+// ex_docmd.c: functions for executing an Ex command line.
#include <assert.h>
#include <string.h>
@@ -22,6 +20,7 @@
#include "nvim/digraph.h"
#include "nvim/edit.h"
#include "nvim/eval.h"
+#include "nvim/eval/userfunc.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds2.h"
#include "nvim/ex_eval.h"
@@ -40,6 +39,7 @@
#include "nvim/menu.h"
#include "nvim/message.h"
#include "nvim/misc1.h"
+#include "nvim/ex_session.h"
#include "nvim/keymap.h"
#include "nvim/file_search.h"
#include "nvim/garray.h"
@@ -79,9 +79,6 @@
static int quitmore = 0;
static bool ex_pressedreturn = false;
-/// Whether ":lcd" or ":tcd" was produced for a session.
-static int did_lcd;
-
typedef struct ucmd {
char_u *uc_name; // The command name
uint32_t uc_argt; // The argument type
@@ -201,7 +198,7 @@ static void save_dbg_stuff(struct dbg_stuff *dsp)
static void restore_dbg_stuff(struct dbg_stuff *dsp)
{
- suppress_errthrow = FALSE;
+ suppress_errthrow = false;
trylevel = dsp->trylevel;
force_abort = dsp->force_abort;
caught_stack = dsp->caught_stack;
@@ -398,8 +395,8 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline,
* Initialize "force_abort" and "suppress_errthrow" at the top level.
*/
if (!recursive) {
- force_abort = FALSE;
- suppress_errthrow = FALSE;
+ force_abort = false;
+ suppress_errthrow = false;
}
// If requested, store and reset the global values controlling the
@@ -883,16 +880,14 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline,
xfree(sourcing_name);
sourcing_name = saved_sourcing_name;
sourcing_lnum = saved_sourcing_lnum;
+ } else if (got_int || (did_emsg && force_abort)) {
+ // On an interrupt or an aborting error not converted to an exception,
+ // disable the conversion of errors to exceptions. (Interrupts are not
+ // converted any more, here.) This enables also the interrupt message
+ // when force_abort is set and did_emsg unset in case of an interrupt
+ // from a finally clause after an error.
+ suppress_errthrow = true;
}
- /*
- * On an interrupt or an aborting error not converted to an exception,
- * disable the conversion of errors to exceptions. (Interrupts are not
- * converted any more, here.) This enables also the interrupt message
- * when force_abort is set and did_emsg unset in case of an interrupt
- * from a finally clause after an error.
- */
- else if (got_int || (did_emsg && force_abort))
- suppress_errthrow = TRUE;
}
// The current cstack will be freed when do_cmdline() returns. An uncaught
@@ -1787,10 +1782,14 @@ static char_u * do_one_cmd(char_u **cmdlinep,
if (!ea.skip) {
if (sandbox != 0 && !(ea.argt & SBOXOK)) {
- /* Command not allowed in sandbox. */
+ // Command not allowed in sandbox.
errormsg = (char_u *)_(e_sandbox);
goto doend;
}
+ if (restricted != 0 && (ea.argt & RESTRICT)) {
+ errormsg = (char_u *)_("E981: Command not allowed in restricted mode");
+ goto doend;
+ }
if (!MODIFIABLE(curbuf) && (ea.argt & MODIFY)
// allow :put in terminals
&& (!curbuf->terminal || ea.cmdidx != CMD_put)) {
@@ -2477,8 +2476,11 @@ int parse_cmd_address(exarg_T *eap, char_u **errormsg)
if (*eap->cmd == ';') {
if (!eap->skip) {
curwin->w_cursor.lnum = eap->line2;
- // don't leave the cursor on an illegal line or column
- check_cursor();
+ // Don't leave the cursor on an illegal line or column, but do
+ // accept zero as address, so 0;/PATTERN/ works correctly.
+ if (eap->line2 > 0) {
+ check_cursor();
+ }
}
} else if (*eap->cmd != ',') {
break;
@@ -4377,7 +4379,7 @@ int expand_filename(exarg_T *eap, char_u **cmdlinep, char_u **errormsgp)
if (has_wildcards) {
expand_T xpc;
- int options = WILD_LIST_NOTFOUND|WILD_ADD_SLASH;
+ int options = WILD_LIST_NOTFOUND | WILD_NOERROR | WILD_ADD_SLASH;
ExpandInit(&xpc);
xpc.xp_context = EXPAND_FILES;
@@ -4542,6 +4544,21 @@ skip_cmd_arg (
return p;
}
+int get_bad_opt(const char_u *p, exarg_T *eap)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (STRICMP(p, "keep") == 0) {
+ eap->bad_char = BAD_KEEP;
+ } else if (STRICMP(p, "drop") == 0) {
+ eap->bad_char = BAD_DROP;
+ } else if (MB_BYTE2LEN(*p) == 1 && p[1] == NUL) {
+ eap->bad_char = *p;
+ } else {
+ return FAIL;
+ }
+ return OK;
+}
+
/*
* Get "++opt=arg" argument.
* Return FAIL or OK.
@@ -4600,8 +4617,10 @@ static int getargopt(exarg_T *eap)
*arg = NUL;
if (pp == &eap->force_ff) {
- if (check_ff_value(eap->cmd + eap->force_ff) == FAIL)
+ if (check_ff_value(eap->cmd + eap->force_ff) == FAIL) {
return FAIL;
+ }
+ eap->force_ff = eap->cmd[eap->force_ff];
} else if (pp == &eap->force_enc) {
/* Make 'fileencoding' lower case. */
for (p = eap->cmd + eap->force_enc; *p != NUL; ++p)
@@ -4609,15 +4628,9 @@ static int getargopt(exarg_T *eap)
} else {
/* Check ++bad= argument. Must be a single-byte character, "keep" or
* "drop". */
- p = eap->cmd + bad_char_idx;
- if (STRICMP(p, "keep") == 0)
- eap->bad_char = BAD_KEEP;
- else if (STRICMP(p, "drop") == 0)
- eap->bad_char = BAD_DROP;
- else if (MB_BYTE2LEN(*p) == 1 && p[1] == NUL)
- eap->bad_char = *p;
- else
+ if (get_bad_opt(eap->cmd + bad_char_idx, eap) == FAIL) {
return FAIL;
+ }
}
return OK;
@@ -5134,9 +5147,8 @@ static char *get_command_complete(int arg)
static void uc_list(char_u *name, size_t name_len)
{
int i, j;
- int found = FALSE;
+ bool found = false;
ucmd_T *cmd;
- int len;
uint32_t a;
// In cmdwin, the alternative buffer should be used.
@@ -5155,62 +5167,96 @@ static void uc_list(char_u *name, size_t name_len)
continue;
}
- /* Put out the title first time */
- if (!found)
- MSG_PUTS_TITLE(_("\n Name Args Address Complete Definition"));
- found = TRUE;
+ // Put out the title first time
+ if (!found) {
+ MSG_PUTS_TITLE(_("\n Name Args Address "
+ "Complete Definition"));
+ }
+ found = true;
msg_putchar('\n');
if (got_int)
break;
- /* Special cases */
- msg_putchar(a & BANG ? '!' : ' ');
- msg_putchar(a & REGSTR ? '"' : ' ');
- msg_putchar(gap != &ucmds ? 'b' : ' ');
- msg_putchar(' ');
+ // Special cases
+ int len = 4;
+ if (a & BANG) {
+ msg_putchar('!');
+ len--;
+ }
+ if (a & REGSTR) {
+ msg_putchar('"');
+ len--;
+ }
+ if (gap != &ucmds) {
+ msg_putchar('b');
+ len--;
+ }
+ if (a & TRLBAR) {
+ msg_putchar('|');
+ len--;
+ }
+ while (len-- > 0) {
+ msg_putchar(' ');
+ }
msg_outtrans_attr(cmd->uc_name, HL_ATTR(HLF_D));
len = (int)STRLEN(cmd->uc_name) + 4;
do {
msg_putchar(' ');
- ++len;
- } while (len < 16);
+ len++;
+ } while (len < 22);
+ // "over" is how much longer the name is than the column width for
+ // the name, we'll try to align what comes after.
+ const int over = len - 22;
len = 0;
- /* Arguments */
+ // Arguments
switch (a & (EXTRA|NOSPC|NEEDARG)) {
- case 0: IObuff[len++] = '0'; break;
- case (EXTRA): IObuff[len++] = '*'; break;
- case (EXTRA|NOSPC): IObuff[len++] = '?'; break;
- case (EXTRA|NEEDARG): IObuff[len++] = '+'; break;
- case (EXTRA|NOSPC|NEEDARG): IObuff[len++] = '1'; break;
+ case 0:
+ IObuff[len++] = '0';
+ break;
+ case (EXTRA):
+ IObuff[len++] = '*';
+ break;
+ case (EXTRA|NOSPC):
+ IObuff[len++] = '?';
+ break;
+ case (EXTRA|NEEDARG):
+ IObuff[len++] = '+';
+ break;
+ case (EXTRA|NOSPC|NEEDARG):
+ IObuff[len++] = '1';
+ break;
}
do {
IObuff[len++] = ' ';
- } while (len < 5);
+ } while (len < 5 - over);
- /* Range */
+ // Address / Range
if (a & (RANGE|COUNT)) {
if (a & COUNT) {
- /* -count=N */
- sprintf((char *)IObuff + len, "%" PRId64 "c", (int64_t)cmd->uc_def);
+ // -count=N
+ snprintf((char *)IObuff + len, IOSIZE, "%" PRId64 "c",
+ (int64_t)cmd->uc_def);
len += (int)STRLEN(IObuff + len);
- } else if (a & DFLALL)
+ } else if (a & DFLALL) {
IObuff[len++] = '%';
- else if (cmd->uc_def >= 0) {
- /* -range=N */
- sprintf((char *)IObuff + len, "%" PRId64 "", (int64_t)cmd->uc_def);
+ } else if (cmd->uc_def >= 0) {
+ // -range=N
+ snprintf((char *)IObuff + len, IOSIZE, "%" PRId64 "",
+ (int64_t)cmd->uc_def);
len += (int)STRLEN(IObuff + len);
- } else
+ } else {
IObuff[len++] = '.';
+ }
}
do {
IObuff[len++] = ' ';
- } while (len < 11);
+ } while (len < 9 - over);
// Address Type
for (j = 0; addr_type_complete[j].expand != -1; j++) {
@@ -5224,7 +5270,7 @@ static void uc_list(char_u *name, size_t name_len)
do {
IObuff[len++] = ' ';
- } while (len < 21);
+ } while (len < 13 - over);
// Completion
char *cmd_compl = get_command_complete(cmd->uc_compl);
@@ -5235,12 +5281,13 @@ static void uc_list(char_u *name, size_t name_len)
do {
IObuff[len++] = ' ';
- } while (len < 35);
+ } while (len < 24 - over);
IObuff[len] = '\0';
msg_outtrans(IObuff);
- msg_outtrans_special(cmd->uc_rep, false);
+ msg_outtrans_special(cmd->uc_rep, false,
+ name_len == 0 ? Columns - 46 : 0);
if (p_verbose > 0) {
last_set_msg(cmd->uc_script_ctx);
}
@@ -5428,9 +5475,8 @@ static void ex_command(exarg_T *eap)
end = p;
name_len = (int)(end - name);
- /* If there is nothing after the name, and no attributes were specified,
- * we are listing commands
- */
+ // If there is nothing after the name, and no attributes were specified,
+ // we are listing commands
p = skipwhite(end);
if (!has_attr && ends_excmd(*p)) {
uc_list(name, end - name);
@@ -7323,7 +7369,7 @@ static void ex_syncbind(exarg_T *eap)
topline = curwin->w_topline;
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (wp->w_p_scb && wp->w_buffer) {
- y = wp->w_buffer->b_ml.ml_line_count - p_so;
+ y = wp->w_buffer->b_ml.ml_line_count - get_scrolloff_value();
if (topline > y) {
topline = y;
}
@@ -8085,167 +8131,6 @@ static void close_redir(void)
}
}
-#ifdef USE_CRNL
-# define MKSESSION_NL
-static int mksession_nl = FALSE; /* use NL only in put_eol() */
-#endif
-
-/*
- * ":mkexrc", ":mkvimrc", ":mkview" and ":mksession".
- */
-static void ex_mkrc(exarg_T *eap)
-{
- FILE *fd;
- int failed = false;
- int view_session = false;
- int using_vdir = false; // using 'viewdir'?
- char *viewFile = NULL;
- unsigned *flagp;
-
- if (eap->cmdidx == CMD_mksession || eap->cmdidx == CMD_mkview) {
- view_session = TRUE;
- }
-
- /* Use the short file name until ":lcd" is used. We also don't use the
- * short file name when 'acd' is set, that is checked later. */
- did_lcd = FALSE;
-
- char *fname;
- // ":mkview" or ":mkview 9": generate file name with 'viewdir'
- if (eap->cmdidx == CMD_mkview
- && (*eap->arg == NUL
- || (ascii_isdigit(*eap->arg) && eap->arg[1] == NUL))) {
- eap->forceit = true;
- fname = get_view_file(*eap->arg);
- if (fname == NULL) {
- return;
- }
- viewFile = fname;
- using_vdir = true;
- } else if (*eap->arg != NUL) {
- fname = (char *) eap->arg;
- } else if (eap->cmdidx == CMD_mkvimrc) {
- fname = VIMRC_FILE;
- } else if (eap->cmdidx == CMD_mksession) {
- fname = SESSION_FILE;
- } else {
- fname = EXRC_FILE;
- }
-
- /* When using 'viewdir' may have to create the directory. */
- if (using_vdir && !os_isdir(p_vdir)) {
- vim_mkdir_emsg((const char *)p_vdir, 0755);
- }
-
- fd = open_exfile((char_u *) fname, eap->forceit, WRITEBIN);
- if (fd != NULL) {
- if (eap->cmdidx == CMD_mkview)
- flagp = &vop_flags;
- else
- flagp = &ssop_flags;
-
-#ifdef MKSESSION_NL
- /* "unix" in 'sessionoptions': use NL line separator */
- if (view_session && (*flagp & SSOP_UNIX))
- mksession_nl = TRUE;
-#endif
-
- /* Write the version command for :mkvimrc */
- if (eap->cmdidx == CMD_mkvimrc)
- (void)put_line(fd, "version 6.0");
-
- if (eap->cmdidx == CMD_mksession) {
- if (put_line(fd, "let SessionLoad = 1") == FAIL)
- failed = TRUE;
- }
-
- if (!view_session
- || (eap->cmdidx == CMD_mksession
- && (*flagp & SSOP_OPTIONS)))
- failed |= (makemap(fd, NULL) == FAIL
- || makeset(fd, OPT_GLOBAL, FALSE) == FAIL);
-
- if (!failed && view_session) {
- if (put_line(fd,
- "let s:so_save = &so | let s:siso_save = &siso | set so=0 siso=0")
- == FAIL)
- failed = TRUE;
- if (eap->cmdidx == CMD_mksession) {
- char_u *dirnow; /* current directory */
-
- dirnow = xmalloc(MAXPATHL);
- /*
- * Change to session file's dir.
- */
- if (os_dirname(dirnow, MAXPATHL) == FAIL
- || os_chdir((char *)dirnow) != 0)
- *dirnow = NUL;
- if (*dirnow != NUL && (ssop_flags & SSOP_SESDIR)) {
- if (vim_chdirfile((char_u *) fname) == OK) {
- shorten_fnames(true);
- }
- } else if (*dirnow != NUL
- && (ssop_flags & SSOP_CURDIR) && globaldir != NULL) {
- if (os_chdir((char *)globaldir) == 0)
- shorten_fnames(TRUE);
- }
-
- failed |= (makeopens(fd, dirnow) == FAIL);
-
- /* restore original dir */
- if (*dirnow != NUL && ((ssop_flags & SSOP_SESDIR)
- || ((ssop_flags & SSOP_CURDIR) && globaldir !=
- NULL))) {
- if (os_chdir((char *)dirnow) != 0)
- EMSG(_(e_prev_dir));
- shorten_fnames(TRUE);
- /* restore original dir */
- if (*dirnow != NUL && ((ssop_flags & SSOP_SESDIR)
- || ((ssop_flags & SSOP_CURDIR) && globaldir !=
- NULL))) {
- if (os_chdir((char *)dirnow) != 0)
- EMSG(_(e_prev_dir));
- shorten_fnames(TRUE);
- }
- }
- xfree(dirnow);
- } else {
- failed |= (put_view(fd, curwin, !using_vdir, flagp,
- -1) == FAIL);
- }
- if (put_line(fd, "let &so = s:so_save | let &siso = s:siso_save")
- == FAIL)
- failed = TRUE;
- if (put_line(fd, "doautoall SessionLoadPost") == FAIL)
- failed = TRUE;
- if (eap->cmdidx == CMD_mksession) {
- if (put_line(fd, "unlet SessionLoad") == FAIL)
- failed = TRUE;
- }
- }
- if (put_line(fd, "\" vim: set ft=vim :") == FAIL)
- failed = TRUE;
-
- failed |= fclose(fd);
-
- if (failed) {
- EMSG(_(e_write));
- } else if (eap->cmdidx == CMD_mksession) {
- // successful session write - set v:this_session
- char *const tbuf = xmalloc(MAXPATHL);
- if (vim_FullName(fname, tbuf, MAXPATHL, false) == OK) {
- set_vim_var_string(VV_THIS_SESSION, tbuf, -1);
- }
- xfree(tbuf);
- }
-#ifdef MKSESSION_NL
- mksession_nl = FALSE;
-#endif
- }
-
- xfree(viewFile);
-}
-
/// Try creating a directory, give error message on failure
///
/// @param[in] name Directory to create.
@@ -8328,6 +8213,57 @@ void update_topline_cursor(void)
update_curswant();
}
+// Save the current State and go to Normal mode.
+// Return true if the typeahead could be saved.
+bool save_current_state(save_state_T *sst)
+ FUNC_ATTR_NONNULL_ALL
+{
+ sst->save_msg_scroll = msg_scroll;
+ sst->save_restart_edit = restart_edit;
+ sst->save_msg_didout = msg_didout;
+ sst->save_State = State;
+ sst->save_insertmode = p_im;
+ sst->save_finish_op = finish_op;
+ sst->save_opcount = opcount;
+ sst->save_reg_executing = reg_executing;
+
+ msg_scroll = false; // no msg scrolling in Normal mode
+ restart_edit = 0; // don't go to Insert mode
+ p_im = false; // don't use 'insertmode
+
+ // Save the current typeahead. This is required to allow using ":normal"
+ // from an event handler and makes sure we don't hang when the argument
+ // ends with half a command.
+ save_typeahead(&sst->tabuf);
+ return sst->tabuf.typebuf_valid;
+}
+
+void restore_current_state(save_state_T *sst)
+ FUNC_ATTR_NONNULL_ALL
+{
+ // Restore the previous typeahead.
+ restore_typeahead(&sst->tabuf);
+
+ msg_scroll = sst->save_msg_scroll;
+ if (force_restart_edit) {
+ force_restart_edit = false;
+ } else {
+ // Some function (terminal_enter()) was aware of ex_normal and decided to
+ // override the value of restart_edit anyway.
+ restart_edit = sst->save_restart_edit;
+ }
+ p_im = sst->save_insertmode;
+ finish_op = sst->save_finish_op;
+ opcount = sst->save_opcount;
+ reg_executing = sst->save_reg_executing;
+ msg_didout |= sst->save_msg_didout; // don't reset msg_didout now
+
+ // Restore the state (needed when called from a function executed for
+ // 'indentexpr'). Update the mouse and cursor, they may have changed.
+ State = sst->save_State;
+ ui_cursor_shape(); // may show different cursor shape
+}
+
/*
* ":normal[!] {commands}": Execute normal mode commands.
*/
@@ -8337,15 +8273,7 @@ static void ex_normal(exarg_T *eap)
EMSG("Can't re-enter normal mode from terminal mode");
return;
}
- int save_msg_scroll = msg_scroll;
- int save_restart_edit = restart_edit;
- int save_msg_didout = msg_didout;
- int save_State = State;
- tasave_T tabuf;
- int save_insertmode = p_im;
- int save_finish_op = finish_op;
- long save_opcount = opcount;
- const int save_reg_executing = reg_executing;
+ save_state_T save_state;
char_u *arg = NULL;
int l;
char_u *p;
@@ -8358,11 +8286,6 @@ static void ex_normal(exarg_T *eap)
EMSG(_("E192: Recursive use of :normal too deep"));
return;
}
- ++ex_normal_busy;
-
- msg_scroll = FALSE; /* no msg scrolling in Normal mode */
- restart_edit = 0; /* don't go to Insert mode */
- p_im = FALSE; /* don't use 'insertmode' */
/*
* vgetc() expects a CSI and K_SPECIAL to have been escaped. Don't do
@@ -8396,19 +8319,11 @@ static void ex_normal(exarg_T *eap)
}
}
- /*
- * Save the current typeahead. This is required to allow using ":normal"
- * from an event handler and makes sure we don't hang when the argument
- * ends with half a command.
- */
- save_typeahead(&tabuf);
- // TODO(philix): after save_typeahead() this is always TRUE
- if (tabuf.typebuf_valid) {
- /*
- * Repeat the :normal command for each line in the range. When no
- * range given, execute it just once, without positioning the cursor
- * first.
- */
+ ex_normal_busy++;
+ if (save_current_state(&save_state)) {
+ // Repeat the :normal command for each line in the range. When no
+ // range given, execute it just once, without positioning the cursor
+ // first.
do {
if (eap->addr_count != 0) {
curwin->w_cursor.lnum = eap->line1++;
@@ -8425,29 +8340,12 @@ static void ex_normal(exarg_T *eap)
/* Might not return to the main loop when in an event handler. */
update_topline_cursor();
- /* Restore the previous typeahead. */
- restore_typeahead(&tabuf);
+ restore_current_state(&save_state);
- --ex_normal_busy;
- msg_scroll = save_msg_scroll;
- if (force_restart_edit) {
- force_restart_edit = false;
- } else {
- // Some function (terminal_enter()) was aware of ex_normal and decided to
- // override the value of restart_edit anyway.
- restart_edit = save_restart_edit;
- }
- p_im = save_insertmode;
- finish_op = save_finish_op;
- opcount = save_opcount;
- reg_executing = save_reg_executing;
- msg_didout |= save_msg_didout; // don't reset msg_didout now
+ ex_normal_busy--;
- /* Restore the state (needed when called from a function executed for
- * 'indentexpr'). Update the mouse and cursor, they may have changed. */
- State = save_State;
setmouse();
- ui_cursor_shape(); /* may show different cursor shape */
+ ui_cursor_shape(); // may show different cursor shape
xfree(arg);
}
@@ -9118,864 +9016,6 @@ char_u *expand_sfile(char_u *arg)
return result;
}
-
-/*
- * Write openfile commands for the current buffers to an .exrc file.
- * Return FAIL on error, OK otherwise.
- */
-static int
-makeopens(
- FILE *fd,
- char_u *dirnow /* Current directory name */
-)
-{
- int only_save_windows = TRUE;
- int nr;
- int restore_size = true;
- win_T *wp;
- char_u *sname;
- win_T *edited_win = NULL;
- int tabnr;
- win_T *tab_firstwin;
- frame_T *tab_topframe;
- int cur_arg_idx = 0;
- int next_arg_idx = 0;
-
- if (ssop_flags & SSOP_BUFFERS)
- only_save_windows = FALSE; /* Save ALL buffers */
-
- // Begin by setting v:this_session, and then other sessionable variables.
- if (put_line(fd, "let v:this_session=expand(\"<sfile>:p\")") == FAIL) {
- return FAIL;
- }
- if (ssop_flags & SSOP_GLOBALS) {
- if (store_session_globals(fd) == FAIL) {
- return FAIL;
- }
- }
-
- /*
- * Close all windows but one.
- */
- if (put_line(fd, "silent only") == FAIL)
- return FAIL;
-
- /*
- * Now a :cd command to the session directory or the current directory
- */
- if (ssop_flags & SSOP_SESDIR) {
- if (put_line(fd, "exe \"cd \" . escape(expand(\"<sfile>:p:h\"), ' ')")
- == FAIL)
- return FAIL;
- } else if (ssop_flags & SSOP_CURDIR) {
- sname = home_replace_save(NULL, globaldir != NULL ? globaldir : dirnow);
- if (fputs("cd ", fd) < 0
- || ses_put_fname(fd, sname, &ssop_flags) == FAIL
- || put_eol(fd) == FAIL) {
- xfree(sname);
- return FAIL;
- }
- xfree(sname);
- }
-
- /*
- * If there is an empty, unnamed buffer we will wipe it out later.
- * Remember the buffer number.
- */
- if (put_line(fd,
- "if expand('%') == '' && !&modified && line('$') <= 1 && getline(1) == ''")
- ==
- FAIL)
- return FAIL;
- if (put_line(fd, " let s:wipebuf = bufnr('%')") == FAIL)
- return FAIL;
- if (put_line(fd, "endif") == FAIL)
- return FAIL;
-
- /*
- * Now save the current files, current buffer first.
- */
- if (put_line(fd, "set shortmess=aoO") == FAIL)
- return FAIL;
-
- /* Now put the other buffers into the buffer list */
- FOR_ALL_BUFFERS(buf) {
- if (!(only_save_windows && buf->b_nwindows == 0)
- && !(buf->b_help && !(ssop_flags & SSOP_HELP))
- && buf->b_fname != NULL
- && buf->b_p_bl) {
- if (fprintf(fd, "badd +%" PRId64 " ",
- buf->b_wininfo == NULL
- ? (int64_t)1L
- : (int64_t)buf->b_wininfo->wi_fpos.lnum) < 0
- || ses_fname(fd, buf, &ssop_flags, true) == FAIL) {
- return FAIL;
- }
- }
- }
-
- /* the global argument list */
- if (ses_arglist(fd, "argglobal", &global_alist.al_ga,
- !(ssop_flags & SSOP_CURDIR), &ssop_flags) == FAIL) {
- return FAIL;
- }
-
- if (ssop_flags & SSOP_RESIZE) {
- /* Note: after the restore we still check it worked!*/
- if (fprintf(fd, "set lines=%" PRId64 " columns=%" PRId64,
- (int64_t)Rows, (int64_t)Columns) < 0
- || put_eol(fd) == FAIL)
- return FAIL;
- }
-
- int restore_stal = FALSE;
- // When there are two or more tabpages and 'showtabline' is 1 the tabline
- // will be displayed when creating the next tab. That resizes the windows
- // in the first tab, which may cause problems. Set 'showtabline' to 2
- // temporarily to avoid that.
- if (p_stal == 1 && first_tabpage->tp_next != NULL) {
- if (put_line(fd, "set stal=2") == FAIL) {
- return FAIL;
- }
- restore_stal = TRUE;
- }
-
- /*
- * May repeat putting Windows for each tab, when "tabpages" is in
- * 'sessionoptions'.
- * Don't use goto_tabpage(), it may change directory and trigger
- * autocommands.
- */
- tab_firstwin = firstwin; /* first window in tab page "tabnr" */
- tab_topframe = topframe;
- for (tabnr = 1;; tabnr++) {
- tabpage_T *tp = find_tabpage(tabnr);
- if (tp == NULL) {
- break; // done all tab pages
- }
-
- int need_tabnew = false;
- int cnr = 1;
-
- if ((ssop_flags & SSOP_TABPAGES)) {
- if (tp == curtab) {
- tab_firstwin = firstwin;
- tab_topframe = topframe;
- } else {
- tab_firstwin = tp->tp_firstwin;
- tab_topframe = tp->tp_topframe;
- }
- if (tabnr > 1)
- need_tabnew = TRUE;
- }
-
- /*
- * Before creating the window layout, try loading one file. If this
- * is aborted we don't end up with a number of useless windows.
- * This may have side effects! (e.g., compressed or network file).
- */
- for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) {
- if (ses_do_win(wp)
- && wp->w_buffer->b_ffname != NULL
- && !bt_help(wp->w_buffer)
- && !bt_nofile(wp->w_buffer)
- ) {
- if (fputs(need_tabnew ? "tabedit " : "edit ", fd) < 0
- || ses_fname(fd, wp->w_buffer, &ssop_flags, true) == FAIL) {
- return FAIL;
- }
- need_tabnew = false;
- if (!wp->w_arg_idx_invalid) {
- edited_win = wp;
- }
- break;
- }
- }
-
- /* If no file got edited create an empty tab page. */
- if (need_tabnew && put_line(fd, "tabnew") == FAIL)
- return FAIL;
-
- /*
- * Save current window layout.
- */
- if (put_line(fd, "set splitbelow splitright") == FAIL)
- return FAIL;
- if (ses_win_rec(fd, tab_topframe) == FAIL)
- return FAIL;
- if (!p_sb && put_line(fd, "set nosplitbelow") == FAIL)
- return FAIL;
- if (!p_spr && put_line(fd, "set nosplitright") == FAIL)
- return FAIL;
-
- /*
- * Check if window sizes can be restored (no windows omitted).
- * Remember the window number of the current window after restoring.
- */
- nr = 0;
- for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) {
- if (ses_do_win(wp))
- ++nr;
- else
- restore_size = FALSE;
- if (curwin == wp)
- cnr = nr;
- }
-
- /* Go to the first window. */
- if (put_line(fd, "wincmd t") == FAIL)
- return FAIL;
-
- // If more than one window, see if sizes can be restored.
- // First set 'winheight' and 'winwidth' to 1 to avoid the windows being
- // resized when moving between windows.
- // Do this before restoring the view, so that the topline and the
- // cursor can be set. This is done again below.
- // winminheight and winminwidth need to be set to avoid an error if the
- // user has set winheight or winwidth.
- if (put_line(fd, "set winminheight=0") == FAIL
- || put_line(fd, "set winheight=1") == FAIL
- || put_line(fd, "set winminwidth=0") == FAIL
- || put_line(fd, "set winwidth=1") == FAIL) {
- return FAIL;
- }
- if (nr > 1 && ses_winsizes(fd, restore_size, tab_firstwin) == FAIL) {
- return FAIL;
- }
-
- /*
- * Restore the view of the window (options, file, cursor, etc.).
- */
- for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) {
- if (!ses_do_win(wp))
- continue;
- if (put_view(fd, wp, wp != edited_win, &ssop_flags,
- cur_arg_idx) == FAIL)
- return FAIL;
- if (nr > 1 && put_line(fd, "wincmd w") == FAIL)
- return FAIL;
- next_arg_idx = wp->w_arg_idx;
- }
-
- /* The argument index in the first tab page is zero, need to set it in
- * each window. For further tab pages it's the window where we do
- * "tabedit". */
- cur_arg_idx = next_arg_idx;
-
- /*
- * Restore cursor to the current window if it's not the first one.
- */
- if (cnr > 1 && (fprintf(fd, "%dwincmd w", cnr) < 0
- || put_eol(fd) == FAIL))
- return FAIL;
-
- /*
- * Restore window sizes again after jumping around in windows, because
- * the current window has a minimum size while others may not.
- */
- if (nr > 1 && ses_winsizes(fd, restore_size, tab_firstwin) == FAIL)
- return FAIL;
-
- // Take care of tab-local working directories if applicable
- if (tp->tp_localdir) {
- if (fputs("if exists(':tcd') == 2 | tcd ", fd) < 0
- || ses_put_fname(fd, tp->tp_localdir, &ssop_flags) == FAIL
- || fputs(" | endif", fd) < 0
- || put_eol(fd) == FAIL) {
- return FAIL;
- }
- did_lcd = true;
- }
-
- /* Don't continue in another tab page when doing only the current one
- * or when at the last tab page. */
- if (!(ssop_flags & SSOP_TABPAGES))
- break;
- }
-
- if (ssop_flags & SSOP_TABPAGES) {
- if (fprintf(fd, "tabnext %d", tabpage_index(curtab)) < 0
- || put_eol(fd) == FAIL)
- return FAIL;
- }
- if (restore_stal && put_line(fd, "set stal=1") == FAIL) {
- return FAIL;
- }
-
- /*
- * Wipe out an empty unnamed buffer we started in.
- */
- if (put_line(fd, "if exists('s:wipebuf') "
- "&& getbufvar(s:wipebuf, '&buftype') isnot# 'terminal'")
- == FAIL)
- return FAIL;
- if (put_line(fd, " silent exe 'bwipe ' . s:wipebuf") == FAIL)
- return FAIL;
- if (put_line(fd, "endif") == FAIL)
- return FAIL;
- if (put_line(fd, "unlet! s:wipebuf") == FAIL)
- return FAIL;
-
- // Re-apply options.
- if (fprintf(fd, "set winheight=%" PRId64 " winwidth=%" PRId64
- " winminheight=%" PRId64 " winminwidth=%" PRId64
- " shortmess=%s",
- (int64_t)p_wh,
- (int64_t)p_wiw,
- (int64_t)p_wmh,
- (int64_t)p_wmw,
- p_shm) < 0
- || put_eol(fd) == FAIL) {
- return FAIL;
- }
-
- /*
- * Lastly, execute the x.vim file if it exists.
- */
- if (put_line(fd, "let s:sx = expand(\"<sfile>:p:r\").\"x.vim\"") == FAIL
- || put_line(fd, "if file_readable(s:sx)") == FAIL
- || put_line(fd, " exe \"source \" . fnameescape(s:sx)") == FAIL
- || put_line(fd, "endif") == FAIL)
- return FAIL;
-
- return OK;
-}
-
-static int ses_winsizes(FILE *fd, int restore_size, win_T *tab_firstwin)
-{
- int n = 0;
- win_T *wp;
-
- if (restore_size && (ssop_flags & SSOP_WINSIZE)) {
- for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) {
- if (!ses_do_win(wp)) {
- continue;
- }
- n++;
-
- // restore height when not full height
- if (wp->w_height + wp->w_status_height < topframe->fr_height
- && (fprintf(fd,
- "exe '%dresize ' . ((&lines * %" PRId64
- " + %" PRId64 ") / %" PRId64 ")",
- n, (int64_t)wp->w_height,
- (int64_t)Rows / 2, (int64_t)Rows) < 0
- || put_eol(fd) == FAIL)) {
- return FAIL;
- }
-
- // restore width when not full width
- if (wp->w_width < Columns
- && (fprintf(fd,
- "exe 'vert %dresize ' . ((&columns * %" PRId64
- " + %" PRId64 ") / %" PRId64 ")",
- n, (int64_t)wp->w_width, (int64_t)Columns / 2,
- (int64_t)Columns) < 0
- || put_eol(fd) == FAIL)) {
- return FAIL;
- }
- }
- } else {
- // Just equalise window sizes
- if (put_line(fd, "wincmd =") == FAIL) {
- return FAIL;
- }
- }
- 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.
- */
-static int ses_win_rec(FILE *fd, frame_T *fr)
-{
- frame_T *frc;
- int count = 0;
-
- if (fr->fr_layout != FR_LEAF) {
- /* Find first frame that's not skipped and then create a window for
- * each following one (first frame is already there). */
- frc = ses_skipframe(fr->fr_child);
- if (frc != NULL)
- while ((frc = ses_skipframe(frc->fr_next)) != NULL) {
- /* Make window as big as possible so that we have lots of room
- * to split. */
- if (put_line(fd, "wincmd _ | wincmd |") == FAIL
- || put_line(fd, fr->fr_layout == FR_COL
- ? "split" : "vsplit") == FAIL)
- return FAIL;
- ++count;
- }
-
- /* Go back to the first window. */
- if (count > 0 && (fprintf(fd, fr->fr_layout == FR_COL
- ? "%dwincmd k" : "%dwincmd h", count) < 0
- || put_eol(fd) == FAIL))
- return FAIL;
-
- /* Recursively create frames/windows in each window of this column or
- * row. */
- frc = ses_skipframe(fr->fr_child);
- while (frc != NULL) {
- ses_win_rec(fd, frc);
- frc = ses_skipframe(frc->fr_next);
- /* Go to next window. */
- if (frc != NULL && put_line(fd, "wincmd w") == FAIL)
- return FAIL;
- }
- }
- return OK;
-}
-
-/*
- * Skip frames that don't contain windows we want to save in the Session.
- * Returns NULL when there none.
- */
-static frame_T *ses_skipframe(frame_T *fr)
-{
- frame_T *frc;
-
- FOR_ALL_FRAMES(frc, fr) {
- if (ses_do_frame(frc)) {
- break;
- }
- }
- return frc;
-}
-
-// 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)
-{
- const frame_T *frc;
-
- if (fr->fr_layout == FR_LEAF) {
- return ses_do_win(fr->fr_win);
- }
- FOR_ALL_FRAMES(frc, fr->fr_child) {
- if (ses_do_frame(frc)) {
- return true;
- }
- }
- return false;
-}
-
-/// 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
- // When 'buftype' is "nofile" can't restore the window contents.
- || (!wp->w_buffer->terminal && bt_nofile(wp->w_buffer))) {
- return ssop_flags & SSOP_BLANK;
- }
- if (bt_help(wp->w_buffer)) {
- return ssop_flags & SSOP_HELP;
- }
- return true;
-}
-
-static int put_view_curpos(FILE *fd, const win_T *wp, char *spaces)
-{
- int r;
-
- if (wp->w_curswant == MAXCOL) {
- r = fprintf(fd, "%snormal! $", spaces);
- } else {
- r = fprintf(fd, "%snormal! 0%d|", spaces, wp->w_virtcol + 1);
- }
- return r < 0 || put_eol(fd) == FAIL ? FAIL : OK;
-}
-
-/*
- * Write commands to "fd" to restore the view of a window.
- * Caller must make sure 'scrolloff' is zero.
- */
-static int
-put_view(
- FILE *fd,
- win_T *wp,
- int add_edit, /* add ":edit" command to view */
- unsigned *flagp, /* vop_flags or ssop_flags */
- int current_arg_idx /* current argument index of the window, use
- * -1 if unknown */
-)
-{
- win_T *save_curwin;
- int f;
- int do_cursor;
- int did_next = FALSE;
-
- /* Always restore cursor position for ":mksession". For ":mkview" only
- * when 'viewoptions' contains "cursor". */
- do_cursor = (flagp == &ssop_flags || *flagp & SSOP_CURSOR);
-
- /*
- * Local argument list.
- */
- if (wp->w_alist == &global_alist) {
- if (put_line(fd, "argglobal") == FAIL)
- return FAIL;
- } else {
- if (ses_arglist(fd, "arglocal", &wp->w_alist->al_ga,
- flagp == &vop_flags
- || !(*flagp & SSOP_CURDIR)
- || wp->w_localdir != NULL, flagp) == FAIL)
- return FAIL;
- }
-
- /* Only when part of a session: restore the argument index. Some
- * arguments may have been deleted, check if the index is valid. */
- if (wp->w_arg_idx != current_arg_idx && wp->w_arg_idx < WARGCOUNT(wp)
- && flagp == &ssop_flags) {
- if (fprintf(fd, "%" PRId64 "argu", (int64_t)wp->w_arg_idx + 1) < 0
- || put_eol(fd) == FAIL) {
- return FAIL;
- }
- did_next = true;
- }
-
- /* Edit the file. Skip this when ":next" already did it. */
- if (add_edit && (!did_next || wp->w_arg_idx_invalid)) {
- /*
- * Load the file.
- */
- if (wp->w_buffer->b_ffname != NULL
- && (!bt_nofile(wp->w_buffer) || wp->w_buffer->terminal)
- ) {
- // Editing a file in this buffer: use ":edit file".
- // This may have side effects! (e.g., compressed or network file).
- //
- // Note, if a buffer for that file already exists, use :badd to
- // edit that buffer, to not lose folding information (:edit resets
- // folds in other buffers)
- if (fputs("if bufexists(\"", fd) < 0
- || ses_fname(fd, wp->w_buffer, flagp, false) == FAIL
- || fputs("\") | buffer ", fd) < 0
- || ses_fname(fd, wp->w_buffer, flagp, false) == FAIL
- || fputs(" | else | edit ", fd) < 0
- || ses_fname(fd, wp->w_buffer, flagp, false) == FAIL
- || fputs(" | endif", fd) < 0
- || put_eol(fd) == FAIL) {
- return FAIL;
- }
- } else {
- // No file in this buffer, just make it empty.
- if (put_line(fd, "enew") == FAIL) {
- return FAIL;
- }
- if (wp->w_buffer->b_ffname != NULL) {
- // The buffer does have a name, but it's not a file name.
- if (fputs("file ", fd) < 0
- || ses_fname(fd, wp->w_buffer, flagp, true) == FAIL) {
- return FAIL;
- }
- }
- do_cursor = false;
- }
- }
-
- /*
- * Local mappings and abbreviations.
- */
- if ((*flagp & (SSOP_OPTIONS | SSOP_LOCALOPTIONS))
- && makemap(fd, wp->w_buffer) == FAIL)
- return FAIL;
-
- /*
- * Local options. Need to go to the window temporarily.
- * Store only local values when using ":mkview" and when ":mksession" is
- * used and 'sessionoptions' doesn't include "nvim/options".
- * Some folding options are always stored when "folds" is included,
- * otherwise the folds would not be restored correctly.
- */
- save_curwin = curwin;
- curwin = wp;
- curbuf = curwin->w_buffer;
- if (*flagp & (SSOP_OPTIONS | SSOP_LOCALOPTIONS))
- f = makeset(fd, OPT_LOCAL,
- flagp == &vop_flags || !(*flagp & SSOP_OPTIONS));
- else if (*flagp & SSOP_FOLDS)
- f = makefoldset(fd);
- else
- f = OK;
- curwin = save_curwin;
- curbuf = curwin->w_buffer;
- if (f == FAIL)
- return FAIL;
-
- /*
- * Save Folds when 'buftype' is empty and for help files.
- */
- if ((*flagp & SSOP_FOLDS)
- && wp->w_buffer->b_ffname != NULL
- && (bt_normal(wp->w_buffer) || bt_help(wp->w_buffer))
- ) {
- if (put_folds(fd, wp) == FAIL)
- return FAIL;
- }
-
- /*
- * Set the cursor after creating folds, since that moves the cursor.
- */
- if (do_cursor) {
-
- /* Restore the cursor line in the file and relatively in the
- * window. Don't use "G", it changes the jumplist. */
- if (fprintf(fd,
- "let s:l = %" PRId64 " - ((%" PRId64
- " * winheight(0) + %" PRId64 ") / %" PRId64 ")",
- (int64_t)wp->w_cursor.lnum,
- (int64_t)(wp->w_cursor.lnum - wp->w_topline),
- (int64_t)(wp->w_height_inner / 2),
- (int64_t)wp->w_height_inner) < 0
- || put_eol(fd) == FAIL
- || put_line(fd, "if s:l < 1 | let s:l = 1 | endif") == FAIL
- || put_line(fd, "exe s:l") == FAIL
- || put_line(fd, "normal! zt") == FAIL
- || fprintf(fd, "%" PRId64, (int64_t)wp->w_cursor.lnum) < 0
- || put_eol(fd) == FAIL)
- return FAIL;
- /* Restore the cursor column and left offset when not wrapping. */
- if (wp->w_cursor.col == 0) {
- if (put_line(fd, "normal! 0") == FAIL)
- return FAIL;
- } else {
- if (!wp->w_p_wrap && wp->w_leftcol > 0 && wp->w_width > 0) {
- if (fprintf(fd,
- "let s:c = %" PRId64 " - ((%" PRId64
- " * winwidth(0) + %" PRId64 ") / %" PRId64 ")",
- (int64_t)wp->w_virtcol + 1,
- (int64_t)(wp->w_virtcol - wp->w_leftcol),
- (int64_t)(wp->w_width / 2),
- (int64_t)wp->w_width) < 0
- || put_eol(fd) == FAIL
- || put_line(fd, "if s:c > 0") == FAIL
- || fprintf(fd, " exe 'normal! ' . s:c . '|zs' . %" PRId64 " . '|'",
- (int64_t)wp->w_virtcol + 1) < 0
- || put_eol(fd) == FAIL
- || put_line(fd, "else") == FAIL
- || put_view_curpos(fd, wp, " ") == FAIL
- || put_line(fd, "endif") == FAIL) {
- return FAIL;
- }
- } else if (put_view_curpos(fd, wp, "") == FAIL) {
- return FAIL;
- }
- }
- }
-
- //
- // Local directory, if the current flag is not view options or the "curdir"
- // option is included.
- //
- if (wp->w_localdir != NULL
- && (flagp != &vop_flags || (*flagp & SSOP_CURDIR))) {
- if (fputs("lcd ", fd) < 0
- || ses_put_fname(fd, wp->w_localdir, flagp) == FAIL
- || put_eol(fd) == FAIL) {
- return FAIL;
- }
- did_lcd = true;
- }
-
- return OK;
-}
-
-/*
- * Write an argument list to the session file.
- * Returns FAIL if writing fails.
- */
-static int
-ses_arglist(
- FILE *fd,
- char *cmd,
- garray_T *gap,
- int fullname, /* TRUE: use full path name */
- unsigned *flagp
-)
-{
- char_u *buf = NULL;
- char_u *s;
-
- if (fputs(cmd, fd) < 0 || put_eol(fd) == FAIL) {
- return FAIL;
- }
- if (put_line(fd, "%argdel") == FAIL) {
- return FAIL;
- }
- for (int i = 0; i < gap->ga_len; ++i) {
- /* NULL file names are skipped (only happens when out of memory). */
- s = alist_name(&((aentry_T *)gap->ga_data)[i]);
- if (s != NULL) {
- if (fullname) {
- buf = xmalloc(MAXPATHL);
- (void)vim_FullName((char *)s, (char *)buf, MAXPATHL, FALSE);
- s = buf;
- }
- if (fputs("$argadd ", fd) < 0 || ses_put_fname(fd, s, flagp) == FAIL
- || put_eol(fd) == FAIL) {
- xfree(buf);
- return FAIL;
- }
- xfree(buf);
- }
- }
- return OK;
-}
-
-/// Write a buffer name to the session file.
-/// Also ends the line, if "add_eol" is true.
-/// Returns FAIL if writing fails.
-static int ses_fname(FILE *fd, buf_T *buf, unsigned *flagp, bool add_eol)
-{
- char_u *name;
-
- /* Use the short file name if the current directory is known at the time
- * the session file will be sourced.
- * Don't do this for ":mkview", we don't know the current directory.
- * Don't do this after ":lcd", we don't keep track of what the current
- * directory is. */
- if (buf->b_sfname != NULL
- && flagp == &ssop_flags
- && (ssop_flags & (SSOP_CURDIR | SSOP_SESDIR))
- && !p_acd
- && !did_lcd)
- name = buf->b_sfname;
- else
- name = buf->b_ffname;
- if (ses_put_fname(fd, name, flagp) == FAIL
- || (add_eol && put_eol(fd) == FAIL)) {
- return FAIL;
- }
- return OK;
-}
-
-/*
- * 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.
- */
-static int ses_put_fname(FILE *fd, char_u *name, unsigned *flagp)
-{
- char_u *p;
-
- char_u *sname = home_replace_save(NULL, name);
-
- if (*flagp & SSOP_SLASH) {
- // change all backslashes to forward slashes
- for (p = sname; *p != NUL; MB_PTR_ADV(p)) {
- if (*p == '\\') {
- *p = '/';
- }
- }
- }
-
- // Escape special characters.
- p = (char_u *)vim_strsave_fnameescape((const char *)sname, false);
- xfree(sname);
-
- /* write the result */
- bool retval = fputs((char *)p, fd) < 0 ? FAIL : OK;
- xfree(p);
- return retval;
-}
-
-/*
- * ":loadview [nr]"
- */
-static void ex_loadview(exarg_T *eap)
-{
- char *fname = get_view_file(*eap->arg);
- if (fname != NULL) {
- if (do_source((char_u *)fname, FALSE, DOSO_NONE) == FAIL) {
- EMSG2(_(e_notopen), fname);
- }
- xfree(fname);
- }
-}
-
-/// Get the name of the view file for the current buffer.
-static char *get_view_file(int c)
-{
- if (curbuf->b_ffname == NULL) {
- EMSG(_(e_noname));
- return NULL;
- }
- char *sname = (char *)home_replace_save(NULL, curbuf->b_ffname);
-
- // We want a file name without separators, because we're not going to make
- // a directory.
- // "normal" path separator -> "=+"
- // "=" -> "=="
- // ":" path separator -> "=-"
- size_t len = 0;
- for (char *p = sname; *p; p++) {
- if (*p == '=' || vim_ispathsep(*p)) {
- ++len;
- }
- }
- char *retval = xmalloc(strlen(sname) + len + STRLEN(p_vdir) + 9);
- STRCPY(retval, p_vdir);
- add_pathsep(retval);
- char *s = retval + strlen(retval);
- for (char *p = sname; *p; p++) {
- if (*p == '=') {
- *s++ = '=';
- *s++ = '=';
- } else if (vim_ispathsep(*p)) {
- *s++ = '=';
-#if defined(BACKSLASH_IN_FILENAME)
- if (*p == ':')
- *s++ = '-';
- else
-#endif
- *s++ = '+';
- } else
- *s++ = *p;
- }
- *s++ = '=';
- *s++ = c;
- strcpy(s, ".vim");
-
- xfree(sname);
- return retval;
-}
-
-
-/*
- * Write end-of-line character(s) for ":mkexrc", ":mkvimrc" and ":mksession".
- * Return FAIL for a write error.
- */
-int put_eol(FILE *fd)
-{
-#if defined(USE_CRNL) && defined(MKSESSION_NL)
- if ((!mksession_nl && putc('\r', fd) < 0) || putc('\n', fd) < 0) {
-#elif defined(USE_CRNL)
- if (putc('\r', fd) < 0 || putc('\n', fd) < 0) {
-#else
- if (putc('\n', fd) < 0) {
-#endif
- return FAIL;
- }
- return OK;
-}
-
-/*
- * Write a line to "fd".
- * Return FAIL for a write error.
- */
-int put_line(FILE *fd, char *s)
-{
- if (fputs(s, fd) < 0 || put_eol(fd) == FAIL)
- return FAIL;
- return OK;
-}
-
/*
* ":rshada" and ":wshada".
*/
@@ -10268,8 +9308,9 @@ static void ex_match(exarg_T *eap)
static void ex_fold(exarg_T *eap)
{
- if (foldManualAllowed(TRUE))
- foldCreate(eap->line1, eap->line2);
+ if (foldManualAllowed(true)) {
+ foldCreate(curwin, eap->line1, eap->line2);
+ }
}
static void ex_foldopen(exarg_T *eap)
diff --git a/src/nvim/ex_docmd.h b/src/nvim/ex_docmd.h
index cff350de08..f6bd2adcd5 100644
--- a/src/nvim/ex_docmd.h
+++ b/src/nvim/ex_docmd.h
@@ -20,6 +20,20 @@
#define EXMODE_NORMAL 1
#define EXMODE_VIM 2
+// Structure used to save the current state. Used when executing Normal mode
+// commands while in any other mode.
+typedef struct {
+ int save_msg_scroll;
+ int save_restart_edit;
+ int save_msg_didout;
+ int save_State;
+ int save_insertmode;
+ bool save_finish_op;
+ long save_opcount;
+ int save_reg_executing;
+ tasave_T tabuf;
+} save_state_T;
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_docmd.h.generated.h"
#endif
diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c
index f70a568e4a..81274fcf2a 100644
--- a/src/nvim/ex_eval.c
+++ b/src/nvim/ex_eval.c
@@ -16,7 +16,7 @@
#include "nvim/ex_eval.h"
#include "nvim/charset.h"
#include "nvim/eval.h"
-#include "nvim/eval/typval.h"
+#include "nvim/eval/userfunc.h"
#include "nvim/ex_cmds2.h"
#include "nvim/ex_docmd.h"
#include "nvim/message.h"
@@ -508,7 +508,7 @@ static int throw_exception(void *value, except_type_T type, char_u *cmdname)
nomem:
xfree(excp);
- suppress_errthrow = TRUE;
+ suppress_errthrow = true;
EMSG(_(e_outofmem));
fail:
current_exception = NULL;
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index e6b7bfaebf..b799e86ff7 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -23,6 +23,7 @@
#include "nvim/digraph.h"
#include "nvim/edit.h"
#include "nvim/eval.h"
+#include "nvim/eval/userfunc.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds2.h"
#include "nvim/ex_docmd.h"
@@ -621,6 +622,16 @@ static int command_line_execute(VimState *state, int key)
s->c = Ctrl_N;
}
}
+ if (compl_match_array || s->did_wild_list) {
+ if (s->c == Ctrl_E) {
+ s->res = nextwild(&s->xpc, WILD_CANCEL, WILD_NO_BEEP,
+ s->firstc != '@');
+ } else if (s->c == Ctrl_Y) {
+ s->res = nextwild(&s->xpc, WILD_APPLY, WILD_NO_BEEP,
+ s->firstc != '@');
+ s->c = Ctrl_E;
+ }
+ }
// Hitting CR after "emenu Name.": complete submenu
if (s->xpc.xp_context == EXPAND_MENUNAMES && p_wmnu
@@ -937,6 +948,10 @@ static int command_line_execute(VimState *state, int key)
// - wildcard expansion is only done when the 'wildchar' key is really
// typed, not when it comes from a macro
if ((s->c == p_wc && !s->gotesc && KeyTyped) || s->c == p_wcm) {
+ int options = WILD_NO_BEEP;
+ if (wim_flags[s->wim_index] & WIM_BUFLASTUSED) {
+ options |= WILD_BUFLASTUSED;
+ }
if (s->xpc.xp_numfiles > 0) { // typed p_wc at least twice
// if 'wildmode' contains "list" may still need to list
if (s->xpc.xp_numfiles > 1
@@ -950,11 +965,11 @@ static int command_line_execute(VimState *state, int key)
}
if (wim_flags[s->wim_index] & WIM_LONGEST) {
- s->res = nextwild(&s->xpc, WILD_LONGEST, WILD_NO_BEEP,
- s->firstc != '@');
+ s->res = nextwild(&s->xpc, WILD_LONGEST, options,
+ s->firstc != '@');
} else if (wim_flags[s->wim_index] & WIM_FULL) {
- s->res = nextwild(&s->xpc, WILD_NEXT, WILD_NO_BEEP,
- s->firstc != '@');
+ s->res = nextwild(&s->xpc, WILD_NEXT, options,
+ s->firstc != '@');
} else {
s->res = OK; // don't insert 'wildchar' now
}
@@ -965,11 +980,11 @@ static int command_line_execute(VimState *state, int key)
// if 'wildmode' first contains "longest", get longest
// common part
if (wim_flags[0] & WIM_LONGEST) {
- s->res = nextwild(&s->xpc, WILD_LONGEST, WILD_NO_BEEP,
- s->firstc != '@');
+ s->res = nextwild(&s->xpc, WILD_LONGEST, options,
+ s->firstc != '@');
} else {
- s->res = nextwild(&s->xpc, WILD_EXPAND_KEEP, WILD_NO_BEEP,
- s->firstc != '@');
+ s->res = nextwild(&s->xpc, WILD_EXPAND_KEEP, options,
+ s->firstc != '@');
}
// if interrupted while completing, behave like it failed
@@ -1006,11 +1021,11 @@ static int command_line_execute(VimState *state, int key)
s->did_wild_list = true;
if (wim_flags[s->wim_index] & WIM_LONGEST) {
- nextwild(&s->xpc, WILD_LONGEST, WILD_NO_BEEP,
- s->firstc != '@');
+ nextwild(&s->xpc, WILD_LONGEST, options,
+ s->firstc != '@');
} else if (wim_flags[s->wim_index] & WIM_FULL) {
- nextwild(&s->xpc, WILD_NEXT, WILD_NO_BEEP,
- s->firstc != '@');
+ nextwild(&s->xpc, WILD_NEXT, options,
+ s->firstc != '@');
}
} else {
vim_beep(BO_WILD);
@@ -2437,9 +2452,10 @@ redraw:
/* make following messages go to the next line */
msg_didout = FALSE;
msg_col = 0;
- if (msg_row < Rows - 1)
- ++msg_row;
- emsg_on_display = FALSE; /* don't want os_delay() */
+ if (msg_row < Rows - 1) {
+ msg_row++;
+ }
+ emsg_on_display = false; // don't want os_delay()
if (got_int)
ga_clear(&line_ga);
@@ -2701,7 +2717,7 @@ static bool color_cmdline(CmdlineInfo *colored_ccline)
goto color_cmdline_error;
}
if (tv.v_type != VAR_LIST) {
- PRINT_ERRMSG(_("E5400: Callback should return list"));
+ PRINT_ERRMSG("%s", _("E5400: Callback should return list"));
goto color_cmdline_error;
}
if (tv.vval.v_list == NULL) {
@@ -3040,7 +3056,7 @@ void cmdline_screen_cleared(void)
}
}
-/// called by ui_flush, do what redraws neccessary to keep cmdline updated.
+/// called by ui_flush, do what redraws necessary to keep cmdline updated.
void cmdline_ui_flush(void)
{
if (!ui_has(kUICmdline)) {
@@ -3790,6 +3806,12 @@ ExpandOne (
return NULL;
}
+ if (mode == WILD_CANCEL) {
+ ss = vim_strsave(orig_save);
+ } else if (mode == WILD_APPLY) {
+ ss = vim_strsave(findex == -1 ? orig_save : xp->xp_files[findex]);
+ }
+
/* free old names */
if (xp->xp_numfiles != -1 && mode != WILD_ALL && mode != WILD_LONGEST) {
FreeWild(xp->xp_numfiles, xp->xp_files);
@@ -3801,7 +3823,7 @@ ExpandOne (
if (mode == WILD_FREE) /* only release file name */
return NULL;
- if (xp->xp_numfiles == -1) {
+ if (xp->xp_numfiles == -1 && mode != WILD_APPLY && mode != WILD_CANCEL) {
xfree(orig_save);
orig_save = orig;
orig_saved = TRUE;
@@ -4693,6 +4715,9 @@ ExpandFromContext (
flags |= EW_KEEPALL;
if (options & WILD_SILENT)
flags |= EW_SILENT;
+ if (options & WILD_NOERROR) {
+ flags |= EW_NOERROR;
+ }
if (options & WILD_ALLLINKS) {
flags |= EW_ALLLINKS;
}
diff --git a/src/nvim/ex_getln.h b/src/nvim/ex_getln.h
index 99d5a7786d..dc4395e081 100644
--- a/src/nvim/ex_getln.h
+++ b/src/nvim/ex_getln.h
@@ -16,6 +16,8 @@
#define WILD_ALL 6
#define WILD_LONGEST 7
#define WILD_ALL_KEEP 8
+#define WILD_CANCEL 9
+#define WILD_APPLY 10
#define WILD_LIST_NOTFOUND 0x01
#define WILD_HOME_REPLACE 0x02
@@ -29,6 +31,7 @@
#define WILD_ALLLINKS 0x200
#define WILD_IGNORE_COMPLETESLASH 0x400
#define WILD_NOERROR 0x800 // sets EW_NOERROR
+#define WILD_BUFLASTUSED 0x1000
/// Present history tables
typedef enum {
diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c
new file mode 100644
index 0000000000..1b797c6168
--- /dev/null
+++ b/src/nvim/ex_session.c
@@ -0,0 +1,1047 @@
+// 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
+
+// Functions for creating a session file, i.e. implementing:
+// :mkexrc
+// :mkvimrc
+// :mkview
+// :mksession
+
+#include <assert.h>
+#include <string.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <inttypes.h>
+
+#include "nvim/vim.h"
+#include "nvim/globals.h"
+#include "nvim/ascii.h"
+#include "nvim/buffer.h"
+#include "nvim/cursor.h"
+#include "nvim/edit.h"
+#include "nvim/eval.h"
+#include "nvim/ex_cmds2.h"
+#include "nvim/ex_docmd.h"
+#include "nvim/ex_getln.h"
+#include "nvim/ex_session.h"
+#include "nvim/file_search.h"
+#include "nvim/fileio.h"
+#include "nvim/fold.h"
+#include "nvim/getchar.h"
+#include "nvim/keymap.h"
+#include "nvim/misc1.h"
+#include "nvim/move.h"
+#include "nvim/option.h"
+#include "nvim/os/input.h"
+#include "nvim/os/os.h"
+#include "nvim/os/time.h"
+#include "nvim/path.h"
+#include "nvim/window.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "ex_session.c.generated.h"
+#endif
+
+/// Whether ":lcd" or ":tcd" was produced for a session.
+static int did_lcd;
+
+#define PUTLINE_FAIL(s) \
+ do { if (FAIL == put_line(fd, (s))) { return FAIL; } } while (0)
+
+static int put_view_curpos(FILE *fd, const win_T *wp, char *spaces)
+{
+ int r;
+
+ if (wp->w_curswant == MAXCOL) {
+ r = fprintf(fd, "%snormal! $\n", spaces);
+ } else {
+ r = fprintf(fd, "%snormal! 0%d|\n", spaces, wp->w_virtcol + 1);
+ }
+ return r >= 0;
+}
+
+static int ses_winsizes(FILE *fd, int restore_size, win_T *tab_firstwin)
+{
+ int n = 0;
+ win_T *wp;
+
+ if (restore_size && (ssop_flags & SSOP_WINSIZE)) {
+ for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) {
+ if (!ses_do_win(wp)) {
+ continue;
+ }
+ n++;
+
+ // restore height when not full height
+ if (wp->w_height + wp->w_status_height < topframe->fr_height
+ && (fprintf(fd,
+ "exe '%dresize ' . ((&lines * %" PRId64
+ " + %" PRId64 ") / %" PRId64 ")\n",
+ n, (int64_t)wp->w_height,
+ (int64_t)Rows / 2, (int64_t)Rows) < 0)) {
+ return FAIL;
+ }
+
+ // restore width when not full width
+ if (wp->w_width < Columns
+ && (fprintf(fd,
+ "exe 'vert %dresize ' . ((&columns * %" PRId64
+ " + %" PRId64 ") / %" PRId64 ")\n",
+ n, (int64_t)wp->w_width, (int64_t)Columns / 2,
+ (int64_t)Columns) < 0)) {
+ return FAIL;
+ }
+ }
+ } else {
+ // Just equalize window sizes.
+ PUTLINE_FAIL("wincmd =");
+ }
+ 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.
+static int ses_win_rec(FILE *fd, frame_T *fr)
+{
+ frame_T *frc;
+ int count = 0;
+
+ if (fr->fr_layout != FR_LEAF) {
+ // Find first frame that's not skipped and then create a window for
+ // each following one (first frame is already there).
+ frc = ses_skipframe(fr->fr_child);
+ if (frc != NULL) {
+ while ((frc = ses_skipframe(frc->fr_next)) != NULL) {
+ // Make window as big as possible so that we have lots of room
+ // to split.
+ if (fprintf(fd, "%s%s",
+ "wincmd _ | wincmd |\n",
+ (fr->fr_layout == FR_COL ? "split\n" : "vsplit\n")) < 0) {
+ return FAIL;
+ }
+ count++;
+ }
+ }
+
+ // Go back to the first window.
+ if (count > 0 && (fprintf(fd, fr->fr_layout == FR_COL
+ ? "%dwincmd k\n" : "%dwincmd h\n", count) < 0)) {
+ return FAIL;
+ }
+
+ // Recursively create frames/windows in each window of this column or row.
+ frc = ses_skipframe(fr->fr_child);
+ while (frc != NULL) {
+ ses_win_rec(fd, frc);
+ frc = ses_skipframe(frc->fr_next);
+ // Go to next window.
+ if (frc != NULL && put_line(fd, "wincmd w") == FAIL) {
+ return FAIL;
+ }
+ }
+ }
+ return OK;
+}
+
+// Skip frames that don't contain windows we want to save in the Session.
+// Returns NULL when there none.
+static frame_T *ses_skipframe(frame_T *fr)
+{
+ frame_T *frc;
+
+ FOR_ALL_FRAMES(frc, fr) {
+ if (ses_do_frame(frc)) {
+ break;
+ }
+ }
+ return frc;
+}
+
+// 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)
+{
+ const frame_T *frc;
+
+ if (fr->fr_layout == FR_LEAF) {
+ return ses_do_win(fr->fr_win);
+ }
+ FOR_ALL_FRAMES(frc, fr->fr_child) {
+ if (ses_do_frame(frc)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/// 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
+ // When 'buftype' is "nofile" can't restore the window contents.
+ || (!wp->w_buffer->terminal && bt_nofile(wp->w_buffer))) {
+ return ssop_flags & SSOP_BLANK;
+ }
+ if (bt_help(wp->w_buffer)) {
+ return ssop_flags & SSOP_HELP;
+ }
+ return true;
+}
+
+/// Writes an :argument list to the session file.
+///
+/// @param fd
+/// @param cmd
+/// @param gap
+/// @param fullname true: use full path name
+/// @param flagp
+///
+/// @returns FAIL if writing fails.
+static int ses_arglist(FILE *fd, char *cmd, garray_T *gap, int fullname,
+ unsigned *flagp)
+{
+ char_u *buf = NULL;
+ char_u *s;
+
+ if (fprintf(fd, "%s\n%s\n", cmd, "%argdel") < 0) {
+ return FAIL;
+ }
+ for (int i = 0; i < gap->ga_len; i++) {
+ // NULL file names are skipped (only happens when out of memory).
+ s = alist_name(&((aentry_T *)gap->ga_data)[i]);
+ if (s != NULL) {
+ if (fullname) {
+ buf = xmalloc(MAXPATHL);
+ (void)vim_FullName((char *)s, (char *)buf, MAXPATHL, false);
+ s = buf;
+ }
+ char *fname_esc = ses_escape_fname((char *)s, flagp);
+ if (fprintf(fd, "$argadd %s\n", fname_esc) < 0) {
+ xfree(fname_esc);
+ xfree(buf);
+ return FAIL;
+ }
+ xfree(fname_esc);
+ xfree(buf);
+ }
+ }
+ return OK;
+}
+
+/// Gets 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
+ // the session file will be sourced.
+ // Don't do this for ":mkview", we don't know the current directory.
+ // Don't do this after ":lcd", we don't keep track of what the current
+ // directory is.
+ if (buf->b_sfname != NULL
+ && flagp == &ssop_flags
+ && (ssop_flags & (SSOP_CURDIR | SSOP_SESDIR))
+ && !p_acd
+ && !did_lcd) {
+ return (char *)buf->b_sfname;
+ }
+ return (char *)buf->b_ffname;
+}
+
+/// Write a buffer name to the session file.
+/// Also ends the line, if "add_eol" is true.
+/// Returns 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);
+ if (ses_put_fname(fd, (char_u *)name, flagp) == FAIL
+ || (add_eol && fprintf(fd, "\n") < 0)) {
+ return FAIL;
+ }
+ return OK;
+}
+
+// Escapes a filename for session writing.
+// Takes care of "slash" flag in 'sessionoptions' and escapes special
+// characters.
+//
+// Returns allocated string or NULL.
+static char *ses_escape_fname(char *name, unsigned *flagp)
+{
+ char *p;
+ char *sname = (char *)home_replace_save(NULL, (char_u *)name);
+
+ // Always SSOP_SLASH: change all backslashes to forward slashes.
+ for (p = sname; *p != NUL; MB_PTR_ADV(p)) {
+ if (*p == '\\') {
+ *p = '/';
+ }
+ }
+
+ // Escape special characters.
+ p = vim_strsave_fnameescape(sname, false);
+ xfree(sname);
+ 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.
+static int ses_put_fname(FILE *fd, char_u *name, unsigned *flagp)
+{
+ char *p = ses_escape_fname((char *)name, flagp);
+ bool retval = fputs(p, fd) < 0 ? FAIL : OK;
+ xfree(p);
+ return retval;
+}
+
+// Write commands to "fd" to restore the view of a window.
+// Caller must make sure 'scrolloff' is zero.
+static int put_view(
+ FILE *fd,
+ win_T *wp,
+ int add_edit, // add ":edit" command to view
+ unsigned *flagp, // vop_flags or ssop_flags
+ int current_arg_idx // current argument index of the window, use
+) // -1 if unknown
+{
+ win_T *save_curwin;
+ int f;
+ int do_cursor;
+ int did_next = false;
+
+ // Always restore cursor position for ":mksession". For ":mkview" only
+ // when 'viewoptions' contains "cursor".
+ do_cursor = (flagp == &ssop_flags || *flagp & SSOP_CURSOR);
+
+ //
+ // Local argument list.
+ //
+ if (wp->w_alist == &global_alist) {
+ PUTLINE_FAIL("argglobal");
+ } else {
+ if (ses_arglist(fd, "arglocal", &wp->w_alist->al_ga,
+ flagp == &vop_flags
+ || !(*flagp & SSOP_CURDIR)
+ || wp->w_localdir != NULL, flagp) == FAIL) {
+ return FAIL;
+ }
+ }
+
+ // Only when part of a session: restore the argument index. Some
+ // arguments may have been deleted, check if the index is valid.
+ if (wp->w_arg_idx != current_arg_idx && wp->w_arg_idx < WARGCOUNT(wp)
+ && flagp == &ssop_flags) {
+ if (fprintf(fd, "%" PRId64 "argu\n", (int64_t)wp->w_arg_idx + 1) < 0) {
+ return FAIL;
+ }
+ did_next = true;
+ }
+
+ // Edit the file. Skip this when ":next" already did it.
+ if (add_edit && (!did_next || wp->w_arg_idx_invalid)) {
+ char *fname_esc =
+ ses_escape_fname(ses_get_fname(wp->w_buffer, flagp), flagp);
+ //
+ // Load the file.
+ //
+ if (wp->w_buffer->b_ffname != NULL
+ && (!bt_nofile(wp->w_buffer) || wp->w_buffer->terminal)
+ ) {
+ // Editing a file in this buffer: use ":edit file".
+ // This may have side effects! (e.g., compressed or network file).
+ //
+ // Note, if a buffer for that file already exists, use :badd to
+ // edit that buffer, to not lose folding information (:edit resets
+ // folds in other buffers)
+ if (fprintf(fd,
+ "if bufexists(\"%s\") | buffer %s | else | edit %s | endif\n"
+ // Fixup :terminal buffer name. #7836
+ "if &buftype ==# 'terminal'\n"
+ " silent file %s\n"
+ "endif\n",
+ fname_esc,
+ fname_esc,
+ fname_esc,
+ fname_esc) < 0) {
+ xfree(fname_esc);
+ return FAIL;
+ }
+ } else {
+ // No file in this buffer, just make it empty.
+ PUTLINE_FAIL("enew");
+ if (wp->w_buffer->b_ffname != NULL) {
+ // The buffer does have a name, but it's not a file name.
+ if (fprintf(fd, "file %s\n", fname_esc) < 0) {
+ xfree(fname_esc);
+ return FAIL;
+ }
+ }
+ do_cursor = false;
+ }
+ xfree(fname_esc);
+ }
+
+ //
+ // Local mappings and abbreviations.
+ //
+ if ((*flagp & (SSOP_OPTIONS | SSOP_LOCALOPTIONS))
+ && makemap(fd, wp->w_buffer) == FAIL) {
+ return FAIL;
+ }
+
+ //
+ // Local options. Need to go to the window temporarily.
+ // Store only local values when using ":mkview" and when ":mksession" is
+ // used and 'sessionoptions' doesn't include "nvim/options".
+ // Some folding options are always stored when "folds" is included,
+ // otherwise the folds would not be restored correctly.
+ //
+ save_curwin = curwin;
+ curwin = wp;
+ curbuf = curwin->w_buffer;
+ if (*flagp & (SSOP_OPTIONS | SSOP_LOCALOPTIONS)) {
+ f = makeset(fd, OPT_LOCAL,
+ flagp == &vop_flags || !(*flagp & SSOP_OPTIONS));
+ } else if (*flagp & SSOP_FOLDS) {
+ f = makefoldset(fd);
+ } else {
+ f = OK;
+ }
+ curwin = save_curwin;
+ curbuf = curwin->w_buffer;
+ if (f == FAIL) {
+ return FAIL;
+ }
+
+ //
+ // Save Folds when 'buftype' is empty and for help files.
+ //
+ if ((*flagp & SSOP_FOLDS)
+ && wp->w_buffer->b_ffname != NULL
+ && (bt_normal(wp->w_buffer) || bt_help(wp->w_buffer))
+ ) {
+ if (put_folds(fd, wp) == FAIL) {
+ return FAIL;
+ }
+ }
+
+ //
+ // Set the cursor after creating folds, since that moves the cursor.
+ //
+ if (do_cursor) {
+ // Restore the cursor line in the file and relatively in the
+ // window. Don't use "G", it changes the jumplist.
+ if (fprintf(fd,
+ "let s:l = %" PRId64 " - ((%" PRId64
+ " * winheight(0) + %" PRId64 ") / %" PRId64 ")\n"
+ "if s:l < 1 | let s:l = 1 | endif\n"
+ "exe s:l\n"
+ "normal! zt\n"
+ "%" PRId64 "\n",
+ (int64_t)wp->w_cursor.lnum,
+ (int64_t)(wp->w_cursor.lnum - wp->w_topline),
+ (int64_t)(wp->w_height_inner / 2),
+ (int64_t)wp->w_height_inner,
+ (int64_t)wp->w_cursor.lnum) < 0) {
+ return FAIL;
+ }
+ // Restore the cursor column and left offset when not wrapping.
+ if (wp->w_cursor.col == 0) {
+ PUTLINE_FAIL("normal! 0");
+ } else {
+ if (!wp->w_p_wrap && wp->w_leftcol > 0 && wp->w_width > 0) {
+ if (fprintf(fd,
+ "let s:c = %" PRId64 " - ((%" PRId64
+ " * winwidth(0) + %" PRId64 ") / %" PRId64 ")\n"
+ "if s:c > 0\n"
+ " exe 'normal! ' . s:c . '|zs' . %" PRId64 " . '|'\n"
+ "else\n",
+ (int64_t)wp->w_virtcol + 1,
+ (int64_t)(wp->w_virtcol - wp->w_leftcol),
+ (int64_t)(wp->w_width / 2),
+ (int64_t)wp->w_width,
+ (int64_t)wp->w_virtcol + 1) < 0
+ || put_view_curpos(fd, wp, " ") == FAIL
+ || put_line(fd, "endif") == FAIL) {
+ return FAIL;
+ }
+ } else if (put_view_curpos(fd, wp, "") == FAIL) {
+ return FAIL;
+ }
+ }
+ }
+
+ //
+ // Local directory, if the current flag is not view options or the "curdir"
+ // option is included.
+ //
+ if (wp->w_localdir != NULL
+ && (flagp != &vop_flags || (*flagp & SSOP_CURDIR))) {
+ if (fputs("lcd ", fd) < 0
+ || ses_put_fname(fd, wp->w_localdir, flagp) == FAIL
+ || fprintf(fd, "\n") < 0) {
+ return FAIL;
+ }
+ did_lcd = true;
+ }
+
+ return OK;
+}
+
+/// Writes commands for restoring the current buffers, for :mksession.
+///
+/// Legacy 'sessionoptions' flags SSOP_UNIX, SSOP_SLASH are always enabled.
+///
+/// @param dirnow Current directory name
+/// @param fd File descriptor to write to
+///
+/// @return FAIL on error, OK otherwise.
+static int makeopens(FILE *fd, char_u *dirnow)
+{
+ int only_save_windows = true;
+ int nr;
+ int restore_size = true;
+ win_T *wp;
+ char_u *sname;
+ win_T *edited_win = NULL;
+ int tabnr;
+ win_T *tab_firstwin;
+ frame_T *tab_topframe;
+ int cur_arg_idx = 0;
+ int next_arg_idx = 0;
+
+ if (ssop_flags & SSOP_BUFFERS) {
+ only_save_windows = false; // Save ALL buffers
+ }
+
+ // Begin by setting v:this_session, and then other sessionable variables.
+ PUTLINE_FAIL("let v:this_session=expand(\"<sfile>:p\")");
+ if (ssop_flags & SSOP_GLOBALS) {
+ if (store_session_globals(fd) == FAIL) {
+ return FAIL;
+ }
+ }
+
+ // Close all windows but one.
+ PUTLINE_FAIL("silent only");
+
+ //
+ // Now a :cd command to the session directory or the current directory
+ //
+ if (ssop_flags & SSOP_SESDIR) {
+ PUTLINE_FAIL("exe \"cd \" . escape(expand(\"<sfile>:p:h\"), ' ')");
+ } else if (ssop_flags & SSOP_CURDIR) {
+ sname = home_replace_save(NULL, globaldir != NULL ? globaldir : dirnow);
+ char *fname_esc = ses_escape_fname((char *)sname, &ssop_flags);
+ if (fprintf(fd, "cd %s\n", fname_esc) < 0) {
+ xfree(fname_esc);
+ xfree(sname);
+ return FAIL;
+ }
+ xfree(fname_esc);
+ xfree(sname);
+ }
+
+ if (fprintf(fd,
+ "%s",
+ // If there is an empty, unnamed buffer we will wipe it out later.
+ // Remember the buffer number.
+ "if expand('%') == '' && !&modified && line('$') <= 1"
+ " && getline(1) == ''\n"
+ " let s:wipebuf = bufnr('%')\n"
+ "endif\n"
+ // Now save the current files, current buffer first.
+ "set shortmess=aoO\n") < 0) {
+ return FAIL;
+ }
+
+ // Now put the other buffers into the buffer list.
+ FOR_ALL_BUFFERS(buf) {
+ if (!(only_save_windows && buf->b_nwindows == 0)
+ && !(buf->b_help && !(ssop_flags & SSOP_HELP))
+ && buf->b_fname != NULL
+ && buf->b_p_bl) {
+ if (fprintf(fd, "badd +%" PRId64 " ",
+ buf->b_wininfo == NULL
+ ? (int64_t)1L
+ : (int64_t)buf->b_wininfo->wi_fpos.lnum) < 0
+ || ses_fname(fd, buf, &ssop_flags, true) == FAIL) {
+ return FAIL;
+ }
+ }
+ }
+
+ // the global argument list
+ if (ses_arglist(fd, "argglobal", &global_alist.al_ga,
+ !(ssop_flags & SSOP_CURDIR), &ssop_flags) == FAIL) {
+ return FAIL;
+ }
+
+ if (ssop_flags & SSOP_RESIZE) {
+ // Note: after the restore we still check it worked!
+ if (fprintf(fd, "set lines=%" PRId64 " columns=%" PRId64 "\n",
+ (int64_t)Rows, (int64_t)Columns) < 0) {
+ return FAIL;
+ }
+ }
+
+ int restore_stal = false;
+ // When there are two or more tabpages and 'showtabline' is 1 the tabline
+ // will be displayed when creating the next tab. That resizes the windows
+ // in the first tab, which may cause problems. Set 'showtabline' to 2
+ // temporarily to avoid that.
+ if (p_stal == 1 && first_tabpage->tp_next != NULL) {
+ PUTLINE_FAIL("set stal=2");
+ restore_stal = true;
+ }
+
+ //
+ // For each tab:
+ // - Put windows for each tab, when "tabpages" is in 'sessionoptions'.
+ // - Don't use goto_tabpage(), it may change CWD and trigger autocommands.
+ //
+ tab_firstwin = firstwin; // First window in tab page "tabnr".
+ tab_topframe = topframe;
+ for (tabnr = 1;; tabnr++) {
+ tabpage_T *tp = find_tabpage(tabnr);
+ if (tp == NULL) {
+ break; // done all tab pages
+ }
+
+ int need_tabnew = false;
+ int cnr = 1;
+
+ if ((ssop_flags & SSOP_TABPAGES)) {
+ if (tp == curtab) {
+ tab_firstwin = firstwin;
+ tab_topframe = topframe;
+ } else {
+ tab_firstwin = tp->tp_firstwin;
+ tab_topframe = tp->tp_topframe;
+ }
+ if (tabnr > 1) {
+ need_tabnew = true;
+ }
+ }
+
+ //
+ // Before creating the window layout, try loading one file. If this
+ // is aborted we don't end up with a number of useless windows.
+ // This may have side effects! (e.g., compressed or network file).
+ //
+ for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) {
+ if (ses_do_win(wp)
+ && wp->w_buffer->b_ffname != NULL
+ && !bt_help(wp->w_buffer)
+ && !bt_nofile(wp->w_buffer)
+ ) {
+ if (fputs(need_tabnew ? "tabedit " : "edit ", fd) < 0
+ || ses_fname(fd, wp->w_buffer, &ssop_flags, true) == FAIL) {
+ return FAIL;
+ }
+ need_tabnew = false;
+ if (!wp->w_arg_idx_invalid) {
+ edited_win = wp;
+ }
+ break;
+ }
+ }
+
+ // If no file got edited create an empty tab page.
+ if (need_tabnew && put_line(fd, "tabnew") == FAIL) {
+ return FAIL;
+ }
+
+ //
+ // Save current window layout.
+ //
+ PUTLINE_FAIL("set splitbelow splitright");
+ if (ses_win_rec(fd, tab_topframe) == FAIL) {
+ return FAIL;
+ }
+ if (!p_sb && put_line(fd, "set nosplitbelow") == FAIL) {
+ return FAIL;
+ }
+ if (!p_spr && put_line(fd, "set nosplitright") == FAIL) {
+ return FAIL;
+ }
+
+ //
+ // Check if window sizes can be restored (no windows omitted).
+ // Remember the window number of the current window after restoring.
+ //
+ nr = 0;
+ for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) {
+ if (ses_do_win(wp)) {
+ nr++;
+ } else {
+ restore_size = false;
+ }
+ if (curwin == wp) {
+ cnr = nr;
+ }
+ }
+
+ // Go to the first window.
+ PUTLINE_FAIL("wincmd t");
+
+ // If more than one window, see if sizes can be restored.
+ // First set 'winheight' and 'winwidth' to 1 to avoid the windows being
+ // resized when moving between windows.
+ // Do this before restoring the view, so that the topline and the
+ // cursor can be set. This is done again below.
+ // winminheight and winminwidth need to be set to avoid an error if the
+ // user has set winheight or winwidth.
+ if (fprintf(fd,
+ "set winminheight=0\n"
+ "set winheight=1\n"
+ "set winminwidth=0\n"
+ "set winwidth=1\n") < 0) {
+ return FAIL;
+ }
+ if (nr > 1 && ses_winsizes(fd, restore_size, tab_firstwin) == FAIL) {
+ return FAIL;
+ }
+
+ //
+ // Restore the view of the window (options, file, cursor, etc.).
+ //
+ for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) {
+ if (!ses_do_win(wp)) {
+ continue;
+ }
+ if (put_view(fd, wp, wp != edited_win, &ssop_flags, cur_arg_idx)
+ == FAIL) {
+ return FAIL;
+ }
+ if (nr > 1 && put_line(fd, "wincmd w") == FAIL) {
+ return FAIL;
+ }
+ next_arg_idx = wp->w_arg_idx;
+ }
+
+ // The argument index in the first tab page is zero, need to set it in
+ // each window. For further tab pages it's the window where we do
+ // "tabedit".
+ cur_arg_idx = next_arg_idx;
+
+ //
+ // Restore cursor to the current window if it's not the first one.
+ //
+ if (cnr > 1 && (fprintf(fd, "%dwincmd w\n", cnr) < 0)) {
+ return FAIL;
+ }
+
+ //
+ // Restore window sizes again after jumping around in windows, because
+ // the current window has a minimum size while others may not.
+ //
+ if (nr > 1 && ses_winsizes(fd, restore_size, tab_firstwin) == FAIL) {
+ return FAIL;
+ }
+
+ // Take care of tab-local working directories if applicable
+ if (tp->tp_localdir) {
+ if (fputs("if exists(':tcd') == 2 | tcd ", fd) < 0
+ || ses_put_fname(fd, tp->tp_localdir, &ssop_flags) == FAIL
+ || fputs(" | endif\n", fd) < 0) {
+ return FAIL;
+ }
+ did_lcd = true;
+ }
+
+ // Don't continue in another tab page when doing only the current one
+ // or when at the last tab page.
+ if (!(ssop_flags & SSOP_TABPAGES)) {
+ break;
+ }
+ }
+
+ if (ssop_flags & SSOP_TABPAGES) {
+ if (fprintf(fd, "tabnext %d\n", tabpage_index(curtab)) < 0) {
+ return FAIL;
+ }
+ }
+ if (restore_stal && put_line(fd, "set stal=1") == FAIL) {
+ return FAIL;
+ }
+
+ //
+ // Wipe out an empty unnamed buffer we started in.
+ //
+ if (fprintf(fd, "%s",
+ "if exists('s:wipebuf') "
+ "&& getbufvar(s:wipebuf, '&buftype') isnot# 'terminal'\n"
+ " silent exe 'bwipe ' . s:wipebuf\n"
+ "endif\n"
+ "unlet! s:wipebuf\n") < 0) {
+ return FAIL;
+ }
+
+ // Re-apply options.
+ if (fprintf(fd,
+ "set winheight=%" PRId64 " winwidth=%" PRId64
+ " winminheight=%" PRId64 " winminwidth=%" PRId64
+ " shortmess=%s\n",
+ (int64_t)p_wh,
+ (int64_t)p_wiw,
+ (int64_t)p_wmh,
+ (int64_t)p_wmw,
+ p_shm) < 0) {
+ return FAIL;
+ }
+
+ //
+ // Lastly, execute the x.vim file if it exists.
+ //
+ if (fprintf(fd, "%s",
+ "let s:sx = expand(\"<sfile>:p:r\").\"x.vim\"\n"
+ "if filereadable(s:sx)\n"
+ " exe \"source \" . fnameescape(s:sx)\n"
+ "endif\n") < 0) {
+ return FAIL;
+ }
+
+ return OK;
+}
+
+/// ":loadview [nr]"
+void ex_loadview(exarg_T *eap)
+{
+ char *fname = get_view_file(*eap->arg);
+ if (fname != NULL) {
+ if (do_source((char_u *)fname, false, DOSO_NONE) == FAIL) {
+ EMSG2(_(e_notopen), fname);
+ }
+ xfree(fname);
+ }
+}
+
+/// ":mkexrc", ":mkvimrc", ":mkview", ":mksession".
+///
+/// Legacy 'sessionoptions' flags SSOP_UNIX, SSOP_SLASH are always enabled.
+/// - SSOP_UNIX: line-endings are always LF
+/// - SSOP_SLASH: filenames are always written with "/" slash
+void ex_mkrc(exarg_T *eap)
+{
+ FILE *fd;
+ int failed = false;
+ int view_session = false; // :mkview, :mksession
+ int using_vdir = false; // using 'viewdir'?
+ char *viewFile = NULL;
+ unsigned *flagp;
+
+ if (eap->cmdidx == CMD_mksession || eap->cmdidx == CMD_mkview) {
+ view_session = true;
+ }
+
+ // Use the short file name until ":lcd" is used. We also don't use the
+ // short file name when 'acd' is set, that is checked later.
+ did_lcd = false;
+
+ char *fname;
+ // ":mkview" or ":mkview 9": generate file name with 'viewdir'
+ if (eap->cmdidx == CMD_mkview
+ && (*eap->arg == NUL
+ || (ascii_isdigit(*eap->arg) && eap->arg[1] == NUL))) {
+ eap->forceit = true;
+ fname = get_view_file(*eap->arg);
+ if (fname == NULL) {
+ return;
+ }
+ viewFile = fname;
+ using_vdir = true;
+ } else if (*eap->arg != NUL) {
+ fname = (char *)eap->arg;
+ } else if (eap->cmdidx == CMD_mkvimrc) {
+ fname = VIMRC_FILE;
+ } else if (eap->cmdidx == CMD_mksession) {
+ fname = SESSION_FILE;
+ } else {
+ fname = EXRC_FILE;
+ }
+
+ // When using 'viewdir' may have to create the directory.
+ if (using_vdir && !os_isdir(p_vdir)) {
+ vim_mkdir_emsg((const char *)p_vdir, 0755);
+ }
+
+ fd = open_exfile((char_u *)fname, eap->forceit, WRITEBIN);
+ if (fd != NULL) {
+ if (eap->cmdidx == CMD_mkview) {
+ flagp = &vop_flags;
+ } else {
+ flagp = &ssop_flags;
+ }
+
+ // Write the version command for :mkvimrc
+ if (eap->cmdidx == CMD_mkvimrc) {
+ (void)put_line(fd, "version 6.0");
+ }
+
+ if (eap->cmdidx == CMD_mksession) {
+ if (put_line(fd, "let SessionLoad = 1") == FAIL) {
+ failed = true;
+ }
+ }
+
+ if (!view_session || (eap->cmdidx == CMD_mksession
+ && (*flagp & SSOP_OPTIONS))) {
+ failed |= (makemap(fd, NULL) == FAIL
+ || makeset(fd, OPT_GLOBAL, false) == FAIL);
+ }
+
+ if (!failed && view_session) {
+ if (put_line(fd,
+ "let s:so_save = &so | let s:siso_save = &siso"
+ " | set so=0 siso=0") == FAIL) {
+ failed = true;
+ }
+ if (eap->cmdidx == CMD_mksession) {
+ char_u *dirnow; // current directory
+
+ dirnow = xmalloc(MAXPATHL);
+ //
+ // Change to session file's dir.
+ //
+ if (os_dirname(dirnow, MAXPATHL) == FAIL
+ || os_chdir((char *)dirnow) != 0) {
+ *dirnow = NUL;
+ }
+ if (*dirnow != NUL && (ssop_flags & SSOP_SESDIR)) {
+ if (vim_chdirfile((char_u *)fname) == OK) {
+ shorten_fnames(true);
+ }
+ } else if (*dirnow != NUL
+ && (ssop_flags & SSOP_CURDIR) && globaldir != NULL) {
+ if (os_chdir((char *)globaldir) == 0) {
+ shorten_fnames(true);
+ }
+ }
+
+ failed |= (makeopens(fd, dirnow) == FAIL);
+
+ // restore original dir
+ if (*dirnow != NUL && ((ssop_flags & SSOP_SESDIR)
+ || ((ssop_flags & SSOP_CURDIR) && globaldir !=
+ NULL))) {
+ if (os_chdir((char *)dirnow) != 0) {
+ EMSG(_(e_prev_dir));
+ }
+ shorten_fnames(true);
+ // restore original dir
+ if (*dirnow != NUL && ((ssop_flags & SSOP_SESDIR)
+ || ((ssop_flags & SSOP_CURDIR) && globaldir !=
+ NULL))) {
+ if (os_chdir((char *)dirnow) != 0) {
+ EMSG(_(e_prev_dir));
+ }
+ shorten_fnames(true);
+ }
+ }
+ xfree(dirnow);
+ } else {
+ failed |= (put_view(fd, curwin, !using_vdir, flagp, -1) == FAIL);
+ }
+ if (fprintf(fd,
+ "%s",
+ "let &so = s:so_save | let &siso = s:siso_save\n"
+ "doautoall SessionLoadPost\n")
+ < 0) {
+ failed = true;
+ }
+ if (eap->cmdidx == CMD_mksession) {
+ if (fprintf(fd, "unlet SessionLoad\n") < 0) {
+ failed = true;
+ }
+ }
+ }
+ if (put_line(fd, "\" vim: set ft=vim :") == FAIL) {
+ failed = true;
+ }
+
+ failed |= fclose(fd);
+
+ if (failed) {
+ EMSG(_(e_write));
+ } else if (eap->cmdidx == CMD_mksession) {
+ // successful session write - set v:this_session
+ char *const tbuf = xmalloc(MAXPATHL);
+ if (vim_FullName(fname, tbuf, MAXPATHL, false) == OK) {
+ set_vim_var_string(VV_THIS_SESSION, tbuf, -1);
+ }
+ xfree(tbuf);
+ }
+ }
+
+ xfree(viewFile);
+}
+
+/// Get the name of the view file for the current buffer.
+static char *get_view_file(int c)
+{
+ if (curbuf->b_ffname == NULL) {
+ EMSG(_(e_noname));
+ return NULL;
+ }
+ char *sname = (char *)home_replace_save(NULL, curbuf->b_ffname);
+
+ // We want a file name without separators, because we're not going to make
+ // a directory.
+ // "normal" path separator -> "=+"
+ // "=" -> "=="
+ // ":" path separator -> "=-"
+ size_t len = 0;
+ for (char *p = sname; *p; p++) {
+ if (*p == '=' || vim_ispathsep(*p)) {
+ len++;
+ }
+ }
+ char *retval = xmalloc(strlen(sname) + len + STRLEN(p_vdir) + 9);
+ STRCPY(retval, p_vdir);
+ add_pathsep(retval);
+ char *s = retval + strlen(retval);
+ for (char *p = sname; *p; p++) {
+ if (*p == '=') {
+ *s++ = '=';
+ *s++ = '=';
+ } else if (vim_ispathsep(*p)) {
+ *s++ = '=';
+#if defined(BACKSLASH_IN_FILENAME)
+ *s++ = (*p == ':') ? '-' : '+';
+#else
+ *s++ = '+';
+#endif
+ } else {
+ *s++ = *p;
+ }
+ }
+ *s++ = '=';
+ assert(c >= CHAR_MIN && c <= CHAR_MAX);
+ *s++ = (char)c;
+ xstrlcpy(s, ".vim", 5);
+
+ xfree(sname);
+ return retval;
+}
+
+// TODO(justinmk): remove this, not needed after 5ba3cecb68cd.
+int put_eol(FILE *fd)
+{
+ if (putc('\n', fd) < 0) {
+ return FAIL;
+ }
+ return OK;
+}
+
+// TODO(justinmk): remove this, not needed after 5ba3cecb68cd.
+int put_line(FILE *fd, char *s)
+{
+ if (fprintf(fd, "%s\n", s) < 0) {
+ return FAIL;
+ }
+ return OK;
+}
diff --git a/src/nvim/ex_session.h b/src/nvim/ex_session.h
new file mode 100644
index 0000000000..8d3ea5b91a
--- /dev/null
+++ b/src/nvim/ex_session.h
@@ -0,0 +1,13 @@
+#ifndef NVIM_EX_SESSION_H
+#define NVIM_EX_SESSION_H
+
+#include <stdio.h>
+
+#include "nvim/ex_cmds_defs.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "ex_session.h.generated.h"
+#endif
+
+#endif // NVIM_EX_SESSION_H
+
diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c
index d60723c755..1457a1172d 100644
--- a/src/nvim/extmark.c
+++ b/src/nvim/extmark.c
@@ -86,7 +86,7 @@ uint64_t extmark_set(buf_T *buf, uint64_t ns_id, uint64_t id,
extmark_del(buf, ns_id, id);
} else {
// TODO(bfredl): we need to do more if "revising" a decoration mark.
- MarkTreeIter itr[1];
+ MarkTreeIter itr[1] = { 0 };
old_pos = marktree_lookup(buf->b_marktree, old_mark, itr);
assert(itr->node);
if (old_pos.row == row && old_pos.col == col) {
@@ -119,7 +119,7 @@ revised:
static bool extmark_setraw(buf_T *buf, uint64_t mark, int row, colnr_T col)
{
- MarkTreeIter itr[1];
+ MarkTreeIter itr[1] = { 0 };
mtpos_t pos = marktree_lookup(buf->b_marktree, mark, itr);
if (pos.row == -1) {
return false;
@@ -147,7 +147,7 @@ bool extmark_del(buf_T *buf, uint64_t ns_id, uint64_t id)
return false;
}
- MarkTreeIter itr[1];
+ MarkTreeIter itr[1] = { 0 };
mtpos_t pos = marktree_lookup(buf->b_marktree, mark, itr);
assert(pos.row >= 0);
marktree_del_itr(buf->b_marktree, itr, false);
@@ -207,7 +207,7 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id,
delete_set = map_new(uint64_t, uint64_t)();
}
- MarkTreeIter itr[1];
+ MarkTreeIter itr[1] = { 0 };
marktree_itr_get(buf->b_marktree, l_row, l_col, itr);
while (true) {
mtmark_t mark = marktree_itr_current(itr);
@@ -276,7 +276,7 @@ ExtmarkArray extmark_get(buf_T *buf, uint64_t ns_id,
int64_t amount, bool reverse)
{
ExtmarkArray array = KV_INITIAL_VALUE;
- MarkTreeIter itr[1];
+ MarkTreeIter itr[1] = { 0 };
// Find all the marks
marktree_itr_get_ext(buf->b_marktree, (mtpos_t){ l_row, l_col },
itr, reverse, false, NULL);
@@ -396,7 +396,7 @@ void u_extmark_copy(buf_T *buf,
ExtmarkUndoObject undo;
- MarkTreeIter itr[1];
+ MarkTreeIter itr[1] = { 0 };
marktree_itr_get(buf->b_marktree, l_row, l_col, itr);
while (true) {
mtmark_t mark = marktree_itr_current(itr);
@@ -578,6 +578,15 @@ void extmark_splice(buf_T *buf,
}
}
+void extmark_splice_cols(buf_T *buf,
+ int start_row, colnr_T start_col,
+ colnr_T old_col, colnr_T new_col,
+ ExtmarkOp undo)
+{
+ extmark_splice(buf, start_row, start_col,
+ 0, old_col,
+ 0, new_col, undo);
+}
void extmark_move_region(buf_T *buf,
int start_row, colnr_T start_col,
@@ -699,21 +708,30 @@ void bufhl_add_hl_pos_offset(buf_T *buf,
// TODO(bfredl): if decoration had blocky mode, we could avoid this loop
for (linenr_T lnum = pos_start.lnum; lnum <= pos_end.lnum; lnum ++) {
+ int end_off = 0;
if (pos_start.lnum < lnum && lnum < pos_end.lnum) {
- hl_start = offset-1;
- hl_end = MAXCOL;
+ // TODO(bfredl): This is quite ad-hoc, but the space between |num| and
+ // text being highlighted is the indication of \n being part of the
+ // substituted text. But it would be more consistent to highlight
+ // a space _after_ the previous line instead (like highlight EOL list
+ // char)
+ hl_start = MAX(offset-1, 0);
+ end_off = 1;
+ hl_end = 0;
} else if (lnum == pos_start.lnum && lnum < pos_end.lnum) {
hl_start = pos_start.col + offset;
- hl_end = MAXCOL;
+ end_off = 1;
+ hl_end = 0;
} else if (pos_start.lnum < lnum && lnum == pos_end.lnum) {
- hl_start = offset-1;
+ hl_start = MAX(offset-1, 0);
hl_end = pos_end.col + offset;
} else if (pos_start.lnum == lnum && pos_end.lnum == lnum) {
hl_start = pos_start.col + offset;
hl_end = pos_end.col + offset;
}
(void)extmark_add_decoration(buf, (uint64_t)src_id, hl_id,
- (int)lnum-1, hl_start, (int)lnum-1, hl_end,
+ (int)lnum-1, hl_start,
+ (int)lnum-1+end_off, hl_end,
VIRTTEXT_EMPTY);
}
}
@@ -729,7 +747,7 @@ void clear_virttext(VirtText *text)
VirtText *extmark_find_virttext(buf_T *buf, int row, uint64_t ns_id)
{
- MarkTreeIter itr[1];
+ MarkTreeIter itr[1] = { 0 };
marktree_itr_get(buf->b_marktree, row, 0, itr);
while (true) {
mtmark_t mark = marktree_itr_current(itr);
@@ -747,17 +765,17 @@ VirtText *extmark_find_virttext(buf_T *buf, int row, uint64_t ns_id)
return NULL;
}
-
-bool extmark_decorations_reset(buf_T *buf, DecorationState *state)
+bool decorations_redraw_reset(buf_T *buf, DecorationRedrawState *state)
{
state->row = -1;
- return buf->b_extmark_index;
+ kv_size(state->active) = 0;
+ return buf->b_extmark_index || buf->b_luahl;
}
-bool extmark_decorations_start(buf_T *buf, int top_row, DecorationState *state)
+bool decorations_redraw_start(buf_T *buf, int top_row,
+ DecorationRedrawState *state)
{
- kv_size(state->active) = 0;
state->top_row = top_row;
marktree_itr_get(buf->b_marktree, top_row, 0, state->itr);
if (!state->itr->node) {
@@ -780,7 +798,7 @@ bool extmark_decorations_start(buf_T *buf, int top_row, DecorationState *state)
ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index,
start_id, false);
if ((!(mark.id&MARKTREE_END_FLAG) && altpos.row < top_row
- && !kv_size(item->virt_text))
+ && item && !kv_size(item->virt_text))
|| ((mark.id&MARKTREE_END_FLAG) && altpos.row >= top_row)) {
goto next_mark;
}
@@ -808,17 +826,17 @@ next_mark:
return true; // TODO(bfredl): check if available in the region
}
-bool extmark_decorations_line(buf_T *buf, int row, DecorationState *state)
+bool decorations_redraw_line(buf_T *buf, int row, DecorationRedrawState *state)
{
if (state->row == -1) {
- extmark_decorations_start(buf, row, state);
+ decorations_redraw_start(buf, row, state);
}
state->row = row;
state->col_until = -1;
return true; // TODO(bfredl): be more precise
}
-int extmark_decorations_col(buf_T *buf, int col, DecorationState *state)
+int decorations_redraw_col(buf_T *buf, int col, DecorationRedrawState *state)
{
if (col <= state->col_until) {
return state->current;
@@ -845,7 +863,7 @@ int extmark_decorations_col(buf_T *buf, int col, DecorationState *state)
if (endpos.row < mark.row
|| (endpos.row == mark.row && endpos.col <= mark.col)) {
- if (!kv_size(item->virt_text)) {
+ if (item && !kv_size(item->virt_text)) {
goto next_mark;
}
}
@@ -897,9 +915,9 @@ next_mark:
return attr;
}
-VirtText *extmark_decorations_virt_text(buf_T *buf, DecorationState *state)
+VirtText *decorations_redraw_virt_text(buf_T *buf, DecorationRedrawState *state)
{
- extmark_decorations_col(buf, MAXCOL, state);
+ decorations_redraw_col(buf, MAXCOL, state);
for (size_t i = 0; i < kv_size(state->active); i++) {
HlRange item = kv_A(state->active, i);
if (item.start_row == state->row && item.virt_text) {
diff --git a/src/nvim/extmark.h b/src/nvim/extmark.h
index 829cbe0236..b5eb0db3b6 100644
--- a/src/nvim/extmark.h
+++ b/src/nvim/extmark.h
@@ -83,7 +83,7 @@ typedef struct {
int col_until;
int current;
VirtText *virt_text;
-} DecorationState;
+} DecorationRedrawState;
#ifdef INCLUDE_GENERATED_DECLARATIONS
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index 7a46957151..2335aba6dd 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -20,7 +20,7 @@
#include "nvim/cursor.h"
#include "nvim/diff.h"
#include "nvim/edit.h"
-#include "nvim/eval.h"
+#include "nvim/eval/userfunc.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_eval.h"
@@ -62,6 +62,11 @@
#define BUFSIZE 8192 /* size of normal write buffer */
#define SMBUFSIZE 256 /* size of emergency write buffer */
+// For compatibility with libuv < 1.20.0 (tested on 1.18.0)
+#ifndef UV_FS_COPYFILE_FICLONE
+#define UV_FS_COPYFILE_FICLONE 0
+#endif
+
//
// The autocommands are stored in a list for each event.
// Autocommands for the same pattern, that are consecutive, are joined
@@ -407,11 +412,27 @@ readfile(
if (newfile) {
if (apply_autocmds_exarg(EVENT_BUFREADCMD, NULL, sfname,
- FALSE, curbuf, eap))
- return aborting() ? FAIL : OK;
+ false, curbuf, eap)) {
+ int status = OK;
+
+ if (aborting()) {
+ status = FAIL;
+ }
+
+ // The BufReadCmd code usually uses ":read" to get the text and
+ // perhaps ":file" to change the buffer name. But we should
+ // consider this to work like ":edit", thus reset the
+ // BF_NOTEDITED flag. Then ":write" will work to overwrite the
+ // same file.
+ if (status == OK) {
+ curbuf->b_flags &= ~BF_NOTEDITED;
+ }
+ return status;
+ }
} else if (apply_autocmds_exarg(EVENT_FILEREADCMD, sfname, sfname,
- FALSE, NULL, eap))
+ false, NULL, eap)) {
return aborting() ? FAIL : OK;
+ }
curbuf->b_op_start = pos;
}
@@ -2032,14 +2053,16 @@ readfile_linenr(
* Fill "*eap" to force the 'fileencoding', 'fileformat' and 'binary to be
* equal to the buffer "buf". Used for calling readfile().
*/
-void prep_exarg(exarg_T *eap, buf_T *buf)
+void prep_exarg(exarg_T *eap, const buf_T *buf)
+ FUNC_ATTR_NONNULL_ALL
{
- eap->cmd = xmalloc(STRLEN(buf->b_p_ff) + STRLEN(buf->b_p_fenc) + 15);
+ const size_t cmd_len = 15 + STRLEN(buf->b_p_fenc);
+ eap->cmd = xmalloc(cmd_len);
- sprintf((char *)eap->cmd, "e ++ff=%s ++enc=%s", buf->b_p_ff, buf->b_p_fenc);
- eap->force_enc = 14 + (int)STRLEN(buf->b_p_ff);
+ snprintf((char *)eap->cmd, cmd_len, "e ++enc=%s", buf->b_p_fenc);
+ eap->force_enc = 8;
eap->bad_char = buf->b_bad_char;
- eap->force_ff = 7;
+ eap->force_ff = *buf->b_p_ff;
eap->force_bin = buf->b_p_bin ? FORCE_BIN : FORCE_NOBIN;
eap->read_edit = FALSE;
@@ -6587,7 +6610,7 @@ static int autocmd_nested = FALSE;
/// Execute autocommands for "event" and file name "fname".
///
-/// @param event event that occured
+/// @param event event that occurred
/// @param fname filename, NULL or empty means use actual file name
/// @param fname_io filename to use for <afile> on cmdline
/// @param force When true, ignore autocmd_busy
@@ -6604,7 +6627,7 @@ bool apply_autocmds(event_T event, char_u *fname, char_u *fname_io, bool force,
/// Like apply_autocmds(), but with extra "eap" argument. This takes care of
/// setting v:filearg.
///
-/// @param event event that occured
+/// @param event event that occurred
/// @param fname NULL or empty means use actual file name
/// @param fname_io fname to use for <afile> on cmdline
/// @param force When true, ignore autocmd_busy
@@ -6624,7 +6647,7 @@ static bool apply_autocmds_exarg(event_T event, char_u *fname, char_u *fname_io,
/// conditional, no autocommands are executed. If otherwise the autocommands
/// cause the script to be aborted, retval is set to FAIL.
///
-/// @param event event that occured
+/// @param event event that occurred
/// @param fname NULL or empty means use actual file name
/// @param fname_io fname to use for <afile> on cmdline
/// @param force When true, ignore autocmd_busy
@@ -6684,7 +6707,7 @@ bool has_event(event_T event) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
/// Execute autocommands for "event" and file name "fname".
///
-/// @param event event that occured
+/// @param event event that occurred
/// @param fname filename, NULL or empty means use actual file name
/// @param fname_io filename to use for <afile> on cmdline,
/// NULL means use `fname`.
@@ -7197,8 +7220,8 @@ char_u *getnextac(int c, void *cookie, int indent, bool do_concat)
/// To account for buffer-local autocommands, function needs to know
/// in which buffer the file will be opened.
///
-/// @param event event that occured.
-/// @param sfname filename the event occured in.
+/// @param event event that occurred.
+/// @param sfname filename the event occurred in.
/// @param buf buffer the file is open in
bool has_autocmd(event_T event, char_u *sfname, buf_T *buf)
FUNC_ATTR_WARN_UNUSED_RESULT
diff --git a/src/nvim/fold.c b/src/nvim/fold.c
index 0b14a6affb..61a85171e8 100644
--- a/src/nvim/fold.c
+++ b/src/nvim/fold.c
@@ -19,6 +19,7 @@
#include "nvim/diff.h"
#include "nvim/eval.h"
#include "nvim/ex_docmd.h"
+#include "nvim/ex_session.h"
#include "nvim/func_attr.h"
#include "nvim/indent.h"
#include "nvim/buffer_updates.h"
@@ -538,12 +539,10 @@ int foldManualAllowed(int create)
return FALSE;
}
-/* foldCreate() {{{2 */
-/*
- * Create a fold from line "start" to line "end" (inclusive) in the current
- * window.
- */
-void foldCreate(linenr_T start, linenr_T end)
+// foldCreate() {{{2
+/// Create a fold from line "start" to line "end" (inclusive) in the current
+/// window.
+void foldCreate(win_T *wp, linenr_T start, linenr_T end)
{
fold_T *fp;
garray_T *gap;
@@ -564,16 +563,16 @@ void foldCreate(linenr_T start, linenr_T end)
end_rel = end;
}
- /* When 'foldmethod' is "marker" add markers, which creates the folds. */
- if (foldmethodIsMarker(curwin)) {
- foldCreateMarkers(start, end);
+ // When 'foldmethod' is "marker" add markers, which creates the folds.
+ if (foldmethodIsMarker(wp)) {
+ foldCreateMarkers(wp, start, end);
return;
}
- checkupdate(curwin);
+ checkupdate(wp);
- /* Find the place to insert the new fold. */
- gap = &curwin->w_folds;
+ // Find the place to insert the new fold
+ gap = &wp->w_folds;
for (;; ) {
if (!foldFind(gap, start_rel, &fp))
break;
@@ -583,12 +582,14 @@ void foldCreate(linenr_T start, linenr_T end)
start_rel -= fp->fd_top;
end_rel -= fp->fd_top;
if (use_level || fp->fd_flags == FD_LEVEL) {
- use_level = TRUE;
- if (level >= curwin->w_p_fdl)
- closed = TRUE;
- } else if (fp->fd_flags == FD_CLOSED)
- closed = TRUE;
- ++level;
+ use_level = true;
+ if (level >= wp->w_p_fdl) {
+ closed = true;
+ }
+ } else if (fp->fd_flags == FD_CLOSED) {
+ closed = true;
+ }
+ level++;
} else {
/* This fold and new fold overlap: Insert here and move some folds
* inside the new fold. */
@@ -641,26 +642,28 @@ void foldCreate(linenr_T start, linenr_T end)
/* We want the new fold to be closed. If it would remain open because
* of using 'foldlevel', need to adjust fd_flags of containing folds.
*/
- if (use_level && !closed && level < curwin->w_p_fdl)
+ if (use_level && !closed && level < wp->w_p_fdl) {
closeFold(start, 1L);
- if (!use_level)
- curwin->w_fold_manual = true;
+ }
+ if (!use_level) {
+ wp->w_fold_manual = true;
+ }
fp->fd_flags = FD_CLOSED;
fp->fd_small = kNone;
- /* redraw */
- changed_window_setting();
+ // redraw
+ changed_window_setting_win(wp);
}
}
-/* deleteFold() {{{2 */
-/*
- * Delete a fold at line "start" in the current window.
- * When "end" is not 0, delete all folds from "start" to "end".
- * When "recursive" is TRUE delete recursively.
- */
+// deleteFold() {{{2
+/// @param start delete all folds from start to end when not 0
+/// @param end delete all folds from start to end when not 0
+/// @param recursive delete recursively if true
+/// @param had_visual true when Visual selection used
void deleteFold(
+ win_T *const wp,
const linenr_T start,
const linenr_T end,
const int recursive,
@@ -677,11 +680,11 @@ void deleteFold(
linenr_T first_lnum = MAXLNUM;
linenr_T last_lnum = 0;
- checkupdate(curwin);
+ checkupdate(wp);
while (lnum <= end) {
// Find the deepest fold for "start".
- garray_T *gap = &curwin->w_folds;
+ garray_T *gap = &wp->w_folds;
garray_T *found_ga = NULL;
linenr_T lnum_off = 0;
bool use_level = false;
@@ -693,10 +696,11 @@ void deleteFold(
found_fp = fp;
found_off = lnum_off;
- /* if "lnum" is folded, don't check nesting */
- if (check_closed(curwin, fp, &use_level, level,
- &maybe_small, lnum_off))
+ // if "lnum" is folded, don't check nesting
+ if (check_closed(wp, fp, &use_level, level,
+ &maybe_small, lnum_off)) {
break;
+ }
/* check nested folds */
gap = &fp->fd_nested;
@@ -708,34 +712,41 @@ void deleteFold(
} else {
lnum = found_fp->fd_top + found_fp->fd_len + found_off;
- if (foldmethodIsManual(curwin))
- deleteFoldEntry(found_ga,
- (int)(found_fp - (fold_T *)found_ga->ga_data), recursive);
- else {
- if (first_lnum > found_fp->fd_top + found_off)
+ if (foldmethodIsManual(wp)) {
+ deleteFoldEntry(wp, found_ga,
+ (int)(found_fp - (fold_T *)found_ga->ga_data),
+ recursive);
+ } else {
+ if (first_lnum > found_fp->fd_top + found_off) {
first_lnum = found_fp->fd_top + found_off;
- if (last_lnum < lnum)
+ }
+ if (last_lnum < lnum) {
last_lnum = lnum;
- if (!did_one)
- parseMarker(curwin);
- deleteFoldMarkers(found_fp, recursive, found_off);
+ }
+ if (!did_one) {
+ parseMarker(wp);
+ }
+ deleteFoldMarkers(wp, found_fp, recursive, found_off);
}
did_one = true;
- /* redraw window */
- changed_window_setting();
+ // redraw window
+ changed_window_setting_win(wp);
}
}
if (!did_one) {
EMSG(_(e_nofold));
- /* Force a redraw to remove the Visual highlighting. */
- if (had_visual)
- redraw_curbuf_later(INVERTED);
- } else
- /* Deleting markers may make cursor column invalid. */
- check_cursor_col();
+ // Force a redraw to remove the Visual highlighting.
+ if (had_visual) {
+ redraw_buf_later(wp->w_buffer, INVERTED);
+ }
+ } else {
+ // Deleting markers may make cursor column invalid
+ check_cursor_col_win(wp);
+ }
if (last_lnum > 0) {
+ // TODO(teto): pass the buffer
changed_lines(first_lnum, (colnr_T)0, last_lnum, 0L, false);
// send one nvim_buf_lines_event at the end
@@ -744,7 +755,7 @@ void deleteFold(
// the modification of the *first* line of the fold, but we send through a
// notification that includes every line that was part of the fold
int64_t num_changed = last_lnum - first_lnum;
- buf_updates_send_changes(curbuf, first_lnum, num_changed,
+ buf_updates_send_changes(wp->w_buffer, first_lnum, num_changed,
num_changed, true);
}
}
@@ -1047,7 +1058,7 @@ void cloneFoldGrowArray(garray_T *from, garray_T *to)
* the first fold below it (careful: it can be beyond the end of the array!).
* Returns FALSE when there is no fold that contains "lnum".
*/
-static int foldFind(garray_T *gap, linenr_T lnum, fold_T **fpp)
+static int foldFind(const garray_T *gap, linenr_T lnum, fold_T **fpp)
{
linenr_T low, high;
fold_T *fp;
@@ -1296,7 +1307,7 @@ static void foldOpenNested(fold_T *fpr)
// Delete fold "idx" from growarray "gap".
// When "recursive" is true also delete all the folds contained in it.
// When "recursive" is false contained folds are moved one level up.
-static void deleteFoldEntry(garray_T *const gap, const int idx,
+static void deleteFoldEntry(win_T *const wp, garray_T *const gap, const int idx,
const bool recursive)
{
fold_T *fp = (fold_T *)gap->ga_data + idx;
@@ -1362,13 +1373,17 @@ void foldMarkAdjust(win_T *wp, linenr_T line1, linenr_T line2, long amount, long
line2 = line1 - amount_after - 1;
/* If appending a line in Insert mode, it should be included in the fold
* just above the line. */
- if ((State & INSERT) && amount == (linenr_T)1 && line2 == MAXLNUM)
- --line1;
- foldMarkAdjustRecurse(&wp->w_folds, line1, line2, amount, amount_after);
+ if ((State & INSERT) && amount == (linenr_T)1 && line2 == MAXLNUM) {
+ line1--;
+ }
+ foldMarkAdjustRecurse(wp, &wp->w_folds, line1, line2, amount, amount_after);
}
-/* foldMarkAdjustRecurse() {{{2 */
-static void foldMarkAdjustRecurse(garray_T *gap, linenr_T line1, linenr_T line2, long amount, long amount_after)
+// foldMarkAdjustRecurse() {{{2
+static void foldMarkAdjustRecurse(
+ win_T *wp, garray_T *gap,
+ linenr_T line1, linenr_T line2, long amount, long amount_after
+)
{
fold_T *fp;
linenr_T last;
@@ -1416,7 +1431,7 @@ static void foldMarkAdjustRecurse(garray_T *gap, linenr_T line1, linenr_T line2,
// 4. fold completely contained in range
if (amount == MAXLNUM) {
// Deleting lines: delete the fold completely
- deleteFoldEntry(gap, i, true);
+ deleteFoldEntry(wp, gap, i, true);
i--; // adjust index for deletion
fp--;
} else {
@@ -1424,9 +1439,9 @@ static void foldMarkAdjustRecurse(garray_T *gap, linenr_T line1, linenr_T line2,
}
} else {
if (fp->fd_top < top) {
- /* 2 or 3: need to correct nested folds too */
- foldMarkAdjustRecurse(&fp->fd_nested, line1 - fp->fd_top,
- line2 - fp->fd_top, amount, amount_after);
+ // 2 or 3: need to correct nested folds too
+ foldMarkAdjustRecurse(wp, &fp->fd_nested, line1 - fp->fd_top,
+ line2 - fp->fd_top, amount, amount_after);
if (last <= line2) {
/* 2. fold contains line1, line2 is below fold */
if (amount == MAXLNUM)
@@ -1441,13 +1456,13 @@ static void foldMarkAdjustRecurse(garray_T *gap, linenr_T line1, linenr_T line2,
/* 5. fold is below line1 and contains line2; need to
* correct nested folds too */
if (amount == MAXLNUM) {
- foldMarkAdjustRecurse(&fp->fd_nested, line1 - fp->fd_top,
+ foldMarkAdjustRecurse(wp, &fp->fd_nested, line1 - fp->fd_top,
line2 - fp->fd_top, amount,
amount_after + (fp->fd_top - top));
fp->fd_len -= line2 - fp->fd_top + 1;
fp->fd_top = line1;
} else {
- foldMarkAdjustRecurse(&fp->fd_nested, line1 - fp->fd_top,
+ foldMarkAdjustRecurse(wp, &fp->fd_nested, line1 - fp->fd_top,
line2 - fp->fd_top, amount,
amount_after - amount);
fp->fd_len += amount_after - amount;
@@ -1464,10 +1479,10 @@ static void foldMarkAdjustRecurse(garray_T *gap, linenr_T line1, linenr_T line2,
* Get the lowest 'foldlevel' value that makes the deepest nested fold in the
* current window open.
*/
-int getDeepestNesting(void)
+int getDeepestNesting(win_T *wp)
{
- checkupdate(curwin);
- return getDeepestNestingRecurse(&curwin->w_folds);
+ checkupdate(wp);
+ return getDeepestNestingRecurse(&wp->w_folds);
}
static int getDeepestNestingRecurse(garray_T *gap)
@@ -1486,17 +1501,22 @@ static int getDeepestNestingRecurse(garray_T *gap)
return maxlevel;
}
-/* check_closed() {{{2 */
-/*
- * Check if a fold is closed and update the info needed to check nested folds.
- */
+// check_closed() {{{2
+/// Check if a fold is closed and update the info needed to check nested folds.
+///
+/// @param[in,out] use_levelp true: outer fold had FD_LEVEL
+/// @param[in,out] fp fold to check
+/// @param level folding depth
+/// @param[out] maybe_smallp true: outer this had fd_small == kNone
+/// @param lnum_off line number offset for fp->fd_top
+/// @return true if fold is closed
static bool check_closed(
- win_T *const win,
+ win_T *const wp,
fold_T *const fp,
- bool *const use_levelp, // true: outer fold had FD_LEVEL
- const int level, // folding depth
- bool *const maybe_smallp, // true: outer this had fd_small == kNone
- const linenr_T lnum_off // line number offset for fp->fd_top
+ bool *const use_levelp,
+ const int level,
+ bool *const maybe_smallp,
+ const linenr_T lnum_off
)
{
bool closed = false;
@@ -1505,7 +1525,7 @@ static bool check_closed(
* fold and all folds it contains depend on 'foldlevel'. */
if (*use_levelp || fp->fd_flags == FD_LEVEL) {
*use_levelp = true;
- if (level >= win->w_p_fdl) {
+ if (level >= wp->w_p_fdl) {
closed = true;
}
} else if (fp->fd_flags == FD_CLOSED) {
@@ -1520,7 +1540,7 @@ static bool check_closed(
if (*maybe_smallp) {
fp->fd_small = kNone;
}
- checkSmall(win, fp, lnum_off);
+ checkSmall(wp, fp, lnum_off);
if (fp->fd_small == kTrue) {
closed = false;
}
@@ -1528,10 +1548,9 @@ static bool check_closed(
return closed;
}
-/* checkSmall() {{{2 */
-/*
- * Update fd_small field of fold "fp".
- */
+// checkSmall() {{{2
+/// Update fd_small field of fold "fp".
+/// @param lnum_off offset for fp->fd_top
static void
checkSmall(
win_T *const wp,
@@ -1543,13 +1562,13 @@ checkSmall(
// Mark any nested folds to maybe-small
setSmallMaybe(&fp->fd_nested);
- if (fp->fd_len > curwin->w_p_fml) {
+ if (fp->fd_len > wp->w_p_fml) {
fp->fd_small = kFalse;
} else {
int count = 0;
for (int n = 0; n < fp->fd_len; n++) {
count += plines_win_nofold(wp, fp->fd_top + lnum_off + n);
- if (count > curwin->w_p_fml) {
+ if (count > wp->w_p_fml) {
fp->fd_small = kFalse;
return;
}
@@ -1574,42 +1593,45 @@ static void setSmallMaybe(garray_T *gap)
* Create a fold from line "start" to line "end" (inclusive) in the current
* window by adding markers.
*/
-static void foldCreateMarkers(linenr_T start, linenr_T end)
+static void foldCreateMarkers(win_T *wp, linenr_T start, linenr_T end)
{
- if (!MODIFIABLE(curbuf)) {
+ buf_T *buf = wp->w_buffer;
+ if (!MODIFIABLE(buf)) {
EMSG(_(e_modifiable));
return;
}
- parseMarker(curwin);
+ parseMarker(wp);
- foldAddMarker(start, curwin->w_p_fmr, foldstartmarkerlen);
- foldAddMarker(end, foldendmarker, foldendmarkerlen);
+ foldAddMarker(buf, start, wp->w_p_fmr, foldstartmarkerlen);
+ foldAddMarker(buf, end, foldendmarker, foldendmarkerlen);
/* Update both changes here, to avoid all folds after the start are
* changed when the start marker is inserted and the end isn't. */
+ // TODO(teto): pass the buffer
changed_lines(start, (colnr_T)0, end, 0L, false);
// Note: foldAddMarker() may not actually change start and/or end if
// u_save() is unable to save the buffer line, but we send the
// nvim_buf_lines_event anyway since it won't do any harm.
int64_t num_changed = 1 + end - start;
- buf_updates_send_changes(curbuf, start, num_changed, num_changed, true);
+ buf_updates_send_changes(buf, start, num_changed, num_changed, true);
}
/* foldAddMarker() {{{2 */
/*
* Add "marker[markerlen]" in 'commentstring' to line "lnum".
*/
-static void foldAddMarker(linenr_T lnum, const char_u *marker, size_t markerlen)
+static void foldAddMarker(
+ buf_T *buf, linenr_T lnum, const char_u *marker, size_t markerlen)
{
- char_u *cms = curbuf->b_p_cms;
+ char_u *cms = buf->b_p_cms;
char_u *line;
char_u *newline;
- char_u *p = (char_u *)strstr((char *)curbuf->b_p_cms, "%s");
+ char_u *p = (char_u *)strstr((char *)buf->b_p_cms, "%s");
bool line_is_comment = false;
// Allocate a new line: old-line + 'cms'-start + marker + 'cms'-end
- line = ml_get(lnum);
+ line = ml_get_buf(buf, lnum, false);
size_t line_len = STRLEN(line);
size_t added = 0;
@@ -1628,11 +1650,10 @@ static void foldAddMarker(linenr_T lnum, const char_u *marker, size_t markerlen)
STRCPY(newline + line_len + (p - cms) + markerlen, p + 2);
added = markerlen + STRLEN(cms)-2;
}
- ml_replace(lnum, newline, false);
+ ml_replace_buf(buf, lnum, newline, false);
if (added) {
- extmark_splice(curbuf, (int)lnum-1, (int)line_len,
- 0, 0,
- 0, (int)added, kExtmarkUndo);
+ extmark_splice_cols(buf, (int)lnum-1, (int)line_len,
+ 0, (int)added, kExtmarkUndo);
}
}
}
@@ -1643,20 +1664,22 @@ static void foldAddMarker(linenr_T lnum, const char_u *marker, size_t markerlen)
*/
static void
deleteFoldMarkers(
+ win_T *wp,
fold_T *fp,
int recursive,
linenr_T lnum_off // offset for fp->fd_top
)
{
if (recursive) {
- for (int i = 0; i < fp->fd_nested.ga_len; ++i) {
- deleteFoldMarkers((fold_T *)fp->fd_nested.ga_data + i, TRUE,
+ for (int i = 0; i < fp->fd_nested.ga_len; i++) {
+ deleteFoldMarkers(wp, (fold_T *)fp->fd_nested.ga_data + i, true,
lnum_off + fp->fd_top);
}
}
- foldDelMarker(fp->fd_top + lnum_off, curwin->w_p_fmr, foldstartmarkerlen);
- foldDelMarker(fp->fd_top + lnum_off + fp->fd_len - 1, foldendmarker,
- foldendmarkerlen);
+ foldDelMarker(wp->w_buffer, fp->fd_top+lnum_off, wp->w_p_fmr,
+ foldstartmarkerlen);
+ foldDelMarker(wp->w_buffer, fp->fd_top + lnum_off + fp->fd_len - 1,
+ foldendmarker, foldendmarkerlen);
}
// foldDelMarker() {{{2
@@ -1665,18 +1688,20 @@ deleteFoldMarkers(
// Delete 'commentstring' if it matches.
// If the marker is not found, there is no error message. Could be a missing
// close-marker.
-static void foldDelMarker(linenr_T lnum, char_u *marker, size_t markerlen)
+static void foldDelMarker(
+ buf_T *buf, linenr_T lnum, char_u *marker, size_t markerlen
+)
{
char_u *newline;
- char_u *cms = curbuf->b_p_cms;
+ char_u *cms = buf->b_p_cms;
char_u *cms2;
// end marker may be missing and fold extends below the last line
- if (lnum > curbuf->b_ml.ml_line_count) {
+ if (lnum > buf->b_ml.ml_line_count) {
return;
}
- char_u *line = ml_get(lnum);
- for (char_u *p = line; *p != NUL; ++p) {
+ char_u *line = ml_get_buf(buf, lnum, false);
+ for (char_u *p = line; *p != NUL; p++) {
if (STRNCMP(p, marker, markerlen) != 0) {
continue;
}
@@ -1700,10 +1725,10 @@ static void foldDelMarker(linenr_T lnum, char_u *marker, size_t markerlen)
assert(p >= line);
memcpy(newline, line, (size_t)(p - line));
STRCPY(newline + (p - line), p + len);
- ml_replace(lnum, newline, false);
- extmark_splice(curbuf, (int)lnum-1, (int)(p - line),
- 0, (int)len,
- 0, 0, kExtmarkUndo);
+ ml_replace_buf(buf, lnum, newline, false);
+ extmark_splice_cols(buf, (int)lnum-1, (int)(p - line),
+ (int)len,
+ 0, kExtmarkUndo);
}
break;
}
@@ -1891,12 +1916,12 @@ void foldtext_cleanup(char_u *str)
/* foldUpdateIEMS() {{{2 */
/*
* Update the folding for window "wp", at least from lines "top" to "bot".
- * Return TRUE if any folds did change.
+ * IEMS = "Indent Expr Marker Syntax"
*/
static void foldUpdateIEMS(win_T *const wp, linenr_T top, linenr_T bot)
{
fline_T fline;
- void (*getlevel)(fline_T *);
+ LevelGetter getlevel = NULL;
fold_T *fp;
/* Avoid problems when being called recursively. */
@@ -2078,8 +2103,8 @@ static void foldUpdateIEMS(win_T *const wp, linenr_T top, linenr_T bot)
}
}
- /* There can't be any folds from start until end now. */
- foldRemove(&wp->w_folds, start, end);
+ // There can't be any folds from start until end now.
+ foldRemove(wp, &wp->w_folds, start, end);
/* If some fold changed, need to redraw and position cursor. */
if (fold_changed && wp->w_p_fen)
@@ -2259,12 +2284,12 @@ static linenr_T foldUpdateIEMSRecurse(
if (fp->fd_top > firstlnum) {
// We will move the start of this fold up, hence we move all
// nested folds (with relative line numbers) down.
- foldMarkAdjustRecurse(&fp->fd_nested,
+ foldMarkAdjustRecurse(flp->wp, &fp->fd_nested,
(linenr_T)0, (linenr_T)MAXLNUM,
(long)(fp->fd_top - firstlnum), 0L);
} else {
// Will move fold down, move nested folds relatively up.
- foldMarkAdjustRecurse(&fp->fd_nested,
+ foldMarkAdjustRecurse(flp->wp, &fp->fd_nested,
(linenr_T)0,
(long)(firstlnum - fp->fd_top - 1),
(linenr_T)MAXLNUM,
@@ -2294,10 +2319,10 @@ static linenr_T foldUpdateIEMSRecurse(
breakstart = flp->lnum;
breakend = flp->lnum;
}
- foldRemove(&fp->fd_nested, breakstart - fp->fd_top,
+ foldRemove(flp->wp, &fp->fd_nested, breakstart - fp->fd_top,
breakend - fp->fd_top);
i = (int)(fp - (fold_T *)gap->ga_data);
- foldSplit(gap, i, breakstart, breakend - 1);
+ foldSplit(flp->wp->w_buffer, gap, i, breakstart, breakend - 1);
fp = (fold_T *)gap->ga_data + i + 1;
/* If using the "marker" or "syntax" method, we
* need to continue until the end of the fold is
@@ -2313,7 +2338,7 @@ static linenr_T foldUpdateIEMSRecurse(
if (i != 0) {
fp2 = fp - 1;
if (fp2->fd_top + fp2->fd_len == fp->fd_top) {
- foldMerge(fp2, gap, fp);
+ foldMerge(flp->wp, fp2, gap, fp);
fp = fp2;
}
}
@@ -2324,12 +2349,13 @@ static linenr_T foldUpdateIEMSRecurse(
// A fold that starts at or after startlnum and stops
// before the new fold must be deleted. Continue
// looking for the next one.
- deleteFoldEntry(gap, (int)(fp - (fold_T *)gap->ga_data), true);
+ deleteFoldEntry(flp->wp, gap,
+ (int)(fp - (fold_T *)gap->ga_data), true);
} else {
/* A fold has some lines above startlnum, truncate it
* to stop just above startlnum. */
fp->fd_len = startlnum - fp->fd_top;
- foldMarkAdjustRecurse(&fp->fd_nested,
+ foldMarkAdjustRecurse(flp->wp, &fp->fd_nested,
(linenr_T)fp->fd_len, (linenr_T)MAXLNUM,
(linenr_T)MAXLNUM, 0L);
fold_changed = true;
@@ -2458,8 +2484,8 @@ static linenr_T foldUpdateIEMSRecurse(
// Delete contained folds from the end of the last one found until where
// we stopped looking.
- foldRemove(&fp->fd_nested, startlnum2 - fp->fd_top,
- flp->lnum - 1 - fp->fd_top);
+ foldRemove(flp->wp, &fp->fd_nested, startlnum2 - fp->fd_top,
+ flp->lnum - 1 - fp->fd_top);
if (lvl < level) {
// End of fold found, update the length when it got shorter.
@@ -2477,7 +2503,7 @@ static linenr_T foldUpdateIEMSRecurse(
// indent or expr method: split fold to create a new one
// below bot
i = (int)(fp - (fold_T *)gap->ga_data);
- foldSplit(gap, i, flp->lnum, bot);
+ foldSplit(flp->wp->w_buffer, gap, i, flp->lnum, bot);
fp = (fold_T *)gap->ga_data + i;
}
} else {
@@ -2495,23 +2521,23 @@ static linenr_T foldUpdateIEMSRecurse(
break;
if (fp2->fd_top + fp2->fd_len > flp->lnum) {
if (fp2->fd_top < flp->lnum) {
- /* Make fold that includes lnum start at lnum. */
- foldMarkAdjustRecurse(&fp2->fd_nested,
- (linenr_T)0, (long)(flp->lnum - fp2->fd_top - 1),
- (linenr_T)MAXLNUM, (long)(fp2->fd_top - flp->lnum));
+ // Make fold that includes lnum start at lnum.
+ foldMarkAdjustRecurse(flp->wp, &fp2->fd_nested,
+ (linenr_T)0, (long)(flp->lnum - fp2->fd_top - 1),
+ (linenr_T)MAXLNUM, (long)(fp2->fd_top-flp->lnum));
fp2->fd_len -= flp->lnum - fp2->fd_top;
fp2->fd_top = flp->lnum;
fold_changed = true;
}
if (lvl >= level) {
- /* merge new fold with existing fold that follows */
- foldMerge(fp, gap, fp2);
+ // merge new fold with existing fold that follows
+ foldMerge(flp->wp, fp, gap, fp2);
}
break;
}
fold_changed = true;
- deleteFoldEntry(gap, (int)(fp2 - (fold_T *)gap->ga_data), true);
+ deleteFoldEntry(flp->wp, gap, (int)(fp2 - (fold_T *)gap->ga_data), true);
}
/* Need to redraw the lines we inspected, which might be further down than
@@ -2547,8 +2573,10 @@ static void foldInsert(garray_T *gap, int i)
* The caller must first have taken care of any nested folds from "top" to
* "bot"!
*/
-static void foldSplit(garray_T *const gap, const int i, const linenr_T top,
- const linenr_T bot)
+static void foldSplit(buf_T *buf, garray_T *const gap,
+ const int i, const linenr_T top,
+ const linenr_T bot
+ )
{
fold_T *fp2;
@@ -2603,7 +2631,9 @@ static void foldSplit(garray_T *const gap, const int i, const linenr_T top,
* 5: made to start below "bot".
* 6: not changed
*/
-static void foldRemove(garray_T *gap, linenr_T top, linenr_T bot)
+static void foldRemove(
+ win_T *const wp, garray_T *gap, linenr_T top, linenr_T bot
+)
{
fold_T *fp = NULL;
@@ -2615,10 +2645,11 @@ static void foldRemove(garray_T *gap, linenr_T top, linenr_T bot)
// Find fold that includes top or a following one.
if (foldFind(gap, top, &fp) && fp->fd_top < top) {
// 2: or 3: need to delete nested folds
- foldRemove(&fp->fd_nested, top - fp->fd_top, bot - fp->fd_top);
+ foldRemove(wp, &fp->fd_nested, top - fp->fd_top, bot - fp->fd_top);
if (fp->fd_top + fp->fd_len - 1 > bot) {
// 3: need to split it.
- foldSplit(gap, (int)(fp - (fold_T *)gap->ga_data), top, bot);
+ foldSplit(wp->w_buffer, gap,
+ (int)(fp - (fold_T *)gap->ga_data), top, bot);
} else {
// 2: truncate fold at "top".
fp->fd_len = top - fp->fd_top;
@@ -2636,7 +2667,8 @@ static void foldRemove(garray_T *gap, linenr_T top, linenr_T bot)
fold_changed = true;
if (fp->fd_top + fp->fd_len - 1 > bot) {
// 5: Make fold that includes bot start below bot.
- foldMarkAdjustRecurse(&fp->fd_nested,
+ foldMarkAdjustRecurse(
+ wp, &fp->fd_nested,
(linenr_T)0, (long)(bot - fp->fd_top),
(linenr_T)MAXLNUM, (long)(fp->fd_top - bot - 1));
fp->fd_len -= bot - fp->fd_top + 1;
@@ -2645,7 +2677,7 @@ static void foldRemove(garray_T *gap, linenr_T top, linenr_T bot)
}
// 4: Delete completely contained fold.
- deleteFoldEntry(gap, (int)(fp - (fold_T *)gap->ga_data), true);
+ deleteFoldEntry(wp, gap, (int)(fp - (fold_T *)gap->ga_data), true);
}
}
}
@@ -2697,19 +2729,22 @@ static void foldReverseOrder(
// 8. truncated below dest and shifted up.
// 9. shifted up
// 10. not changed
-static void truncate_fold(fold_T *fp, linenr_T end)
+static void truncate_fold(win_T *const wp, fold_T *fp, linenr_T end)
{
// I want to stop *at here*, foldRemove() stops *above* top
end += 1;
- foldRemove(&fp->fd_nested, end - fp->fd_top, MAXLNUM);
+ foldRemove(wp, &fp->fd_nested, end - fp->fd_top, MAXLNUM);
fp->fd_len = end - fp->fd_top;
}
#define FOLD_END(fp) ((fp)->fd_top + (fp)->fd_len - 1)
#define VALID_FOLD(fp, gap) ((fp) < ((fold_T *)(gap)->ga_data + (gap)->ga_len))
#define FOLD_INDEX(fp, gap) ((size_t)(fp - ((fold_T *)(gap)->ga_data)))
-void foldMoveRange(garray_T *gap, const linenr_T line1, const linenr_T line2,
- const linenr_T dest)
+void foldMoveRange(
+ win_T *const wp, garray_T *gap,
+ const linenr_T line1, const linenr_T line2,
+ const linenr_T dest
+)
{
fold_T *fp;
const linenr_T range_len = line2 - line1 + 1;
@@ -2720,20 +2755,20 @@ void foldMoveRange(garray_T *gap, const linenr_T line1, const linenr_T line2,
if (FOLD_END(fp) > dest) {
// Case 4 -- don't have to change this fold, but have to move nested
// folds.
- foldMoveRange(&fp->fd_nested, line1 - fp->fd_top, line2 -
+ foldMoveRange(wp, &fp->fd_nested, line1 - fp->fd_top, line2 -
fp->fd_top, dest - fp->fd_top);
return;
} else if (FOLD_END(fp) > line2) {
// Case 3 -- Remove nested folds between line1 and line2 & reduce the
// length of fold by "range_len".
// Folds after this one must be dealt with.
- foldMarkAdjustRecurse(&fp->fd_nested, line1 - fp->fd_top,
+ foldMarkAdjustRecurse(wp, &fp->fd_nested, line1 - fp->fd_top,
line2 - fp->fd_top, MAXLNUM, -range_len);
fp->fd_len -= range_len;
} else {
// Case 2 -- truncate fold *above* line1.
// Folds after this one must be dealt with.
- truncate_fold(fp, line1 - 1);
+ truncate_fold(wp, fp, line1 - 1);
}
// Look at the next fold, and treat that one as if it were the first after
// "line1" (because now it is).
@@ -2751,13 +2786,13 @@ void foldMoveRange(garray_T *gap, const linenr_T line1, const linenr_T line2,
}
if (VALID_FOLD(fp, gap) && fp->fd_top <= dest) {
// Case 8. -- ensure truncated at dest, shift up
- truncate_fold(fp, dest);
+ truncate_fold(wp, fp, dest);
fp->fd_top -= range_len;
}
return;
} else if (FOLD_END(fp) > dest) {
// Case 7 -- remove nested folds and shrink
- foldMarkAdjustRecurse(&fp->fd_nested, line2 + 1 - fp->fd_top,
+ foldMarkAdjustRecurse(wp, &fp->fd_nested, line2 + 1 - fp->fd_top,
dest - fp->fd_top, MAXLNUM, -move_len);
fp->fd_len -= move_len;
fp->fd_top += move_len;
@@ -2773,7 +2808,7 @@ void foldMoveRange(garray_T *gap, const linenr_T line1, const linenr_T line2,
// 5, or 6
if (FOLD_END(fp) > line2) {
// 6, truncate before moving
- truncate_fold(fp, line2);
+ truncate_fold(wp, fp, line2);
}
fp->fd_top += move_len;
continue;
@@ -2785,7 +2820,7 @@ void foldMoveRange(garray_T *gap, const linenr_T line1, const linenr_T line2,
}
if (FOLD_END(fp) > dest) {
- truncate_fold(fp, dest);
+ truncate_fold(wp, fp, dest);
}
fp->fd_top -= range_len;
@@ -2817,7 +2852,7 @@ void foldMoveRange(garray_T *gap, const linenr_T line1, const linenr_T line2,
* The resulting fold is "fp1", nested folds are moved from "fp2" to "fp1".
* Fold entry "fp2" in "gap" is deleted.
*/
-static void foldMerge(fold_T *fp1, garray_T *gap, fold_T *fp2)
+static void foldMerge(win_T *const wp, fold_T *fp1, garray_T *gap, fold_T *fp2)
{
fold_T *fp3;
fold_T *fp4;
@@ -2827,8 +2862,9 @@ static void foldMerge(fold_T *fp1, garray_T *gap, fold_T *fp2)
/* If the last nested fold in fp1 touches the first nested fold in fp2,
* merge them recursively. */
- if (foldFind(gap1, fp1->fd_len - 1L, &fp3) && foldFind(gap2, 0L, &fp4))
- foldMerge(fp3, gap2, fp4);
+ if (foldFind(gap1, fp1->fd_len - 1L, &fp3) && foldFind(gap2, 0L, &fp4)) {
+ foldMerge(wp, fp3, gap2, fp4);
+ }
/* Move nested folds in fp2 to the end of fp1. */
if (!GA_EMPTY(gap2)) {
@@ -2843,7 +2879,7 @@ static void foldMerge(fold_T *fp1, garray_T *gap, fold_T *fp2)
}
fp1->fd_len += fp2->fd_len;
- deleteFoldEntry(gap, (int)(fp2 - (fold_T *)gap->ga_data), true);
+ deleteFoldEntry(wp, gap, (int)(fp2 - (fold_T *)gap->ga_data), true);
fold_changed = true;
}
diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua
index e861cfda35..6e80ad0e5c 100644
--- a/src/nvim/generators/gen_api_dispatch.lua
+++ b/src/nvim/generators/gen_api_dispatch.lua
@@ -237,6 +237,12 @@ for i = 1, #functions do
(j - 1)..'].type == kObjectTypeInteger && args.items['..(j - 1)..'].data.integer >= 0) {')
output:write('\n '..converted..' = (handle_T)args.items['..(j - 1)..'].data.integer;')
end
+ if rt:match('^Float$') then
+ -- accept integers for Floats
+ output:write('\n } else if (args.items['..
+ (j - 1)..'].type == kObjectTypeInteger) {')
+ output:write('\n '..converted..' = (Float)args.items['..(j - 1)..'].data.integer;')
+ end
-- accept empty lua tables as empty dictionarys
if rt:match('^Dictionary') then
output:write('\n } else if (args.items['..(j - 1)..'].type == kObjectTypeArray && args.items['..(j - 1)..'].data.array.size == 0) {') --luacheck: ignore 631
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
index 81666bf5d6..5ab5a7db1b 100644
--- a/src/nvim/getchar.c
+++ b/src/nvim/getchar.c
@@ -41,6 +41,7 @@
#include "nvim/option.h"
#include "nvim/regexp.h"
#include "nvim/screen.h"
+#include "nvim/ex_session.h"
#include "nvim/state.h"
#include "nvim/strings.h"
#include "nvim/ui.h"
@@ -1095,26 +1096,40 @@ void del_typebuf(int len, int offset)
* Write typed characters to script file.
* If recording is on put the character in the recordbuffer.
*/
-static void gotchars(char_u *chars, size_t len)
+static void gotchars(const char_u *chars, size_t len)
+ FUNC_ATTR_NONNULL_ALL
{
- char_u *s = chars;
- int c;
+ const char_u *s = chars;
+ static char_u buf[4] = { 0 };
+ static size_t buflen = 0;
+ size_t todo = len;
- // remember how many chars were last recorded
- if (reg_recording != 0) {
- last_recorded_len += len;
- }
+ while (todo--) {
+ buf[buflen++] = *s++;
+
+ // When receiving a special key sequence, store it until we have all
+ // the bytes and we can decide what to do with it.
+ if (buflen == 1 && buf[0] == K_SPECIAL) {
+ continue;
+ }
+ if (buflen == 2) {
+ continue;
+ }
- while (len--) {
// Handle one byte at a time; no translation to be done.
- c = *s++;
- updatescript(c);
+ for (size_t i = 0; i < buflen; i++) {
+ updatescript(buf[i]);
+ }
if (reg_recording != 0) {
- char buf[2] = { (char)c, NUL };
- add_buff(&recordbuff, buf, 1L);
+ buf[buflen] = NUL;
+ add_buff(&recordbuff, (char *)buf, (ptrdiff_t)buflen);
+ // remember how many chars were last recorded
+ last_recorded_len += buflen;
}
+ buflen = 0;
}
+
may_sync_undo();
/* output "debug mode" message next time in debug mode */
@@ -1201,7 +1216,7 @@ void save_typeahead(tasave_T *tp)
{
tp->save_typebuf = typebuf;
alloc_typebuf();
- tp->typebuf_valid = TRUE;
+ tp->typebuf_valid = true;
tp->old_char = old_char;
tp->old_mod_mask = old_mod_mask;
old_char = -1;
@@ -2480,12 +2495,11 @@ int inchar(
return fix_input_buffer(buf, len);
}
-/*
- * Fix typed characters for use by vgetc() and check_termcode().
- * buf[] must have room to triple the number of bytes!
- * Returns the new length.
- */
+// Fix typed characters for use by vgetc() and check_termcode().
+// "buf[]" must have room to triple the number of bytes!
+// Returns the new length.
int fix_input_buffer(char_u *buf, int len)
+ FUNC_ATTR_NONNULL_ALL
{
if (!using_script()) {
// Should not escape K_SPECIAL/CSI reading input from the user because vim
@@ -3347,7 +3361,7 @@ showmap (
msg_putchar(' ');
// Display the LHS. Get length of what we write.
- len = (size_t)msg_outtrans_special(mp->m_keys, true);
+ len = (size_t)msg_outtrans_special(mp->m_keys, true, 0);
do {
msg_putchar(' '); /* padd with blanks */
++len;
@@ -3375,7 +3389,7 @@ showmap (
// as typeahead.
char_u *s = vim_strsave(mp->m_str);
vim_unescape_csi(s);
- msg_outtrans_special(s, FALSE);
+ msg_outtrans_special(s, false, 0);
xfree(s);
}
if (p_verbose > 0) {
diff --git a/src/nvim/getchar.h b/src/nvim/getchar.h
index 01f60ccf49..f0b52079aa 100644
--- a/src/nvim/getchar.h
+++ b/src/nvim/getchar.h
@@ -10,12 +10,12 @@
/// Values for "noremap" argument of ins_typebuf()
///
/// Also used for map->m_noremap and menu->noremap[].
-enum {
+enum RemapValues {
REMAP_YES = 0, ///< Allow remapping.
REMAP_NONE = -1, ///< No remapping.
REMAP_SCRIPT = -2, ///< Remap script-local mappings only.
REMAP_SKIP = -3, ///< No remapping for first char.
-} RemapValues;
+};
// Argument for flush_buffers().
typedef enum {
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index 4741778c14..f102c3ddd8 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -120,28 +120,20 @@ typedef off_t off_T;
# endif
#endif
-/*
- * When vgetc() is called, it sets mod_mask to the set of modifiers that are
- * held down based on the MOD_MASK_* symbols that are read first.
- */
-EXTERN int mod_mask INIT(= 0x0); /* current key modifiers */
+// When vgetc() is called, it sets mod_mask to the set of modifiers that are
+// held down based on the MOD_MASK_* symbols that are read first.
+EXTERN int mod_mask INIT(= 0x0); // current key modifiers
-// TODO(bfredl): for the final interface this should find a more suitable
-// location.
-EXTERN sattr_T *lua_attr_buf INIT(= NULL);
-EXTERN size_t lua_attr_bufsize INIT(= 0);
EXTERN bool lua_attr_active INIT(= false);
-/*
- * Cmdline_row is the row where the command line starts, just below the
- * last window.
- * When the cmdline gets longer than the available space the screen gets
- * scrolled up. After a CTRL-D (show matches), after hitting ':' after
- * "hit return", and for the :global command, the command line is
- * temporarily moved. The old position is restored with the next call to
- * update_screen().
- */
+// Cmdline_row is the row where the command line starts, just below the
+// last window.
+// When the cmdline gets longer than the available space the screen gets
+// scrolled up. After a CTRL-D (show matches), after hitting ':' after
+// "hit return", and for the :global command, the command line is
+// temporarily moved. The old position is restored with the next call to
+// update_screen().
EXTERN int cmdline_row;
EXTERN int redraw_cmdline INIT(= false); // cmdline must be redrawn
@@ -153,42 +145,38 @@ EXTERN int cmdline_was_last_drawn INIT(= false); // cmdline was last drawn
EXTERN int exec_from_reg INIT(= false); // executing register
-/*
- * When '$' is included in 'cpoptions' option set:
- * When a change command is given that deletes only part of a line, a dollar
- * is put at the end of the changed text. dollar_vcol is set to the virtual
- * column of this '$'. -1 is used to indicate no $ is being displayed.
- */
+// When '$' is included in 'cpoptions' option set:
+// When a change command is given that deletes only part of a line, a dollar
+// is put at the end of the changed text. dollar_vcol is set to the virtual
+// column of this '$'. -1 is used to indicate no $ is being displayed.
EXTERN colnr_T dollar_vcol INIT(= -1);
-/*
- * Variables for Insert mode completion.
- */
+// Variables for Insert mode completion.
-/* Length in bytes of the text being completed (this is deleted to be replaced
- * by the match.) */
+// Length in bytes of the text being completed (this is deleted to be replaced
+// by the match.)
EXTERN int compl_length INIT(= 0);
-/* Set when character typed while looking for matches and it means we should
- * stop looking for matches. */
-EXTERN int compl_interrupted INIT(= FALSE);
+// Set when character typed while looking for matches and it means we should
+// stop looking for matches.
+EXTERN int compl_interrupted INIT(= false);
// Set when doing something for completion that may call edit() recursively,
// which is not allowed. Also used to disable folding during completion
EXTERN int compl_busy INIT(= false);
-/* List of flags for method of completion. */
+// List of flags for method of completion.
EXTERN int compl_cont_status INIT(= 0);
-# define CONT_ADDING 1 /* "normal" or "adding" expansion */
-# define CONT_INTRPT (2 + 4) /* a ^X interrupted the current expansion */
- /* it's set only iff N_ADDS is set */
-# define CONT_N_ADDS 4 /* next ^X<> will add-new or expand-current */
-# define CONT_S_IPOS 8 /* next ^X<> will set initial_pos?
- * if so, word-wise-expansion will set SOL */
-# define CONT_SOL 16 /* pattern includes start of line, just for
- * word-wise expansion, not set for ^X^L */
-# define CONT_LOCAL 32 /* for ctrl_x_mode 0, ^X^P/^X^N do a local
- * expansion, (eg use complete=.) */
+# define CONT_ADDING 1 // "normal" or "adding" expansion
+# define CONT_INTRPT (2 + 4) // a ^X interrupted the current expansion
+ // it's set only iff N_ADDS is set
+# define CONT_N_ADDS 4 // next ^X<> will add-new or expand-current
+# define CONT_S_IPOS 8 // next ^X<> will set initial_pos?
+ // if so, word-wise-expansion will set SOL
+# define CONT_SOL 16 // pattern includes start of line, just for
+ // word-wise expansion, not set for ^X^L
+# define CONT_LOCAL 32 // for ctrl_x_mode 0, ^X^P/^X^N do a local
+ // expansion, (eg use complete=.)
// state for putting characters in the message area
EXTERN int cmdmsg_rl INIT(= false); // cmdline is drawn right to left
@@ -204,49 +192,49 @@ EXTERN bool msg_scrolled_ign INIT(= false);
EXTERN bool msg_did_scroll INIT(= false);
-EXTERN char_u *keep_msg INIT(= NULL); /* msg to be shown after redraw */
-EXTERN int keep_msg_attr INIT(= 0); /* highlight attr for keep_msg */
-EXTERN int keep_msg_more INIT(= FALSE); /* keep_msg was set by msgmore() */
-EXTERN int need_fileinfo INIT(= FALSE); /* do fileinfo() after redraw */
-EXTERN int msg_scroll INIT(= FALSE); /* msg_start() will scroll */
-EXTERN int msg_didout INIT(= FALSE); /* msg_outstr() was used in line */
-EXTERN int msg_didany INIT(= FALSE); /* msg_outstr() was used at all */
-EXTERN int msg_nowait INIT(= FALSE); /* don't wait for this msg */
-EXTERN int emsg_off INIT(= 0); /* don't display errors for now,
- unless 'debug' is set. */
-EXTERN int info_message INIT(= FALSE); /* printing informative message */
-EXTERN int msg_hist_off INIT(= FALSE); /* don't add messages to history */
-EXTERN int need_clr_eos INIT(= FALSE); /* need to clear text before
- displaying a message. */
-EXTERN int emsg_skip INIT(= 0); /* don't display errors for
- expression that is skipped */
-EXTERN int emsg_severe INIT(= FALSE); /* use message of next of several
- emsg() calls for throw */
-EXTERN int did_endif INIT(= FALSE); /* just had ":endif" */
-EXTERN dict_T vimvardict; /* Dictionary with v: variables */
-EXTERN dict_T globvardict; /* Dictionary with g: variables */
-EXTERN int did_emsg; /* set by emsg() when the message
- is displayed or thrown */
+EXTERN char_u *keep_msg INIT(= NULL); // msg to be shown after redraw
+EXTERN int keep_msg_attr INIT(= 0); // highlight attr for keep_msg
+EXTERN int keep_msg_more INIT(= false); // keep_msg was set by msgmore()
+EXTERN int need_fileinfo INIT(= false); // do fileinfo() after redraw
+EXTERN int msg_scroll INIT(= false); // msg_start() will scroll
+EXTERN int msg_didout INIT(= false); // msg_outstr() was used in line
+EXTERN int msg_didany INIT(= false); // msg_outstr() was used at all
+EXTERN int msg_nowait INIT(= false); // don't wait for this msg
+EXTERN int emsg_off INIT(= 0); // don't display errors for now,
+ // unless 'debug' is set.
+EXTERN int info_message INIT(= false); // printing informative message
+EXTERN int msg_hist_off INIT(= false); // don't add messages to history
+EXTERN int need_clr_eos INIT(= false); // need to clear text before
+ // displaying a message.
+EXTERN int emsg_skip INIT(= 0); // don't display errors for
+ // expression that is skipped
+EXTERN int emsg_severe INIT(= false); // use message of next of several
+ // emsg() calls for throw
+EXTERN int did_endif INIT(= false); // just had ":endif"
+EXTERN dict_T vimvardict; // Dictionary with v: variables
+EXTERN dict_T globvardict; // Dictionary with g: variables
+EXTERN int did_emsg; // set by emsg() when the message
+ // is displayed or thrown
EXTERN bool called_vim_beep; // set if vim_beep() is called
-EXTERN int did_emsg_syntax; /* did_emsg set because of a
- syntax error */
-EXTERN int called_emsg; /* always set by emsg() */
-EXTERN int ex_exitval INIT(= 0); /* exit value for ex mode */
-EXTERN int emsg_on_display INIT(= FALSE); /* there is an error message */
-EXTERN int rc_did_emsg INIT(= FALSE); /* vim_regcomp() called emsg() */
-
-EXTERN int no_wait_return INIT(= 0); /* don't wait for return for now */
-EXTERN int need_wait_return INIT(= 0); /* need to wait for return later */
-EXTERN int did_wait_return INIT(= FALSE); /* wait_return() was used and
- nothing written since then */
-EXTERN int need_maketitle INIT(= TRUE); /* call maketitle() soon */
+EXTERN int did_emsg_syntax; // did_emsg set because of a
+ // syntax error
+EXTERN int called_emsg; // always set by emsg()
+EXTERN int ex_exitval INIT(= 0); // exit value for ex mode
+EXTERN bool emsg_on_display INIT(= false); // there is an error message
+EXTERN int rc_did_emsg INIT(= false); // vim_regcomp() called emsg()
+
+EXTERN int no_wait_return INIT(= 0); // don't wait for return for now
+EXTERN int need_wait_return INIT(= 0); // need to wait for return later
+EXTERN int did_wait_return INIT(= false); // wait_return() was used and
+ // nothing written since then
+EXTERN int need_maketitle INIT(= true); // call maketitle() soon
EXTERN int quit_more INIT(= false); // 'q' hit at "--more--" msg
EXTERN int ex_keep_indent INIT(= false); // getexmodeline(): keep indent
EXTERN int vgetc_busy INIT(= 0); // when inside vgetc() then > 0
-EXTERN int didset_vim INIT(= FALSE); /* did set $VIM ourselves */
-EXTERN int didset_vimruntime INIT(= FALSE); /* idem for $VIMRUNTIME */
+EXTERN int didset_vim INIT(= false); // did set $VIM ourselves
+EXTERN int didset_vimruntime INIT(= false); // idem for $VIMRUNTIME
/// Lines left before a "more" message. Ex mode needs to be able to reset this
/// after you type something.
@@ -254,8 +242,8 @@ EXTERN int lines_left INIT(= -1); // lines left for listing
EXTERN int msg_no_more INIT(= false); // don't use more prompt, truncate
// messages
-EXTERN char_u *sourcing_name INIT( = NULL); /* name of error message source */
-EXTERN linenr_T sourcing_lnum INIT(= 0); /* line number of the source file */
+EXTERN char_u *sourcing_name INIT(= NULL); // name of error message source
+EXTERN linenr_T sourcing_lnum INIT(= 0); // line number of the source file
EXTERN int ex_nesting_level INIT(= 0); // nesting level
EXTERN int debug_break_level INIT(= -1); // break below this level
@@ -286,11 +274,11 @@ EXTERN int check_cstack INIT(= false);
/// commands).
EXTERN int trylevel INIT(= 0);
-/// When "force_abort" is TRUE, always skip commands after an error message,
+/// When "force_abort" is true, always skip commands after an error message,
/// even after the outermost ":endif", ":endwhile" or ":endfor" or for a
-/// function without the "abort" flag. It is set to TRUE when "trylevel" is
+/// function without the "abort" flag. It is set to true when "trylevel" is
/// non-zero (and ":silent!" was not used) or an exception is being thrown at
-/// the time an error is detected. It is set to FALSE when "trylevel" gets
+/// the time an error is detected. It is set to false when "trylevel" gets
/// zero again and there was no error or interrupt or throw.
EXTERN int force_abort INIT(= false);
@@ -308,7 +296,7 @@ EXTERN struct msglist **msg_list INIT(= NULL);
/// interrupt message or reporting an exception that is still uncaught at the
/// top level (which has already been discarded then). Also used for the error
/// message when no exception can be thrown.
-EXTERN int suppress_errthrow INIT(= false);
+EXTERN bool suppress_errthrow INIT(= false);
/// The stack of all caught and not finished exceptions. The exception on the
/// top of the stack is the one got by evaluation of v:exception. The complete
@@ -361,41 +349,38 @@ EXTERN int provider_call_nesting INIT(= 0);
EXTERN int t_colors INIT(= 256); // int value of T_CCO
-/*
- * When highlight_match is TRUE, highlight a match, starting at the cursor
- * position. Search_match_lines is the number of lines after the match (0 for
- * a match within one line), search_match_endcol the column number of the
- * character just after the match in the last line.
- */
-EXTERN int highlight_match INIT(= FALSE); /* show search match pos */
-EXTERN linenr_T search_match_lines; /* lines of of matched string */
-EXTERN colnr_T search_match_endcol; /* col nr of match end */
-
-EXTERN int no_smartcase INIT(= FALSE); /* don't use 'smartcase' once */
-
-EXTERN int need_check_timestamps INIT(= FALSE); /* need to check file
- timestamps asap */
-EXTERN int did_check_timestamps INIT(= FALSE); /* did check timestamps
- recently */
-EXTERN int no_check_timestamps INIT(= 0); /* Don't check timestamps */
-
-EXTERN int autocmd_busy INIT(= FALSE); /* Is apply_autocmds() busy? */
-EXTERN int autocmd_no_enter INIT(= FALSE); /* *Enter autocmds disabled */
-EXTERN int autocmd_no_leave INIT(= FALSE); /* *Leave autocmds disabled */
-EXTERN int modified_was_set; /* did ":set modified" */
-EXTERN int did_filetype INIT(= FALSE); /* FileType event found */
-EXTERN int keep_filetype INIT(= FALSE); /* value for did_filetype when
- starting to execute
- autocommands */
+// When highlight_match is true, highlight a match, starting at the cursor
+// position. Search_match_lines is the number of lines after the match (0 for
+// a match within one line), search_match_endcol the column number of the
+// character just after the match in the last line.
+EXTERN int highlight_match INIT(= false); // show search match pos
+EXTERN linenr_T search_match_lines; // lines of of matched string
+EXTERN colnr_T search_match_endcol; // col nr of match end
+
+EXTERN int no_smartcase INIT(= false); // don't use 'smartcase' once
+
+EXTERN int need_check_timestamps INIT(= false); // need to check file
+ // timestamps asap
+EXTERN int did_check_timestamps INIT(= false); // did check timestamps
+ // recently
+EXTERN int no_check_timestamps INIT(= 0); // Don't check timestamps
+
+EXTERN int autocmd_busy INIT(= false); // Is apply_autocmds() busy?
+EXTERN int autocmd_no_enter INIT(= false); // *Enter autocmds disabled
+EXTERN int autocmd_no_leave INIT(= false); // *Leave autocmds disabled
+EXTERN int modified_was_set; // did ":set modified"
+EXTERN int did_filetype INIT(= false); // FileType event found
+// value for did_filetype when starting to execute autocommands
+EXTERN int keep_filetype INIT(= false);
// When deleting the current buffer, another one must be loaded.
// If we know which one is preferred, au_new_curbuf is set to it.
EXTERN bufref_T au_new_curbuf INIT(= { NULL, 0, 0 });
-// When deleting a buffer/window and autocmd_busy is TRUE, do not free the
+// When deleting a buffer/window and autocmd_busy is true, do not free the
// buffer/window. but link it in the list starting with
// au_pending_free_buf/ap_pending_free_win, using b_next/w_next.
-// Free the buffer/window when autocmd_busy is being set to FALSE.
+// Free the buffer/window when autocmd_busy is being set to false.
EXTERN buf_T *au_pending_free_buf INIT(= NULL);
EXTERN win_T *au_pending_free_win INIT(= NULL);
@@ -403,31 +388,27 @@ EXTERN win_T *au_pending_free_win INIT(= NULL);
EXTERN int mouse_grid;
EXTERN int mouse_row;
EXTERN int mouse_col;
-EXTERN bool mouse_past_bottom INIT(= false); /* mouse below last line */
-EXTERN bool mouse_past_eol INIT(= false); /* mouse right of line */
-EXTERN int mouse_dragging INIT(= 0); /* extending Visual area with
- mouse dragging */
+EXTERN bool mouse_past_bottom INIT(= false); // mouse below last line
+EXTERN bool mouse_past_eol INIT(= false); // mouse right of line
+EXTERN int mouse_dragging INIT(= 0); // extending Visual area with
+ // mouse dragging
-/* The root of the menu hierarchy. */
+// The root of the menu hierarchy.
EXTERN vimmenu_T *root_menu INIT(= NULL);
-/*
- * While defining the system menu, sys_menu is TRUE. This avoids
- * overruling of menus that the user already defined.
- */
-EXTERN int sys_menu INIT(= FALSE);
-
-/* While redrawing the screen this flag is set. It means the screen size
- * ('lines' and 'rows') must not be changed. */
-EXTERN int updating_screen INIT(= FALSE);
-
-/*
- * All windows are linked in a list. firstwin points to the first entry,
- * lastwin to the last entry (can be the same as firstwin) and curwin to the
- * currently active window.
- */
-EXTERN win_T *firstwin; /* first window */
-EXTERN win_T *lastwin; /* last window */
-EXTERN win_T *prevwin INIT(= NULL); /* previous window */
+// While defining the system menu, sys_menu is true. This avoids
+// overruling of menus that the user already defined.
+EXTERN int sys_menu INIT(= false);
+
+// While redrawing the screen this flag is set. It means the screen size
+// ('lines' and 'rows') must not be changed.
+EXTERN int updating_screen INIT(= 0);
+
+// All windows are linked in a list. firstwin points to the first entry,
+// lastwin to the last entry (can be the same as firstwin) and curwin to the
+// currently active window.
+EXTERN win_T *firstwin; // first window
+EXTERN win_T *lastwin; // last window
+EXTERN win_T *prevwin INIT(= NULL); // previous window
# define ONE_WINDOW (firstwin == lastwin)
# define FOR_ALL_FRAMES(frp, first_frame) \
for (frp = first_frame; frp != NULL; frp = frp->fr_next) // NOLINT
@@ -443,33 +424,27 @@ EXTERN win_T *prevwin INIT(= NULL); /* previous window */
for (win_T *wp = ((tp) == curtab) \
? firstwin : (tp)->tp_firstwin; wp != NULL; wp = wp->w_next)
-EXTERN win_T *curwin; /* currently active window */
+EXTERN win_T *curwin; // currently active window
-EXTERN win_T *aucmd_win; /* window used in aucmd_prepbuf() */
-EXTERN int aucmd_win_used INIT(= FALSE); /* aucmd_win is being used */
+EXTERN win_T *aucmd_win; // window used in aucmd_prepbuf()
+EXTERN int aucmd_win_used INIT(= false); // aucmd_win is being used
-/*
- * The window layout is kept in a tree of frames. topframe points to the top
- * of the tree.
- */
-EXTERN frame_T *topframe; /* top of the window frame tree */
+// The window layout is kept in a tree of frames. topframe points to the top
+// of the tree.
+EXTERN frame_T *topframe; // top of the window frame tree
-/*
- * Tab pages are alternative topframes. "first_tabpage" points to the first
- * one in the list, "curtab" is the current one.
- */
+// Tab pages are alternative topframes. "first_tabpage" points to the first
+// one in the list, "curtab" is the current one.
EXTERN tabpage_T *first_tabpage;
EXTERN tabpage_T *lastused_tabpage;
EXTERN tabpage_T *curtab;
-EXTERN int redraw_tabline INIT(= FALSE); /* need to redraw tabline */
+EXTERN int redraw_tabline INIT(= false); // need to redraw tabline
// Iterates over all tabs in the tab list
# define FOR_ALL_TABS(tp) for (tabpage_T *tp = first_tabpage; tp != NULL; tp = tp->tp_next)
-/*
- * All buffers are linked in a list. 'firstbuf' points to the first entry,
- * 'lastbuf' to the last entry and 'curbuf' to the currently active buffer.
- */
+// All buffers are linked in a list. 'firstbuf' points to the first entry,
+// 'lastbuf' to the last entry and 'curbuf' to the currently active buffer.
EXTERN buf_T *firstbuf INIT(= NULL); // first buffer
EXTERN buf_T *lastbuf INIT(= NULL); // last buffer
EXTERN buf_T *curbuf INIT(= NULL); // currently active buffer
@@ -485,23 +460,19 @@ EXTERN buf_T *curbuf INIT(= NULL); // currently active buffer
for (sign = buf->b_signlist; sign != NULL; sign = sign->next) // NOLINT
-/*
- * List of files being edited (global argument list). curwin->w_alist points
- * to this when the window is using the global argument list.
- */
-EXTERN alist_T global_alist; /* global argument list */
+// List of files being edited (global argument list). curwin->w_alist points
+// to this when the window is using the global argument list.
+EXTERN alist_T global_alist; // global argument list
EXTERN int max_alist_id INIT(= 0); ///< the previous argument list id
EXTERN bool arg_had_last INIT(= false); // accessed last file in
// global_alist
-EXTERN int ru_col; /* column for ruler */
-EXTERN int ru_wid; /* 'rulerfmt' width of ruler when non-zero */
-EXTERN int sc_col; /* column for shown command */
+EXTERN int ru_col; // column for ruler
+EXTERN int ru_wid; // 'rulerfmt' width of ruler when non-zero
+EXTERN int sc_col; // column for shown command
-//
// When starting or exiting some things are done differently (e.g. screen
// updating).
-//
// First NO_SCREEN, then NO_BUFFERS, then 0 when startup finished.
EXTERN int starting INIT(= NO_SCREEN);
@@ -550,98 +521,78 @@ EXTERN int VIsual_select INIT(= false);
EXTERN int VIsual_reselect;
/// Type of Visual mode.
EXTERN int VIsual_mode INIT(= 'v');
-/// TRUE when redoing Visual.
+/// true when redoing Visual.
EXTERN int redo_VIsual_busy INIT(= false);
/// When pasting text with the middle mouse button in visual mode with
/// restart_edit set, remember where it started so we can set Insstart.
EXTERN pos_T where_paste_started;
-/*
- * This flag is used to make auto-indent work right on lines where only a
- * <RETURN> or <ESC> is typed. It is set when an auto-indent is done, and
- * reset when any other editing is done on the line. If an <ESC> or <RETURN>
- * is received, and did_ai is TRUE, the line is truncated.
- */
+// This flag is used to make auto-indent work right on lines where only a
+// <RETURN> or <ESC> is typed. It is set when an auto-indent is done, and
+// reset when any other editing is done on the line. If an <ESC> or <RETURN>
+// is received, and did_ai is true, the line is truncated.
EXTERN bool did_ai INIT(= false);
-/*
- * Column of first char after autoindent. 0 when no autoindent done. Used
- * when 'backspace' is 0, to avoid backspacing over autoindent.
- */
+// Column of first char after autoindent. 0 when no autoindent done. Used
+// when 'backspace' is 0, to avoid backspacing over autoindent.
EXTERN colnr_T ai_col INIT(= 0);
-/*
- * This is a character which will end a start-middle-end comment when typed as
- * the first character on a new line. It is taken from the last character of
- * the "end" comment leader when the COM_AUTO_END flag is given for that
- * comment end in 'comments'. It is only valid when did_ai is TRUE.
- */
+// This is a character which will end a start-middle-end comment when typed as
+// the first character on a new line. It is taken from the last character of
+// the "end" comment leader when the COM_AUTO_END flag is given for that
+// comment end in 'comments'. It is only valid when did_ai is true.
EXTERN int end_comment_pending INIT(= NUL);
-/*
- * This flag is set after a ":syncbind" to let the check_scrollbind() function
- * know that it should not attempt to perform scrollbinding due to the scroll
- * that was a result of the ":syncbind." (Otherwise, check_scrollbind() will
- * undo some of the work done by ":syncbind.") -ralston
- */
-EXTERN int did_syncbind INIT(= FALSE);
-
-/*
- * This flag is set when a smart indent has been performed. When the next typed
- * character is a '{' the inserted tab will be deleted again.
- */
+// This flag is set after a ":syncbind" to let the check_scrollbind() function
+// know that it should not attempt to perform scrollbinding due to the scroll
+// that was a result of the ":syncbind." (Otherwise, check_scrollbind() will
+// undo some of the work done by ":syncbind.") -ralston
+EXTERN int did_syncbind INIT(= false);
+
+// This flag is set when a smart indent has been performed. When the next typed
+// character is a '{' the inserted tab will be deleted again.
EXTERN bool did_si INIT(= false);
-/*
- * This flag is set after an auto indent. If the next typed character is a '}'
- * one indent will be removed.
- */
+// This flag is set after an auto indent. If the next typed character is a '}'
+// one indent will be removed.
EXTERN bool can_si INIT(= false);
-/*
- * This flag is set after an "O" command. If the next typed character is a '{'
- * one indent will be removed.
- */
+// This flag is set after an "O" command. If the next typed character is a '{'
+// one indent will be removed.
EXTERN bool can_si_back INIT(= false);
// w_cursor before formatting text.
EXTERN pos_T saved_cursor INIT(= { 0, 0, 0 });
-/*
- * Stuff for insert mode.
- */
-EXTERN pos_T Insstart; /* This is where the latest
- * insert/append mode started. */
+// Stuff for insert mode.
+EXTERN pos_T Insstart; // This is where the latest
+ // insert/append mode started.
// This is where the latest insert/append mode started. In contrast to
// Insstart, this won't be reset by certain keys and is needed for
// op_insert(), to detect correctly where inserting by the user started.
EXTERN pos_T Insstart_orig;
-/*
- * Stuff for VREPLACE mode.
- */
-EXTERN int orig_line_count INIT(= 0); /* Line count when "gR" started */
-EXTERN int vr_lines_changed INIT(= 0); /* #Lines changed by "gR" so far */
+// Stuff for VREPLACE mode.
+EXTERN int orig_line_count INIT(= 0); // Line count when "gR" started
+EXTERN int vr_lines_changed INIT(= 0); // #Lines changed by "gR" so far
// increase around internal delete/replace
EXTERN int inhibit_delete_count INIT(= 0);
-/*
- * These flags are set based upon 'fileencoding'.
- * Note that "enc_utf8" is also set for "unicode", because the characters are
- * internally stored as UTF-8 (to avoid trouble with NUL bytes).
- */
-# define DBCS_JPN 932 /* japan */
-# define DBCS_JPNU 9932 /* euc-jp */
-# define DBCS_KOR 949 /* korea */
-# define DBCS_KORU 9949 /* euc-kr */
-# define DBCS_CHS 936 /* chinese */
-# define DBCS_CHSU 9936 /* euc-cn */
-# define DBCS_CHT 950 /* taiwan */
-# define DBCS_CHTU 9950 /* euc-tw */
-# define DBCS_2BYTE 1 /* 2byte- */
+// These flags are set based upon 'fileencoding'.
+// Note that "enc_utf8" is also set for "unicode", because the characters are
+// internally stored as UTF-8 (to avoid trouble with NUL bytes).
+# define DBCS_JPN 932 // japan
+# define DBCS_JPNU 9932 // euc-jp
+# define DBCS_KOR 949 // korea
+# define DBCS_KORU 9949 // euc-kr
+# define DBCS_CHS 936 // chinese
+# define DBCS_CHSU 9936 // euc-cn
+# define DBCS_CHT 950 // taiwan
+# define DBCS_CHTU 9950 // euc-tw
+# define DBCS_2BYTE 1 // 2byte-
# define DBCS_DEBUG -1
// mbyte flags that used to depend on 'encoding'. These are now deprecated, as
@@ -682,40 +633,40 @@ EXTERN int u_sync_once INIT(= 0); // Call u_sync() once when evaluating
EXTERN bool force_restart_edit INIT(= false); // force restart_edit after
// ex_normal returns
-EXTERN int restart_edit INIT(= 0); /* call edit when next cmd finished */
-EXTERN int arrow_used; /* Normally FALSE, set to TRUE after
- * hitting cursor key in insert mode.
- * Used by vgetorpeek() to decide when
- * to call u_sync() */
-EXTERN int ins_at_eol INIT(= FALSE); /* put cursor after eol when
- restarting edit after CTRL-O */
+EXTERN int restart_edit INIT(= 0); // call edit when next cmd finished
+EXTERN int arrow_used; // Normally false, set to true after
+ // hitting cursor key in insert mode.
+ // Used by vgetorpeek() to decide when
+ // to call u_sync()
+EXTERN bool ins_at_eol INIT(= false); // put cursor after eol when
+ // restarting edit after CTRL-O
EXTERN char_u *edit_submode INIT(= NULL); // msg for CTRL-X submode
EXTERN char_u *edit_submode_pre INIT(= NULL); // prepended to edit_submode
EXTERN char_u *edit_submode_extra INIT(= NULL); // appended to edit_submode
EXTERN hlf_T edit_submode_highl; // highl. method for extra info
-EXTERN int no_abbr INIT(= TRUE); /* TRUE when no abbreviations loaded */
+EXTERN int no_abbr INIT(= true); // true when no abbreviations loaded
EXTERN int mapped_ctrl_c INIT(= 0); // Modes where CTRL-C is mapped.
-EXTERN cmdmod_T cmdmod; /* Ex command modifiers */
+EXTERN cmdmod_T cmdmod; // Ex command modifiers
EXTERN int msg_silent INIT(= 0); // don't print messages
EXTERN int emsg_silent INIT(= 0); // don't print error messages
EXTERN bool emsg_noredir INIT(= false); // don't redirect error messages
EXTERN bool cmd_silent INIT(= false); // don't echo the command line
-/* Values for swap_exists_action: what to do when swap file already exists */
-#define SEA_NONE 0 /* don't use dialog */
-#define SEA_DIALOG 1 /* use dialog when possible */
-#define SEA_QUIT 2 /* quit editing the file */
-#define SEA_RECOVER 3 /* recover the file */
+// Values for swap_exists_action: what to do when swap file already exists
+#define SEA_NONE 0 // don't use dialog
+#define SEA_DIALOG 1 // use dialog when possible
+#define SEA_QUIT 2 // quit editing the file
+#define SEA_RECOVER 3 // recover the file
EXTERN int swap_exists_action INIT(= SEA_NONE);
-/* For dialog when swap file already
- * exists. */
-EXTERN int swap_exists_did_quit INIT(= FALSE);
-/* Selected "quit" at the dialog. */
+// For dialog when swap file already
+// exists.
+EXTERN int swap_exists_did_quit INIT(= false);
+// Selected "quit" at the dialog.
EXTERN char_u IObuff[IOSIZE]; ///< Buffer for sprintf, I/O, etc.
EXTERN char_u NameBuff[MAXPATHL]; ///< Buffer for expanding file names
@@ -728,11 +679,11 @@ IOSIZE
#endif
];
-/* When non-zero, postpone redrawing. */
+// When non-zero, postpone redrawing.
EXTERN int RedrawingDisabled INIT(= 0);
-EXTERN int readonlymode INIT(= FALSE); /* Set to TRUE for "view" */
-EXTERN int recoverymode INIT(= FALSE); /* Set to TRUE for "-r" option */
+EXTERN int readonlymode INIT(= false); // Set to true for "view"
+EXTERN int recoverymode INIT(= false); // Set to true for "-r" option
// typeahead buffer
EXTERN typebuf_T typebuf INIT(= { NULL, NULL, 0, 0, 0, 0, 0, 0, 0 });
@@ -742,7 +693,7 @@ EXTERN int ex_normal_lock INIT(= 0); // forbid use of ex_normal()
EXTERN int ignore_script INIT(= false); // ignore script input
EXTERN int stop_insert_mode; // for ":stopinsert" and 'insertmode'
EXTERN bool KeyTyped; // true if user typed current char
-EXTERN int KeyStuffed; // TRUE if current char from stuffbuf
+EXTERN int KeyStuffed; // true if current char from stuffbuf
EXTERN int maptick INIT(= 0); // tick for each non-mapped char
EXTERN int must_redraw INIT(= 0); // type of redraw necessary
@@ -758,15 +709,15 @@ EXTERN FILE *scriptout INIT(= NULL); ///< Stream to write script to.
// volatile because it is used in a signal handler.
EXTERN volatile int got_int INIT(= false); // set to true when interrupt
// signal occurred
-EXTERN int bangredo INIT(= FALSE); /* set to TRUE with ! command */
-EXTERN int searchcmdlen; /* length of previous search cmd */
-EXTERN int reg_do_extmatch INIT(= 0); /* Used when compiling regexp:
- * REX_SET to allow \z\(...\),
- * REX_USE to allow \z\1 et al. */
-EXTERN reg_extmatch_T *re_extmatch_in INIT(= NULL); /* Used by vim_regexec():
- * strings for \z\1...\z\9 */
-EXTERN reg_extmatch_T *re_extmatch_out INIT(= NULL); /* Set by vim_regexec()
- * to store \z\(...\) matches */
+EXTERN int bangredo INIT(= false); // set to true with ! command
+EXTERN int searchcmdlen; // length of previous search cmd
+EXTERN int reg_do_extmatch INIT(= 0); // Used when compiling regexp:
+ // REX_SET to allow \z\(...\),
+ // REX_USE to allow \z\1 et al.
+// Used by vim_regexec(): strings for \z\1...\z\9
+EXTERN reg_extmatch_T *re_extmatch_in INIT(= NULL);
+// Set by vim_regexec() to store \z\(...\) matches
+EXTERN reg_extmatch_T *re_extmatch_out INIT(= NULL);
EXTERN int did_outofmem_msg INIT(= false);
// set after out of memory msg
@@ -785,11 +736,11 @@ EXTERN int autocmd_bufnr INIT(= 0); // fnum for <abuf> on cmdline
EXTERN char_u *autocmd_match INIT(= NULL); // name for <amatch> on cmdline
EXTERN int did_cursorhold INIT(= false); // set when CursorHold t'gerd
-EXTERN int postponed_split INIT(= 0); /* for CTRL-W CTRL-] command */
-EXTERN int postponed_split_flags INIT(= 0); /* args for win_split() */
-EXTERN int postponed_split_tab INIT(= 0); /* cmdmod.tab */
-EXTERN int g_do_tagpreview INIT(= 0); /* for tag preview commands:
- height of preview window */
+EXTERN int postponed_split INIT(= 0); // for CTRL-W CTRL-] command
+EXTERN int postponed_split_flags INIT(= 0); // args for win_split()
+EXTERN int postponed_split_tab INIT(= 0); // cmdmod.tab
+EXTERN int g_do_tagpreview INIT(= 0); // for tag preview commands:
+ // height of preview window
EXTERN int g_tag_at_cursor INIT(= false); // whether the tag command comes
// from the command line (0) or was
// invoked as a normal command (1)
@@ -797,15 +748,13 @@ EXTERN int g_tag_at_cursor INIT(= false); // whether the tag command comes
EXTERN int replace_offset INIT(= 0); // offset for replace_push()
EXTERN char_u *escape_chars INIT(= (char_u *)" \t\\\"|");
-/* need backslash in cmd line */
+// need backslash in cmd line
-EXTERN int keep_help_flag INIT(= FALSE); /* doing :ta from help file */
+EXTERN int keep_help_flag INIT(= false); // doing :ta from help file
-/*
- * When a string option is NULL (which only happens in out-of-memory
- * situations), it is set to empty_option, to avoid having to check for NULL
- * everywhere.
- */
+// When a string option is NULL (which only happens in out-of-memory
+// situations), it is set to empty_option, to avoid having to check for NULL
+// everywhere.
EXTERN char_u *empty_option INIT(= (char_u *)"");
EXTERN int redir_off INIT(= false); // no redirection for a moment
@@ -814,10 +763,10 @@ EXTERN int redir_reg INIT(= 0); // message redirection register
EXTERN int redir_vname INIT(= 0); // message redirection variable
EXTERN garray_T *capture_ga INIT(= NULL); // captured output for execute()
-EXTERN char_u langmap_mapchar[256]; /* mapping for language keys */
+EXTERN char_u langmap_mapchar[256]; // mapping for language keys
-EXTERN int save_p_ls INIT(= -1); /* Save 'laststatus' setting */
-EXTERN int save_p_wmh INIT(= -1); /* Save 'winminheight' setting */
+EXTERN int save_p_ls INIT(= -1); // Save 'laststatus' setting
+EXTERN int save_p_wmh INIT(= -1); // Save 'winminheight' setting
EXTERN int wild_menu_showing INIT(= 0);
enum {
WM_SHOWN = 1, ///< wildmenu showing
@@ -826,25 +775,24 @@ enum {
};
-/*
- * Some file names are stored in pathdef.c, which is generated from the
- * Makefile to make their value depend on the Makefile.
- */
+// Some file names are stored in pathdef.c, which is generated from the
+// Makefile to make their value depend on the Makefile.
#ifdef HAVE_PATHDEF
extern char *default_vim_dir;
extern char *default_vimruntime_dir;
+extern char *default_lib_dir;
extern char_u *compiled_user;
extern char_u *compiled_sys;
#endif
-/* When a window has a local directory, the absolute path of the global
- * current directory is stored here (in allocated memory). If the current
- * directory is not a local directory, globaldir is NULL. */
+// When a window has a local directory, the absolute path of the global
+// current directory is stored here (in allocated memory). If the current
+// directory is not a local directory, globaldir is NULL.
EXTERN char_u *globaldir INIT(= NULL);
-/* Whether 'keymodel' contains "stopsel" and "startsel". */
-EXTERN int km_stopsel INIT(= FALSE);
-EXTERN int km_startsel INIT(= FALSE);
+// Whether 'keymodel' contains "stopsel" and "startsel".
+EXTERN int km_stopsel INIT(= false);
+EXTERN int km_startsel INIT(= false);
EXTERN int cedit_key INIT(= -1); ///< key value of 'cedit' option
EXTERN int cmdwin_type INIT(= 0); ///< type of cmdline window or 0
@@ -853,18 +801,16 @@ EXTERN int cmdwin_level INIT(= 0); ///< cmdline recursion level
EXTERN char_u no_lines_msg[] INIT(= N_("--No lines in buffer--"));
-/*
- * When ":global" is used to number of substitutions and changed lines is
- * accumulated until it's finished.
- * Also used for ":spellrepall".
- */
-EXTERN long sub_nsubs; /* total number of substitutions */
-EXTERN linenr_T sub_nlines; /* total number of lines changed */
+// When ":global" is used to number of substitutions and changed lines is
+// accumulated until it's finished.
+// Also used for ":spellrepall".
+EXTERN long sub_nsubs; // total number of substitutions
+EXTERN linenr_T sub_nlines; // total number of lines changed
-/* table to store parsed 'wildmode' */
+// table to store parsed 'wildmode'
EXTERN char_u wim_flags[4];
-/* whether titlestring and iconstring contains statusline syntax */
+// whether titlestring and iconstring contains statusline syntax
# define STL_IN_ICON 1
# define STL_IN_TITLE 2
EXTERN int stl_syntax INIT(= 0);
@@ -872,7 +818,7 @@ EXTERN int stl_syntax INIT(= 0);
// don't use 'hlsearch' temporarily
EXTERN bool no_hlsearch INIT(= false);
-/* Page number used for %N in 'pageheader' and 'guitablabel'. */
+// Page number used for %N in 'pageheader' and 'guitablabel'.
EXTERN linenr_T printer_page_num;
@@ -890,18 +836,16 @@ EXTERN char pseps[2] INIT(= { '\\', 0 }); // normal path separator string
// kNone when no operator is being executed, kFalse otherwise.
EXTERN TriState virtual_op INIT(= kNone);
-/* Display tick, incremented for each call to update_screen() */
+// Display tick, incremented for each call to update_screen()
EXTERN disptick_T display_tick INIT(= 0);
-/* Line in which spell checking wasn't highlighted because it touched the
- * cursor position in Insert mode. */
+// Line in which spell checking wasn't highlighted because it touched the
+// cursor position in Insert mode.
EXTERN linenr_T spell_redraw_lnum INIT(= 0);
-/*
- * The error messages that can be shared are included here.
- * Excluded are errors that are only used once and debugging messages.
- */
+// The error messages that can be shared are included here.
+// Excluded are errors that are only used once and debugging messages.
EXTERN char_u e_abort[] INIT(= N_("E470: Command aborted"));
EXTERN char_u e_afterinit[] INIT(= N_(
"E905: Cannot set this option after startup"));
@@ -992,6 +936,14 @@ EXTERN char_u e_re_damg[] INIT(= N_("E43: Damaged match string"));
EXTERN char_u e_re_corr[] INIT(= N_("E44: Corrupted regexp program"));
EXTERN char_u e_readonly[] INIT(= N_(
"E45: 'readonly' option is set (add ! to override)"));
+EXTERN char_u e_readonlyvar[] INIT(= N_(
+ "E46: Cannot change read-only variable \"%.*s\""));
+EXTERN char_u e_dictreq[] INIT(= N_("E715: Dictionary required"));
+EXTERN char_u e_toomanyarg[] INIT(= N_("E118: Too many arguments for function: %s"));
+EXTERN char_u e_dictkey[] INIT(= N_("E716: Key not present in Dictionary: %s"));
+EXTERN char_u e_listreq[] INIT(= N_("E714: List required"));
+EXTERN char_u e_listdictarg[] INIT(= N_(
+ "E712: Argument of %s must be a List or Dictionary"));
EXTERN char_u e_readerrf[] INIT(= N_("E47: Error while reading errorfile"));
EXTERN char_u e_sandbox[] INIT(= N_("E48: Not allowed in sandbox"));
EXTERN char_u e_secure[] INIT(= N_("E523: Not allowed here"));
@@ -1065,7 +1017,7 @@ EXTERN char line_msg[] INIT(= N_(" line "));
// For undo we need to know the lowest time possible.
EXTERN time_t starttime;
-EXTERN FILE *time_fd INIT(= NULL); /* where to write startup timing */
+EXTERN FILE *time_fd INIT(= NULL); // where to write startup timing
// Some compilers warn for not using a return value, but in some situations we
// can't do anything useful with the value. Assign to this variable to avoid
@@ -1101,4 +1053,4 @@ typedef enum {
#define MIN_CD_SCOPE kCdScopeWindow
#define MAX_CD_SCOPE kCdScopeGlobal
-#endif /* NVIM_GLOBALS_H */
+#endif // NVIM_GLOBALS_H
diff --git a/src/nvim/grid_defs.h b/src/nvim/grid_defs.h
index 9e588d0387..c6687c8da9 100644
--- a/src/nvim/grid_defs.h
+++ b/src/nvim/grid_defs.h
@@ -18,7 +18,7 @@ typedef int16_t sattr_T;
/// chars[] contains the UTF-8 text that is currently displayed on the grid.
/// It is stored as a single block of cells. When redrawing a part of the grid,
/// the new state can be compared with the existing state of the grid. This way
-/// we can avoid sending bigger updates than neccessary to the Ul layer.
+/// we can avoid sending bigger updates than necessary to the Ul layer.
///
/// Screen cells are stored as NUL-terminated UTF-8 strings, and a cell can
/// contain up to MAX_MCO composing characters after the base character.
diff --git a/src/nvim/hardcopy.c b/src/nvim/hardcopy.c
index cb5c5338a1..f1f84e63be 100644
--- a/src/nvim/hardcopy.c
+++ b/src/nvim/hardcopy.c
@@ -162,21 +162,21 @@ static option_table_T mbfont_opts[OPT_MBFONT_NUM_OPTIONS] =
* These values determine the print position on a page.
*/
typedef struct {
- int lead_spaces; /* remaining spaces for a TAB */
- int print_pos; /* virtual column for computing TABs */
- colnr_T column; /* byte column */
- linenr_T file_line; /* line nr in the buffer */
- size_t bytes_printed; /* bytes printed so far */
- int ff; /* seen form feed character */
+ int lead_spaces; // remaining spaces for a TAB
+ int print_pos; // virtual column for computing TABs
+ colnr_T column; // byte column
+ linenr_T file_line; // line nr in the buffer
+ size_t bytes_printed; // bytes printed so far
+ int ff; // seen form feed character
} prt_pos_T;
struct prt_mediasize_S {
char *name;
- double width; /* width and height in points for portrait */
+ double width; // width and height in points for portrait
double height;
};
-/* PS font names, must be in Roman, Bold, Italic, Bold-Italic order */
+// PS font names, must be in Roman, Bold, Italic, Bold-Italic order
struct prt_ps_font_S {
int wx;
int uline_offset;
@@ -200,7 +200,7 @@ struct prt_ps_charset_S {
int has_charset;
};
-/* Collections of encodings and charsets for multi-byte printing */
+// Collections of encodings and charsets for multi-byte printing
struct prt_ps_mbfont_S {
int num_encodings;
struct prt_ps_encoding_S *encodings;
@@ -361,9 +361,10 @@ static uint32_t darken_rgb(uint32_t rgb)
static uint32_t prt_get_term_color(int colorindex)
{
- /* TODO: Should check for xterm with 88 or 256 colors. */
- if (t_colors > 8)
+ // TODO(vim): Should check for xterm with 88 or 256 colors.
+ if (t_colors > 8) {
return cterm_color_16[colorindex % 16];
+ }
return cterm_color_8[colorindex % 8];
}
@@ -535,7 +536,7 @@ static void prt_header(prt_settings_T *const psettings, const int pagenum,
p_header, use_sandbox,
' ', width, NULL, NULL);
- /* Reset line numbers */
+ // Reset line numbers
curwin->w_cursor.lnum = tmp_lnum;
curwin->w_topline = tmp_topline;
curwin->w_botline = tmp_botline;
@@ -602,7 +603,7 @@ void ex_hardcopy(exarg_T *eap)
if (*eap->arg == '>') {
char_u *errormsg = NULL;
- /* Expand things like "%.ps". */
+ // Expand things like "%.ps".
if (expand_filename(eap, eap->cmdlinep, &errormsg) == FAIL) {
if (errormsg != NULL)
EMSG(errormsg);
@@ -666,7 +667,7 @@ void ex_hardcopy(exarg_T *eap)
goto print_fail_no_begin;
}
- /* Set colors and font to normal. */
+ // Set colors and font to normal.
curr_bg = 0xffffffff;
curr_fg = 0xffffffff;
curr_italic = kNone;
@@ -691,8 +692,8 @@ void ex_hardcopy(exarg_T *eap)
for (collated_copies = 0;
collated_copies < settings.n_collated_copies;
collated_copies++) {
- prt_pos_T prtpos; /* current print position */
- prt_pos_T page_prtpos; /* print position at page start */
+ prt_pos_T prtpos; // current print position
+ prt_pos_T page_prtpos; // print position at page start
int side;
memset(&page_prtpos, 0, sizeof(prt_pos_T));
@@ -700,7 +701,7 @@ void ex_hardcopy(exarg_T *eap)
prtpos = page_prtpos;
if (jobsplit && collated_copies > 0) {
- /* Splitting jobs: Stop a previous job and start a new one. */
+ // Splitting jobs: Stop a previous job and start a new one.
mch_print_end(&settings);
if (!mch_print_begin(&settings))
goto print_fail_no_begin;
@@ -717,7 +718,7 @@ void ex_hardcopy(exarg_T *eap)
for (uncollated_copies = 0;
uncollated_copies < settings.n_uncollated_copies;
uncollated_copies++) {
- /* Set the print position to the start of this page. */
+ // Set the print position to the start of this page.
prtpos = page_prtpos;
/*
@@ -728,7 +729,7 @@ void ex_hardcopy(exarg_T *eap)
* Print one page.
*/
- /* Check for interrupt character every page. */
+ // Check for interrupt character every page.
os_breakcheck();
if (got_int || settings.user_abort)
goto print_fail;
@@ -759,11 +760,12 @@ void ex_hardcopy(exarg_T *eap)
prtpos.column = hardcopy_line(&settings,
page_line, &prtpos);
if (prtpos.column == 0) {
- /* finished a file line */
+ // finished a file line
prtpos.bytes_printed +=
STRLEN(skipwhite(ml_get(prtpos.file_line)));
- if (++prtpos.file_line > eap->line2)
- break; /* reached the end */
+ if (++prtpos.file_line > eap->line2) {
+ break; // reached the end
+ }
} else if (prtpos.ff) {
/* Line had a formfeed in it - start new page but
* stay on the current line */
@@ -771,10 +773,12 @@ void ex_hardcopy(exarg_T *eap)
}
}
- if (!mch_print_end_page())
+ if (!mch_print_end_page()) {
goto print_fail;
- if (prtpos.file_line > eap->line2)
- break; /* reached the end */
+ }
+ if (prtpos.file_line > eap->line2) {
+ break; // reached the end
+ }
}
/*
@@ -791,7 +795,7 @@ void ex_hardcopy(exarg_T *eap)
if (settings.duplex && prtpos.file_line <= eap->line2)
++page_count;
- /* Remember the position where the next page starts. */
+ // Remember the position where the next page starts.
page_prtpos = prtpos;
}
@@ -855,7 +859,7 @@ static colnr_T hardcopy_line(prt_settings_T *psettings, int page_line, prt_pos_T
id = syn_get_final_id(id);
else
id = 0;
- /* Get the line again, a multi-line regexp may invalidate it. */
+ // Get the line again, a multi-line regexp may invalidate it.
line = ml_get(ppos->file_line);
if (id != current_syn_id) {
@@ -881,9 +885,10 @@ static colnr_T hardcopy_line(prt_settings_T *psettings, int page_line, prt_pos_T
if (need_break)
break;
}
- /* Keep the TAB if we didn't finish it. */
- if (need_break && tab_spaces > 0)
+ // Keep the TAB if we didn't finish it.
+ if (need_break && tab_spaces > 0) {
break;
+ }
} else if (line[col] == FF
&& printer_opts[OPT_PRINT_FORMFEED].present
&& TOLOWER_ASC(printer_opts[OPT_PRINT_FORMFEED].string[0])
@@ -942,7 +947,7 @@ static colnr_T hardcopy_line(prt_settings_T *psettings, int page_line, prt_pos_T
* http://www.adobe.com
*/
-#define PRT_PS_DEFAULT_DPI (72) /* Default user space resolution */
+#define PRT_PS_DEFAULT_DPI (72) // Default user space resolution
#define PRT_PS_DEFAULT_FONTSIZE (10)
#define PRT_PS_DEFAULT_BUFFER_SIZE (80)
@@ -951,20 +956,20 @@ static colnr_T hardcopy_line(prt_settings_T *psettings, int page_line, prt_pos_T
static struct prt_mediasize_S prt_mediasize[] =
{
- {"A4", 595.0, 842.0},
- {"letter", 612.0, 792.0},
- {"10x14", 720.0, 1008.0},
- {"A3", 842.0, 1191.0},
- {"A5", 420.0, 595.0},
- {"B4", 729.0, 1032.0},
- {"B5", 516.0, 729.0},
- {"executive", 522.0, 756.0},
- {"folio", 595.0, 935.0},
- {"ledger", 1224.0, 792.0}, /* Yes, it is wider than taller! */
- {"legal", 612.0, 1008.0},
- {"quarto", 610.0, 780.0},
- {"statement", 396.0, 612.0},
- {"tabloid", 792.0, 1224.0}
+ { "A4", 595.0, 842.0 },
+ { "letter", 612.0, 792.0 },
+ { "10x14", 720.0, 1008.0 },
+ { "A3", 842.0, 1191.0 },
+ { "A5", 420.0, 595.0 },
+ { "B4", 729.0, 1032.0 },
+ { "B5", 516.0, 729.0 },
+ { "executive", 522.0, 756.0 },
+ { "folio", 595.0, 935.0 },
+ { "ledger", 1224.0, 792.0 }, // Yes, it is wider than taller!
+ { "legal", 612.0, 1008.0 },
+ { "quarto", 610.0, 780.0 },
+ { "statement", 396.0, 612.0 },
+ { "tabloid", 792.0, 1224.0 }
};
#define PRT_PS_FONT_ROMAN (0)
@@ -972,7 +977,7 @@ static struct prt_mediasize_S prt_mediasize[] =
#define PRT_PS_FONT_OBLIQUE (2)
#define PRT_PS_FONT_BOLDOBLIQUE (3)
-/* Standard font metrics for Courier family */
+// Standard font metrics for Courier family
static struct prt_ps_font_S prt_ps_courier_font =
{
600,
@@ -981,7 +986,7 @@ static struct prt_ps_font_S prt_ps_courier_font =
{"Courier", "Courier-Bold", "Courier-Oblique", "Courier-BoldOblique"}
};
-/* Generic font metrics for multi-byte fonts */
+// Generic font metrics for multi-byte fonts
static struct prt_ps_font_S prt_ps_mb_font =
{
1000,
@@ -990,9 +995,8 @@ static struct prt_ps_font_S prt_ps_mb_font =
{NULL, NULL, NULL, NULL}
};
-/* Pointer to current font set being used */
-static struct prt_ps_font_S* prt_ps_font;
-
+// Pointer to current font set being used
+static struct prt_ps_font_S *prt_ps_font;
#define CS_JIS_C_1978 (0x01)
#define CS_JIS_X_1983 (0x02)
@@ -1003,7 +1007,7 @@ static struct prt_ps_font_S* prt_ps_font;
#define CS_KANJITALK6 (0x40)
#define CS_KANJITALK7 (0x80)
-/* Japanese encodings and charsets */
+// Japanese encodings and charsets
static struct prt_ps_encoding_S j_encodings[] =
{
{"iso-2022-jp", NULL, (CS_JIS_C_1978|CS_JIS_X_1983|CS_JIS_X_1990|
@@ -1035,7 +1039,7 @@ static struct prt_ps_charset_S j_charsets[] =
#define CS_GBK (0x20)
#define CS_SC_ISO10646 (0x40)
-/* Simplified Chinese encodings and charsets */
+// Simplified Chinese encodings and charsets
static struct prt_ps_encoding_S sc_encodings[] =
{
{"iso-2022", NULL, (CS_GB_2312_80|CS_GBT_12345_90)},
@@ -1071,7 +1075,7 @@ static struct prt_ps_charset_S sc_charsets[] =
#define CS_DLHKS (0x800)
#define CS_TC_ISO10646 (0x1000)
-/* Traditional Chinese encodings and charsets */
+// Traditional Chinese encodings and charsets
static struct prt_ps_encoding_S tc_encodings[] =
{
{"iso-2022", NULL, (CS_CNS_PLANE_1|CS_CNS_PLANE_2)},
@@ -1108,7 +1112,7 @@ static struct prt_ps_charset_S tc_charsets[] =
#define CS_KR_X_1992_MS (0x04)
#define CS_KR_ISO10646 (0x08)
-/* Korean encodings and charsets */
+// Korean encodings and charsets
static struct prt_ps_encoding_S k_encodings[] =
{
{"iso-2022-kr", NULL, CS_KR_X_1992},
@@ -1167,7 +1171,7 @@ static struct prt_ps_mbfont_S prt_ps_mbfonts[] =
}
};
-/* Types of PS resource file currently used */
+// Types of PS resource file currently used
#define PRT_RESOURCE_TYPE_PROCSET (0)
#define PRT_RESOURCE_TYPE_ENCODING (1)
#define PRT_RESOURCE_TYPE_CMAP (2)
@@ -1195,7 +1199,7 @@ static char *prt_resource_types[] =
"cmap"
};
-/* Strings to look for in a PS resource file */
+// Strings to look for in a PS resource file
#define PRT_RESOURCE_HEADER "%!PS-Adobe-"
#define PRT_RESOURCE_RESOURCE "Resource-"
#define PRT_RESOURCE_PROCSET "ProcSet"
@@ -1255,20 +1259,20 @@ static double prt_pos_y_moveto = 0.0;
* Various control variables used to decide when and how to change the
* PostScript graphics state.
*/
-static int prt_need_moveto;
-static int prt_do_moveto;
-static int prt_need_font;
+static bool prt_need_moveto;
+static bool prt_do_moveto;
+static bool prt_need_font;
static int prt_font;
-static int prt_need_underline;
+static bool prt_need_underline;
static TriState prt_underline;
static TriState prt_do_underline;
-static int prt_need_fgcol;
+static bool prt_need_fgcol;
static uint32_t prt_fgcol;
-static int prt_need_bgcol;
-static int prt_do_bgcol;
+static bool prt_need_bgcol;
+static bool prt_do_bgcol;
static uint32_t prt_bgcol;
static uint32_t prt_new_bgcol;
-static int prt_attribute_change;
+static bool prt_attribute_change;
static double prt_text_run;
static int prt_page_num;
static int prt_bufsiz;
@@ -1296,8 +1300,8 @@ static int prt_out_mbyte;
static int prt_custom_cmap;
static char prt_cmap[80];
static int prt_use_courier;
-static int prt_in_ascii;
-static int prt_half_width;
+static bool prt_in_ascii;
+static bool prt_half_width;
static char *prt_ascii_encoding;
static char_u prt_hexchar[] = "0123456789abcdef";
@@ -1416,18 +1420,19 @@ static void prt_write_real(double val, int prec)
int fraction;
prt_real_bits(val, prec, &integer, &fraction);
- /* Emit integer part */
- sprintf((char *)prt_line_buffer, "%d", integer);
+ // Emit integer part
+ snprintf((char *)prt_line_buffer, sizeof(prt_line_buffer), "%d", integer);
prt_write_file(prt_line_buffer);
- /* Only emit fraction if necessary */
+ // Only emit fraction if necessary
if (fraction != 0) {
- /* Remove any trailing zeros */
+ // Remove any trailing zeros
while ((fraction % 10) == 0) {
prec--;
fraction /= 10;
}
- /* Emit fraction left padded with zeros */
- sprintf((char *)prt_line_buffer, ".%0*d", prec, fraction);
+ // Emit fraction left padded with zeros
+ snprintf((char *)prt_line_buffer, sizeof(prt_line_buffer), ".%0*d",
+ prec, fraction);
prt_write_file(prt_line_buffer);
}
sprintf((char *)prt_line_buffer, " ");
@@ -1447,13 +1452,13 @@ static void prt_def_var(char *name, double value, int prec)
prt_write_file(prt_line_buffer);
}
-/* Convert size from font space to user space at current font scale */
+// Convert size from font space to user space at current font scale
#define PRT_PS_FONT_TO_USER(scale, size) ((size) * ((scale)/1000.0))
static void prt_flush_buffer(void)
{
if (!GA_EMPTY(&prt_ps_buffer)) {
- /* Any background color must be drawn first */
+ // Any background color must be drawn first
if (prt_do_bgcol && (prt_new_bgcol != PRCOLOR_WHITE)) {
unsigned int r, g, b;
@@ -1461,14 +1466,14 @@ static void prt_flush_buffer(void)
prt_write_real(prt_pos_x_moveto, 2);
prt_write_real(prt_pos_y_moveto, 2);
prt_write_string("m\n");
- prt_do_moveto = FALSE;
+ prt_do_moveto = false;
}
- /* Size of rect of background color on which text is printed */
+ // Size of rect of background color on which text is printed
prt_write_real(prt_text_run, 2);
prt_write_real(prt_line_height, 2);
- /* Lastly add the color of the background */
+ // Lastly add the color of the background
r = (prt_new_bgcol & 0xff0000) >> 16;
g = (prt_new_bgcol & 0xff00) >> 8;
b = prt_new_bgcol & 0xff;
@@ -1485,10 +1490,10 @@ static void prt_flush_buffer(void)
prt_write_real(prt_pos_x_moveto, 2);
prt_write_real(prt_pos_y_moveto, 2);
prt_write_string("m\n");
- prt_do_moveto = FALSE;
+ prt_do_moveto = false;
}
- /* Underline length of text run */
+ // Underline length of text run
prt_write_real(prt_text_run, 2);
prt_write_string("ul\n");
}
@@ -1503,16 +1508,16 @@ static void prt_flush_buffer(void)
prt_write_string(">");
else
prt_write_string(")");
- /* Add a moveto if need be and use the appropriate show procedure */
+ // Add a moveto if need be and use the appropriate show procedure
if (prt_do_moveto) {
prt_write_real(prt_pos_x_moveto, 2);
prt_write_real(prt_pos_y_moveto, 2);
- /* moveto and a show */
+ // moveto and a show
prt_write_string("ms\n");
- prt_do_moveto = FALSE;
- } else /* Simple show */
+ prt_do_moveto = false;
+ } else { // Simple show
prt_write_string("s\n");
-
+ }
ga_clear(&prt_ps_buffer);
ga_init(&prt_ps_buffer, (int)sizeof(char), prt_bufsiz);
}
@@ -1536,7 +1541,7 @@ static int prt_find_resource(char *name, struct prt_ps_resource_S *resource)
buffer = xmallocz(MAXPATHL);
STRLCPY(resource->name, name, 64);
- /* Look for named resource file in runtimepath */
+ // Look for named resource file in runtimepath
STRCPY(buffer, "print");
add_pathsep((char *)buffer);
xstrlcat((char *)buffer, name, MAXPATHL);
@@ -1548,7 +1553,7 @@ static int prt_find_resource(char *name, struct prt_ps_resource_S *resource)
return retval;
}
-/* PS CR and LF characters have platform independent values */
+// PS CR and LF characters have platform independent values
#define PSLF (0x0a)
#define PSCR (0x0d)
@@ -1558,7 +1563,7 @@ static int prt_resfile_next_line(void)
{
int idx;
- /* Move to start of next line and then find end of line */
+ // Move to start of next line and then find end of line
idx = prt_resfile.line_end + 1;
while (idx < prt_resfile.len) {
if (prt_resfile.buffer[idx] != PSLF && prt_resfile.buffer[idx] != PSCR)
@@ -1577,12 +1582,13 @@ static int prt_resfile_next_line(void)
return idx < prt_resfile.len;
}
-static int prt_resfile_strncmp(int offset, char *string, int len)
+static int prt_resfile_strncmp(int offset, const char *string, int len)
+ FUNC_ATTR_NONNULL_ALL
{
- /* Force not equal if string is longer than remainder of line */
- if (len > (prt_resfile.line_end - (prt_resfile.line_start + offset)))
+ // Force not equal if string is longer than remainder of line
+ if (len > (prt_resfile.line_end - (prt_resfile.line_start + offset))) {
return 1;
-
+ }
return STRNCMP(&prt_resfile.buffer[prt_resfile.line_start + offset],
string, len);
}
@@ -1615,178 +1621,182 @@ static int prt_resfile_skip_ws(int offset)
/* prt_next_dsc() - returns detail on next DSC comment line found. Returns true
* if a DSC comment is found, else false */
-static int prt_next_dsc(struct prt_dsc_line_S *p_dsc_line)
+static bool prt_next_dsc(struct prt_dsc_line_S *p_dsc_line)
+ FUNC_ATTR_NONNULL_ALL
{
int comment;
int offset;
- /* Move to start of next line */
- if (!prt_resfile_next_line())
- return FALSE;
-
- /* DSC comments always start %% */
- if (prt_resfile_strncmp(0, "%%", 2) != 0)
- return FALSE;
-
- /* Find type of DSC comment */
- for (comment = 0; comment < (int)ARRAY_SIZE(prt_dsc_table); comment++)
+ // Move to start of next line
+ if (!prt_resfile_next_line()) {
+ return false;
+ }
+ // DSC comments always start %%
+ if (prt_resfile_strncmp(0, "%%", 2) != 0) {
+ return false;
+ }
+ // Find type of DSC comment
+ for (comment = 0; comment < (int)ARRAY_SIZE(prt_dsc_table); comment++) {
if (prt_resfile_strncmp(0, prt_dsc_table[comment].string,
- prt_dsc_table[comment].len) == 0)
+ prt_dsc_table[comment].len) == 0) {
break;
-
+ }
+ }
if (comment != ARRAY_SIZE(prt_dsc_table)) {
- /* Return type of comment */
+ // Return type of comment
p_dsc_line->type = prt_dsc_table[comment].type;
offset = prt_dsc_table[comment].len;
} else {
- /* Unrecognised DSC comment, skip to ws after comment leader */
+ // Unrecognised DSC comment, skip to ws after comment leader
p_dsc_line->type = PRT_DSC_MISC_TYPE;
offset = prt_resfile_skip_nonws(0);
- if (offset == -1)
- return FALSE;
+ if (offset == -1) {
+ return false;
+ }
}
- /* Skip ws to comment value */
+ // Skip ws to comment value
offset = prt_resfile_skip_ws(offset);
- if (offset == -1)
- return FALSE;
-
+ if (offset == -1) {
+ return false;
+ }
p_dsc_line->string = &prt_resfile.buffer[prt_resfile.line_start + offset];
p_dsc_line->len = prt_resfile.line_end - (prt_resfile.line_start + offset);
- return TRUE;
+ return true;
}
/* Improved hand crafted parser to get the type, title, and version number of a
* PS resource file so the file details can be added to the DSC header comments.
*/
-static int prt_open_resource(struct prt_ps_resource_S *resource)
+static bool prt_open_resource(struct prt_ps_resource_S *resource)
+ FUNC_ATTR_NONNULL_ALL
{
- int offset;
- int seen_all;
- int seen_title;
- int seen_version;
- FILE *fd_resource;
struct prt_dsc_line_S dsc_line;
- fd_resource = os_fopen((char *)resource->filename, READBIN);
+ FILE *fd_resource = os_fopen((char *)resource->filename, READBIN);
if (fd_resource == NULL) {
EMSG2(_("E624: Can't open file \"%s\""), resource->filename);
- return FALSE;
+ return false;
}
memset(prt_resfile.buffer, NUL, PRT_FILE_BUFFER_LEN);
- /* Parse first line to ensure valid resource file */
+ // Parse first line to ensure valid resource file
prt_resfile.len = (int)fread((char *)prt_resfile.buffer, sizeof(char_u),
PRT_FILE_BUFFER_LEN, fd_resource);
if (ferror(fd_resource)) {
EMSG2(_("E457: Can't read PostScript resource file \"%s\""),
resource->filename);
fclose(fd_resource);
- return FALSE;
+ return false;
}
fclose(fd_resource);
prt_resfile.line_end = -1;
prt_resfile.line_start = 0;
- if (!prt_resfile_next_line())
- return FALSE;
-
- offset = 0;
+ if (!prt_resfile_next_line()) {
+ return false;
+ }
+ int offset = 0;
if (prt_resfile_strncmp(offset, PRT_RESOURCE_HEADER,
(int)STRLEN(PRT_RESOURCE_HEADER)) != 0) {
EMSG2(_("E618: file \"%s\" is not a PostScript resource file"),
- resource->filename);
- return FALSE;
+ resource->filename);
+ return false;
}
- /* Skip over any version numbers and following ws */
+ // Skip over any version numbers and following ws
offset += (int)STRLEN(PRT_RESOURCE_HEADER);
offset = prt_resfile_skip_nonws(offset);
- if (offset == -1)
- return FALSE;
+ if (offset == -1) {
+ return false;
+ }
offset = prt_resfile_skip_ws(offset);
- if (offset == -1)
- return FALSE;
-
+ if (offset == -1) {
+ return false;
+ }
if (prt_resfile_strncmp(offset, PRT_RESOURCE_RESOURCE,
(int)STRLEN(PRT_RESOURCE_RESOURCE)) != 0) {
EMSG2(_("E619: file \"%s\" is not a supported PostScript resource file"),
- resource->filename);
- return FALSE;
+ resource->filename);
+ return false;
}
offset += (int)STRLEN(PRT_RESOURCE_RESOURCE);
- /* Decide type of resource in the file */
+ // Decide type of resource in the file
if (prt_resfile_strncmp(offset, PRT_RESOURCE_PROCSET,
- (int)STRLEN(PRT_RESOURCE_PROCSET)) == 0)
+ (int)STRLEN(PRT_RESOURCE_PROCSET)) == 0) {
resource->type = PRT_RESOURCE_TYPE_PROCSET;
- else if (prt_resfile_strncmp(offset, PRT_RESOURCE_ENCODING,
- (int)STRLEN(PRT_RESOURCE_ENCODING)) == 0)
+ } else if (prt_resfile_strncmp(offset, PRT_RESOURCE_ENCODING,
+ (int)STRLEN(PRT_RESOURCE_ENCODING)) == 0) {
resource->type = PRT_RESOURCE_TYPE_ENCODING;
- else if (prt_resfile_strncmp(offset, PRT_RESOURCE_CMAP,
- (int)STRLEN(PRT_RESOURCE_CMAP)) == 0)
+ } else if (prt_resfile_strncmp(offset, PRT_RESOURCE_CMAP,
+ (int)STRLEN(PRT_RESOURCE_CMAP)) == 0) {
resource->type = PRT_RESOURCE_TYPE_CMAP;
- else {
+ } else {
EMSG2(_("E619: file \"%s\" is not a supported PostScript resource file"),
- resource->filename);
- return FALSE;
+ resource->filename);
+ return false;
}
- /* Look for title and version of resource */
+ // Look for title and version of resource
resource->title[0] = '\0';
resource->version[0] = '\0';
- seen_title = FALSE;
- seen_version = FALSE;
- seen_all = FALSE;
+ bool seen_title = false;
+ bool seen_version = false;
+ bool seen_all = false;
while (!seen_all && prt_next_dsc(&dsc_line)) {
switch (dsc_line.type) {
case PRT_DSC_TITLE_TYPE:
STRLCPY(resource->title, dsc_line.string, dsc_line.len + 1);
- seen_title = TRUE;
- if (seen_version)
- seen_all = TRUE;
+ seen_title = true;
+ if (seen_version) {
+ seen_all = true;
+ }
break;
case PRT_DSC_VERSION_TYPE:
STRLCPY(resource->version, dsc_line.string, dsc_line.len + 1);
- seen_version = TRUE;
- if (seen_title)
- seen_all = TRUE;
+ seen_version = true;
+ if (seen_title) {
+ seen_all = true;
+ }
break;
case PRT_DSC_ENDCOMMENTS_TYPE:
- /* Wont find title or resource after this comment, stop searching */
- seen_all = TRUE;
+ // Wont find title or resource after this comment, stop searching
+ seen_all = true;
break;
case PRT_DSC_MISC_TYPE:
- /* Not interested in whatever comment this line had */
+ // Not interested in whatever comment this line had
break;
}
}
if (!seen_title || !seen_version) {
EMSG2(_("E619: file \"%s\" is not a supported PostScript resource file"),
- resource->filename);
- return FALSE;
+ resource->filename);
+ return false;
}
- return TRUE;
+ return true;
}
-static int prt_check_resource(struct prt_ps_resource_S *resource, char_u *version)
+static bool prt_check_resource(const struct prt_ps_resource_S *resource,
+ const char_u *version)
+ FUNC_ATTR_NONNULL_ALL
{
- /* Version number m.n should match, the revision number does not matter */
+ // Version number m.n should match, the revision number does not matter
if (STRNCMP(resource->version, version, STRLEN(version))) {
EMSG2(_("E621: \"%s\" resource file has wrong version"),
- resource->name);
- return FALSE;
+ resource->name);
+ return false;
}
- /* Other checks to be added as needed */
- return TRUE;
+ // Other checks to be added as needed
+ return true;
}
static void prt_dsc_start(void)
@@ -1810,7 +1820,7 @@ static void prt_dsc_textline(char *comment, char *text)
static void prt_dsc_text(char *comment, char *text)
{
- /* TODO - should scan 'text' for any chars needing escaping! */
+ // TODO(vim): - should scan 'text' for any chars needing escaping!
vim_snprintf((char *)prt_line_buffer, sizeof(prt_line_buffer),
"%%%%%s: (%s)\n", comment, text);
prt_write_file(prt_line_buffer);
@@ -1834,9 +1844,8 @@ static void prt_dsc_ints(char *comment, int count, int *ints)
prt_write_string("\n");
}
-static void
-prt_dsc_resources (
- char *comment, /* if NULL add to previous */
+static void prt_dsc_resources(
+ char *comment, // if NULL add to previous
char *type,
char *string
)
@@ -1887,8 +1896,9 @@ static void prt_dsc_requirements(int duplex, int tumble, int collate, int color,
prt_write_string(" color");
if (num_copies > 1) {
prt_write_string(" numcopies(");
- /* Note: no space wanted so don't use prt_write_int() */
- sprintf((char *)prt_line_buffer, "%d", num_copies);
+ // Note: no space wanted so don't use prt_write_int()
+ snprintf((char *)prt_line_buffer, sizeof(prt_line_buffer), "%d",
+ num_copies);
prt_write_file(prt_line_buffer);
prt_write_string(")");
}
@@ -2038,13 +2048,13 @@ static int prt_get_lpp(void)
prt_ps_font->bbox_min_y)) / 2);
}
- /* Get height for topmost line based on background rect offset. */
+ // Get height for topmost line based on background rect offset.
prt_first_line_height = prt_line_height + prt_bgcol_offset;
- /* Calculate lpp */
+ // Calculate lpp
lpp = (int)((prt_top_margin - prt_bottom_margin) / prt_line_height);
- /* Adjust top margin if there is a header */
+ // Adjust top margin if there is a header
prt_top_margin -= prt_line_height * prt_header_height();
return lpp - prt_header_height();
@@ -2057,7 +2067,7 @@ static int prt_match_encoding(char *p_encoding, struct prt_ps_mbfont_S *p_cmap,
struct prt_ps_encoding_S *p_mbenc;
*pp_mbenc = NULL;
- /* Look for recognised encoding */
+ // Look for recognised encoding
enc_len = (int)STRLEN(p_encoding);
p_mbenc = p_cmap->encodings;
for (mbenc = 0; mbenc < p_cmap->num_encodings; mbenc++) {
@@ -2076,9 +2086,10 @@ static int prt_match_charset(char *p_charset, struct prt_ps_mbfont_S *p_cmap, st
int char_len;
struct prt_ps_charset_S *p_mbchar;
- /* Look for recognised character set, using default if one is not given */
- if (*p_charset == NUL)
+ // Look for recognised character set, using default if one is not given
+ if (*p_charset == NUL) {
p_charset = p_cmap->defcs;
+ }
char_len = (int)STRLEN(p_charset);
p_mbchar = p_cmap->charsets;
for (mbchar = 0; mbchar < p_cmap->num_charsets; mbchar++) {
@@ -2133,7 +2144,7 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit)
break;
}
- /* Use first encoding matched if no charset matched */
+ // Use first encoding matched if no charset matched
if (p_mbenc_first != NULL && p_mbchar == NULL) {
p_mbenc = p_mbenc_first;
cmap = effective_cmap;
@@ -2144,24 +2155,24 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit)
prt_out_mbyte = (p_mbenc != NULL);
if (prt_out_mbyte) {
- /* Build CMap name - will be same for all multi-byte fonts used */
+ // Build CMap name - will be same for all multi-byte fonts used
prt_cmap[0] = NUL;
prt_custom_cmap = (p_mbchar == NULL);
if (!prt_custom_cmap) {
- /* Check encoding and character set are compatible */
+ // Check encoding and character set are compatible
if ((p_mbenc->needs_charset & p_mbchar->has_charset) == 0) {
EMSG(_("E673: Incompatible multi-byte encoding and character set."));
return FALSE;
}
- /* Add charset name if not empty */
+ // Add charset name if not empty
if (p_mbchar->cmap_charset != NULL) {
STRLCPY(prt_cmap, p_mbchar->cmap_charset, sizeof(prt_cmap) - 2);
STRCAT(prt_cmap, "-");
}
} else {
- /* Add custom CMap character set name */
+ // Add custom CMap character set name
if (*p_pmcs == NUL) {
EMSG(_("E674: printmbcharset cannot be empty with multi-byte encoding."));
return FALSE;
@@ -2170,7 +2181,7 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit)
STRCAT(prt_cmap, "-");
}
- /* CMap name ends with (optional) encoding name and -H for horizontal */
+ // CMap name ends with (optional) encoding name and -H for horizontal
if (p_mbenc->cmap_encoding != NULL && STRLEN(prt_cmap)
+ STRLEN(p_mbenc->cmap_encoding) + 3 < sizeof(prt_cmap)) {
STRCAT(prt_cmap, p_mbenc->cmap_encoding);
@@ -2183,7 +2194,7 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit)
return FALSE;
}
- /* Derive CID font names with fallbacks if not defined */
+ // Derive CID font names with fallbacks if not defined
prt_build_cid_fontname(PRT_PS_FONT_ROMAN,
mbfont_opts[OPT_MBFONT_REGULAR].string,
mbfont_opts[OPT_MBFONT_REGULAR].strlen);
@@ -2288,9 +2299,10 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit)
psettings->chars_per_line = prt_get_cpl();
psettings->lines_per_page = prt_get_lpp();
- /* Catch margin settings that leave no space for output! */
- if (psettings->chars_per_line <= 0 || psettings->lines_per_page <= 0)
+ // Catch margin settings that leave no space for output!
+ if (psettings->chars_per_line <= 0 || psettings->lines_per_page <= 0) {
return FAIL;
+ }
/*
* Sort out the number of copies to be printed. PS by default will do
@@ -2329,10 +2341,10 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit)
prt_tumble = TRUE;
}
- /* For now user abort not supported */
+ // For now user abort not supported
psettings->user_abort = 0;
- /* If the user didn't specify a file name, use a temp file. */
+ // If the user didn't specify a file name, use a temp file.
if (psettings->outfile == NULL) {
prt_ps_file_name = vim_tempname();
if (prt_ps_file_name == NULL) {
@@ -2360,12 +2372,12 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit)
prt_page_num = 0;
- prt_attribute_change = FALSE;
- prt_need_moveto = FALSE;
- prt_need_font = FALSE;
- prt_need_fgcol = FALSE;
- prt_need_bgcol = FALSE;
- prt_need_underline = FALSE;
+ prt_attribute_change = false;
+ prt_need_moveto = false;
+ prt_need_font = false;
+ prt_need_fgcol = false;
+ prt_need_bgcol = false;
+ prt_need_underline = false;
prt_file_error = FALSE;
@@ -2416,9 +2428,7 @@ static int prt_add_resource(struct prt_ps_resource_S *resource)
int mch_print_begin(prt_settings_T *psettings)
{
- time_t now;
int bbox[4];
- char *p_time;
double left;
double right;
double top;
@@ -2442,10 +2452,10 @@ int mch_print_begin(prt_settings_T *psettings)
}
prt_dsc_textline("For", buffer);
prt_dsc_textline("Creator", longVersion);
- /* Note: to ensure Clean8bit I don't think we can use LC_TIME */
- now = time(NULL);
- p_time = ctime(&now);
- /* Note: ctime() adds a \n so we have to remove it :-( */
+ // Note: to ensure Clean8bit I don't think we can use LC_TIME
+ char ctime_buf[50];
+ char *p_time = os_ctime(ctime_buf, sizeof(ctime_buf));
+ // Note: os_ctime() adds a \n so we have to remove it :-(
p = vim_strchr((char_u *)p_time, '\n');
if (p != NULL)
*p = NUL;
@@ -2483,14 +2493,15 @@ int mch_print_begin(prt_settings_T *psettings)
+ 0.5);
}
prt_dsc_ints("BoundingBox", 4, bbox);
- /* The media width and height does not change with landscape printing! */
+ // The media width and height does not change with landscape printing!
prt_dsc_docmedia(prt_mediasize[prt_media].name,
- prt_mediasize[prt_media].width,
- prt_mediasize[prt_media].height,
- (double)0, NULL, NULL);
- /* Define fonts needed */
- if (!prt_out_mbyte || prt_use_courier)
+ prt_mediasize[prt_media].width,
+ prt_mediasize[prt_media].height,
+ (double)0, NULL, NULL);
+ // Define fonts needed
+ if (!prt_out_mbyte || prt_use_courier) {
prt_dsc_font_resource("DocumentNeededResources", &prt_ps_courier_font);
+ }
if (prt_out_mbyte) {
prt_dsc_font_resource((prt_use_courier ? NULL
: "DocumentNeededResources"), &prt_ps_mb_font);
@@ -2498,7 +2509,7 @@ int mch_print_begin(prt_settings_T *psettings)
prt_dsc_resources(NULL, "cmap", prt_cmap);
}
- /* Search for external resources VIM supplies */
+ // Search for external resources VIM supplies
if (!prt_find_resource("prolog", &res_prolog)) {
EMSG(_("E456: Can't find PostScript resource file \"prolog.ps\""));
return FALSE;
@@ -2508,7 +2519,7 @@ int mch_print_begin(prt_settings_T *psettings)
if (!prt_check_resource(&res_prolog, PRT_PROLOG_VERSION))
return FALSE;
if (prt_out_mbyte) {
- /* Look for required version of multi-byte printing procset */
+ // Look for required version of multi-byte printing procset
if (!prt_find_resource("cidfont", &res_cidfont)) {
EMSG(_("E456: Can't find PostScript resource file \"cidfont.ps\""));
return FALSE;
@@ -2528,15 +2539,15 @@ int mch_print_begin(prt_settings_T *psettings)
p_encoding = enc_skip(p_penc);
if (*p_encoding == NUL
|| !prt_find_resource((char *)p_encoding, &res_encoding)) {
- /* 'printencoding' not set or not supported - find alternate */
+ // 'printencoding' not set or not supported - find alternate
int props;
p_encoding = enc_skip(p_enc);
props = enc_canon_props(p_encoding);
if (!(props & ENC_8BIT)
|| !prt_find_resource((char *)p_encoding, &res_encoding)) {
- /* 8-bit 'encoding' is not supported */
- /* Use latin1 as default printing encoding */
+ // 8-bit 'encoding' is not supported
+ // Use latin1 as default printing encoding
p_encoding = (char_u *)"latin1";
if (!prt_find_resource((char *)p_encoding, &res_encoding)) {
EMSG2(_("E456: Can't find PostScript resource file \"%s.ps\""),
@@ -2554,7 +2565,7 @@ int mch_print_begin(prt_settings_T *psettings)
if (*p_encoding == NUL)
p_encoding = enc_skip(p_enc);
if (prt_use_courier) {
- /* Include ASCII range encoding vector */
+ // Include ASCII range encoding vector
if (!prt_find_resource(prt_ascii_encoding, &res_encoding)) {
EMSG2(_("E456: Can't find PostScript resource file \"%s.ps\""),
prt_ascii_encoding);
@@ -2579,7 +2590,7 @@ int mch_print_begin(prt_settings_T *psettings)
prt_do_conv = prt_conv.vc_type != CONV_NONE;
if (prt_out_mbyte && prt_custom_cmap) {
- /* Find user supplied CMap */
+ // Find user supplied CMap
if (!prt_find_resource(prt_cmap, &res_cmap)) {
EMSG2(_("E456: Can't find PostScript resource file \"%s.ps\""),
prt_cmap);
@@ -2589,7 +2600,7 @@ int mch_print_begin(prt_settings_T *psettings)
return FALSE;
}
- /* List resources supplied */
+ // List resources supplied
STRCPY(buffer, res_prolog.title);
STRCAT(buffer, " ");
STRCAT(buffer, res_prolog.version);
@@ -2623,9 +2634,10 @@ int mch_print_begin(prt_settings_T *psettings)
*/
prt_dsc_noarg("BeginDefaults");
- /* List font resources most likely common to all pages */
- if (!prt_out_mbyte || prt_use_courier)
+ // List font resources most likely common to all pages
+ if (!prt_out_mbyte || prt_use_courier) {
prt_dsc_font_resource("PageResources", &prt_ps_courier_font);
+ }
if (prt_out_mbyte) {
prt_dsc_font_resource((prt_use_courier ? NULL : "PageResources"),
&prt_ps_mb_font);
@@ -2633,7 +2645,7 @@ int mch_print_begin(prt_settings_T *psettings)
prt_dsc_resources(NULL, "cmap", prt_cmap);
}
- /* Paper will be used for all pages */
+ // Paper will be used for all pages
prt_dsc_textline("PageMedia", prt_mediasize[prt_media].name);
prt_dsc_noarg("EndDefaults");
@@ -2643,15 +2655,18 @@ int mch_print_begin(prt_settings_T *psettings)
*/
prt_dsc_noarg("BeginProlog");
- /* Add required procsets - NOTE: order is important! */
- if (!prt_add_resource(&res_prolog))
- return FALSE;
+ // Add required procsets - NOTE: order is important!
+ if (!prt_add_resource(&res_prolog)) {
+ return false;
+ }
if (prt_out_mbyte) {
- /* Add CID font procset, and any user supplied CMap */
- if (!prt_add_resource(&res_cidfont))
- return FALSE;
- if (prt_custom_cmap && !prt_add_resource(&res_cmap))
- return FALSE;
+ // Add CID font procset, and any user supplied CMap
+ if (!prt_add_resource(&res_cidfont)) {
+ return false;
+ }
+ if (prt_custom_cmap && !prt_add_resource(&res_cmap)) {
+ return false;
+ }
}
if (!prt_out_mbyte || prt_use_courier)
@@ -2667,7 +2682,7 @@ int mch_print_begin(prt_settings_T *psettings)
*/
prt_dsc_noarg("BeginSetup");
- /* Device setup - page size and number of uncollated copies */
+ // Device setup - page size and number of uncollated copies
prt_write_int((int)prt_mediasize[prt_media].width);
prt_write_int((int)prt_mediasize[prt_media].height);
prt_write_int(0);
@@ -2680,7 +2695,7 @@ int mch_print_begin(prt_settings_T *psettings)
prt_write_boolean(prt_collate);
prt_write_string("c\n");
- /* Font resource inclusion and definition */
+ // Font resource inclusion and definition
if (!prt_out_mbyte || prt_use_courier) {
/* When using Courier for ASCII range when printing multi-byte, need to
* pick up ASCII encoding to use with it. */
@@ -2723,35 +2738,36 @@ int mch_print_begin(prt_settings_T *psettings)
if (!prt_custom_cmap)
prt_dsc_resources("IncludeResource", "cmap", prt_cmap);
prt_def_cidfont("CF1", (int)prt_line_height,
- prt_ps_mb_font.ps_fontname[PRT_PS_FONT_BOLD]);
- } else
- /* Use ROMAN for BOLD */
+ prt_ps_mb_font.ps_fontname[PRT_PS_FONT_BOLD]);
+ } else {
+ // Use ROMAN for BOLD
prt_dup_cidfont("CF0", "CF1");
-
+ }
if (prt_ps_mb_font.ps_fontname[PRT_PS_FONT_OBLIQUE] != NULL) {
prt_dsc_resources("IncludeResource", "font",
prt_ps_mb_font.ps_fontname[PRT_PS_FONT_OBLIQUE]);
if (!prt_custom_cmap)
prt_dsc_resources("IncludeResource", "cmap", prt_cmap);
prt_def_cidfont("CF2", (int)prt_line_height,
- prt_ps_mb_font.ps_fontname[PRT_PS_FONT_OBLIQUE]);
- } else
- /* Use ROMAN for OBLIQUE */
+ prt_ps_mb_font.ps_fontname[PRT_PS_FONT_OBLIQUE]);
+ } else {
+ // Use ROMAN for OBLIQUE
prt_dup_cidfont("CF0", "CF2");
-
+ }
if (prt_ps_mb_font.ps_fontname[PRT_PS_FONT_BOLDOBLIQUE] != NULL) {
prt_dsc_resources("IncludeResource", "font",
prt_ps_mb_font.ps_fontname[PRT_PS_FONT_BOLDOBLIQUE]);
if (!prt_custom_cmap)
prt_dsc_resources("IncludeResource", "cmap", prt_cmap);
prt_def_cidfont("CF3", (int)prt_line_height,
- prt_ps_mb_font.ps_fontname[PRT_PS_FONT_BOLDOBLIQUE]);
- } else
- /* Use BOLD for BOLDOBLIQUE */
+ prt_ps_mb_font.ps_fontname[PRT_PS_FONT_BOLDOBLIQUE]);
+ } else {
+ // Use BOLD for BOLDOBLIQUE
prt_dup_cidfont("CF1", "CF3");
+ }
}
- /* Misc constant vars used for underlining and background rects */
+ // Misc constant vars used for underlining and background rects
prt_def_var("UO", PRT_PS_FONT_TO_USER(prt_line_height,
prt_ps_font->uline_offset), 2);
prt_def_var("UW", PRT_PS_FONT_TO_USER(prt_line_height,
@@ -2760,7 +2776,7 @@ int mch_print_begin(prt_settings_T *psettings)
prt_dsc_noarg("EndSetup");
- /* Fail if any problems writing out to the PS file */
+ // Fail if any problems writing out to the PS file
retval = !prt_file_error;
return retval;
@@ -2783,7 +2799,7 @@ void mch_print_end(prt_settings_T *psettings)
if (!prt_file_error && psettings->outfile == NULL
&& !got_int && !psettings->user_abort) {
- /* Close the file first. */
+ // Close the file first.
if (prt_ps_fd != NULL) {
fclose(prt_ps_fd);
prt_ps_fd = NULL;
@@ -2834,7 +2850,7 @@ int mch_print_begin_page(char_u *str)
prt_bgcol = PRCOLOR_WHITE;
prt_font = PRT_PS_FONT_ROMAN;
- /* Set up page transformation for landscape printing. */
+ // Set up page transformation for landscape printing.
if (!prt_portrait) {
prt_write_int(-((int)prt_mediasize[prt_media].width));
prt_write_string("sl\n");
@@ -2842,7 +2858,7 @@ int mch_print_begin_page(char_u *str)
prt_dsc_noarg("EndPageSetup");
- /* We have reset the font attributes, force setting them again. */
+ // We have reset the font attributes, force setting them again.
curr_bg = 0xffffffff;
curr_fg = 0xffffffff;
curr_bold = kNone;
@@ -2868,9 +2884,9 @@ void mch_print_start_line(const bool margin, const int page_line)
prt_pos_y = prt_top_margin - prt_first_line_height -
page_line * prt_line_height;
- prt_attribute_change = TRUE;
- prt_need_moveto = TRUE;
- prt_half_width = FALSE;
+ prt_attribute_change = true;
+ prt_need_moveto = true;
+ prt_half_width = false;
}
int mch_print_text_out(char_u *const textp, size_t len)
@@ -2892,16 +2908,16 @@ int mch_print_text_out(char_u *const textp, size_t len)
const bool in_ascii = (len == 1 && *p < 0x80);
if (prt_in_ascii) {
if (!in_ascii) {
- /* No longer in ASCII range - need to switch font */
- prt_in_ascii = FALSE;
- prt_need_font = TRUE;
- prt_attribute_change = TRUE;
+ // No longer in ASCII range - need to switch font
+ prt_in_ascii = false;
+ prt_need_font = true;
+ prt_attribute_change = true;
}
} else if (in_ascii) {
- /* Now in ASCII range - need to switch font */
- prt_in_ascii = TRUE;
- prt_need_font = TRUE;
- prt_attribute_change = TRUE;
+ // Now in ASCII range - need to switch font
+ prt_in_ascii = true;
+ prt_need_font = true;
+ prt_attribute_change = true;
}
}
if (prt_out_mbyte) {
@@ -2911,16 +2927,16 @@ int mch_print_text_out(char_u *const textp, size_t len)
}
if (prt_half_width) {
if (!half_width) {
- prt_half_width = FALSE;
+ prt_half_width = false;
prt_pos_x += prt_char_width/4;
- prt_need_moveto = TRUE;
- prt_attribute_change = TRUE;
+ prt_need_moveto = true;
+ prt_attribute_change = true;
}
} else if (half_width) {
- prt_half_width = TRUE;
+ prt_half_width = true;
prt_pos_x += prt_char_width/4;
- prt_need_moveto = TRUE;
- prt_attribute_change = TRUE;
+ prt_need_moveto = true;
+ prt_attribute_change = true;
}
}
@@ -2929,24 +2945,25 @@ int mch_print_text_out(char_u *const textp, size_t len)
*/
if (prt_attribute_change) {
prt_flush_buffer();
- /* Reset count of number of chars that will be printed */
+ // Reset count of number of chars that will be printed
prt_text_run = 0;
if (prt_need_moveto) {
prt_pos_x_moveto = prt_pos_x;
prt_pos_y_moveto = prt_pos_y;
- prt_do_moveto = TRUE;
+ prt_do_moveto = true;
- prt_need_moveto = FALSE;
+ prt_need_moveto = false;
}
if (prt_need_font) {
- if (!prt_in_ascii)
+ if (!prt_in_ascii) {
prt_write_string("CF");
- else
+ } else {
prt_write_string("F");
+ }
prt_write_int(prt_font);
prt_write_string("sf\n");
- prt_need_font = FALSE;
+ prt_need_font = false;
}
if (prt_need_fgcol) {
unsigned int r, g, b;
@@ -2962,22 +2979,24 @@ int mch_print_text_out(char_u *const textp, size_t len)
prt_write_real(b / 255.0, 3);
prt_write_string("r\n");
}
- prt_need_fgcol = FALSE;
+ prt_need_fgcol = false;
}
if (prt_bgcol != PRCOLOR_WHITE) {
prt_new_bgcol = prt_bgcol;
- if (prt_need_bgcol)
- prt_do_bgcol = TRUE;
- } else
- prt_do_bgcol = FALSE;
- prt_need_bgcol = FALSE;
+ if (prt_need_bgcol) {
+ prt_do_bgcol = true;
+ }
+ } else {
+ prt_do_bgcol = false;
+ }
+ prt_need_bgcol = false;
if (prt_need_underline)
prt_do_underline = prt_underline;
- prt_need_underline = FALSE;
+ prt_need_underline = false;
- prt_attribute_change = FALSE;
+ prt_attribute_change = false;
}
if (prt_do_conv) {
@@ -3062,29 +3081,29 @@ void mch_print_set_font(const TriState iBold, const TriState iItalic,
if (font != prt_font) {
prt_font = font;
- prt_attribute_change = TRUE;
- prt_need_font = TRUE;
+ prt_attribute_change = true;
+ prt_need_font = true;
}
if (prt_underline != iUnderline) {
prt_underline = iUnderline;
- prt_attribute_change = TRUE;
- prt_need_underline = TRUE;
+ prt_attribute_change = true;
+ prt_need_underline = true;
}
}
void mch_print_set_bg(uint32_t bgcol)
{
prt_bgcol = bgcol;
- prt_attribute_change = TRUE;
- prt_need_bgcol = TRUE;
+ prt_attribute_change = true;
+ prt_need_bgcol = true;
}
void mch_print_set_fg(uint32_t fgcol)
{
if (fgcol != prt_fgcol) {
prt_fgcol = fgcol;
- prt_attribute_change = TRUE;
- prt_need_fgcol = TRUE;
+ prt_attribute_change = true;
+ prt_need_fgcol = true;
}
}
diff --git a/src/nvim/if_cscope.c b/src/nvim/if_cscope.c
index 0f9984ec38..2af09f10cb 100644
--- a/src/nvim/if_cscope.c
+++ b/src/nvim/if_cscope.c
@@ -71,10 +71,10 @@ static void cs_usage_msg(csid_e x)
static enum {
- EXP_CSCOPE_SUBCMD, /* expand ":cscope" sub-commands */
- EXP_SCSCOPE_SUBCMD, /* expand ":scscope" sub-commands */
- EXP_CSCOPE_FIND, /* expand ":cscope find" arguments */
- EXP_CSCOPE_KILL /* expand ":cscope kill" arguments */
+ EXP_CSCOPE_SUBCMD, // expand ":cscope" sub-commands
+ EXP_SCSCOPE_SUBCMD, // expand ":scscope" sub-commands
+ EXP_CSCOPE_FIND, // expand ":cscope find" arguments
+ EXP_CSCOPE_KILL // expand ":cscope kill" arguments
} expand_what;
/*
@@ -87,13 +87,13 @@ char_u *get_cscope_name(expand_T *xp, int idx)
switch (expand_what) {
case EXP_CSCOPE_SUBCMD:
- /* Complete with sub-commands of ":cscope":
- * add, find, help, kill, reset, show */
+ // Complete with sub-commands of ":cscope":
+ // add, find, help, kill, reset, show
return (char_u *)cs_cmds[idx].name;
case EXP_SCSCOPE_SUBCMD:
{
- /* Complete with sub-commands of ":scscope": same sub-commands as
- * ":cscope" but skip commands which don't support split windows */
+ // Complete with sub-commands of ":scscope": same sub-commands as
+ // ":cscope" but skip commands which don't support split windows
int i;
for (i = 0, current_idx = 0; cs_cmds[i].name != NULL; i++)
if (cs_cmds[i].cansplit)
@@ -118,10 +118,10 @@ char_u *get_cscope_name(expand_T *xp, int idx)
{
static char connection[5];
- /* ":cscope kill" accepts connection numbers or partial names of
- * the pathname of the cscope database as argument. Only complete
- * with connection numbers. -1 can also be used to kill all
- * connections. */
+ // ":cscope kill" accepts connection numbers or partial names of
+ // the pathname of the cscope database as argument. Only complete
+ // with connection numbers. -1 can also be used to kill all
+ // connections.
size_t i;
for (i = 0, current_idx = 0; i < csinfo_size; i++) {
if (csinfo[i].fname == NULL)
@@ -149,7 +149,7 @@ void set_context_in_cscope_cmd(expand_T *xp, const char *arg, cmdidx_T cmdidx)
expand_what = ((cmdidx == CMD_scscope)
? EXP_SCSCOPE_SUBCMD : EXP_CSCOPE_SUBCMD);
- /* (part of) subcommand already typed */
+ // (part of) subcommand already typed
if (*arg != NUL) {
const char *p = (const char *)skiptowhite((const char_u *)arg);
if (*p != NUL) { // Past first word.
@@ -175,7 +175,7 @@ void set_context_in_cscope_cmd(expand_T *xp, const char *arg, cmdidx_T cmdidx)
static void
do_cscope_general(
exarg_T *eap,
- int make_split /* whether to split window */
+ int make_split // whether to split window
)
{
cscmd_T *cmdp;
@@ -276,17 +276,19 @@ void ex_cstag(exarg_T *eap)
/// This simulates a vim_fgets(), but for cscope, returns the next line
/// from the cscope output. should only be called from find_tags()
///
-/// @return TRUE if eof, FALSE otherwise
-int cs_fgets(char_u *buf, int size)
+/// @return true if eof, FALSE otherwise
+bool cs_fgets(char_u *buf, int size)
+ FUNC_ATTR_NONNULL_ALL
{
char *p;
- if ((p = cs_manage_matches(NULL, NULL, 0, Get)) == NULL)
+ if ((p = cs_manage_matches(NULL, NULL, 0, Get)) == NULL) {
return true;
+ }
STRLCPY(buf, p, size);
- return FALSE;
-} /* cs_fgets */
+ return false;
+}
/// Called only from do_tag(), when popping the tag stack.
@@ -328,48 +330,53 @@ void cs_print_tags(void)
*
* Note: All string comparisons are case sensitive!
*/
-int cs_connection(int num, char_u *dbpath, char_u *ppath)
+bool cs_connection(int num, char_u *dbpath, char_u *ppath)
{
- if (num < 0 || num > 4 || (num > 0 && !dbpath))
+ if (num < 0 || num > 4 || (num > 0 && !dbpath)) {
return false;
+ }
for (size_t i = 0; i < csinfo_size; i++) {
- if (!csinfo[i].fname)
+ if (!csinfo[i].fname) {
continue;
-
- if (num == 0)
- return TRUE;
-
+ }
+ if (num == 0) {
+ return true;
+ }
switch (num) {
case 1:
- if (strstr(csinfo[i].fname, (char *)dbpath))
- return TRUE;
+ if (strstr(csinfo[i].fname, (char *)dbpath)) {
+ return true;
+ }
break;
case 2:
- if (strcmp(csinfo[i].fname, (char *)dbpath) == 0)
- return TRUE;
+ if (strcmp(csinfo[i].fname, (char *)dbpath) == 0) {
+ return true;
+ }
break;
case 3:
if (strstr(csinfo[i].fname, (char *)dbpath)
&& ((!ppath && !csinfo[i].ppath)
|| (ppath
&& csinfo[i].ppath
- && strstr(csinfo[i].ppath, (char *)ppath))))
- return TRUE;
+ && strstr(csinfo[i].ppath, (char *)ppath)))) {
+ return true;
+ }
break;
case 4:
if ((strcmp(csinfo[i].fname, (char *)dbpath) == 0)
&& ((!ppath && !csinfo[i].ppath)
|| (ppath
&& csinfo[i].ppath
- && (strcmp(csinfo[i].ppath, (char *)ppath) == 0))))
- return TRUE;
+ && (strcmp(csinfo[i].ppath, (char *)ppath) == 0)))) {
+ return true;
+ }
break;
}
}
- return FALSE;
-} /* cs_connection */
+ return false;
+} // cs_connection
/*
@@ -419,7 +426,7 @@ cs_add_common(
size_t usedlen = 0;
char_u *fbuf = NULL;
- /* get the filename (arg1), expand it, and try to stat it */
+ // get the filename (arg1), expand it, and try to stat it
fname = xmalloc(MAXPATHL + 1);
expand_env((char_u *)arg1, (char_u *)fname, MAXPATHL);
@@ -451,7 +458,7 @@ staterr:
}
int i;
- /* if filename is a directory, append the cscope database name to it */
+ // if filename is a directory, append the cscope database name to it
if ((file_info.stat.st_mode & S_IFMT) == S_IFDIR) {
fname2 = (char *)xmalloc(strlen(CSCOPE_DBFILE) + strlen(fname) + 2);
@@ -512,18 +519,18 @@ add_err:
xfree(fname);
xfree(ppath);
return CSCOPE_FAILURE;
-} /* cs_add_common */
+}
static int cs_check_for_connections(void)
{
return cs_cnt_connections() > 0;
-} /* cs_check_for_connections */
+}
static int cs_check_for_tags(void)
{
return p_tags[0] != NUL && curbuf->b_p_tags != NULL;
-} /* cs_check_for_tags */
+}
/// Count the number of cscope connections.
static size_t cs_cnt_connections(void)
@@ -535,10 +542,10 @@ static size_t cs_cnt_connections(void)
cnt++;
}
return cnt;
-} /* cs_cnt_connections */
+}
static void cs_reading_emsg(
- size_t idx /* connection index */
+ size_t idx // connection index
)
{
EMSGU(_("E262: error reading cscope connection %" PRIu64), idx);
@@ -602,7 +609,7 @@ static int cs_cnt_matches(size_t idx)
xfree(buf);
return nlines;
-} /* cs_cnt_matches */
+}
/// Creates the actual cscope command query from what the user entered.
@@ -646,8 +653,8 @@ static char *cs_create_cmd(char *csoption, char *pattern)
return NULL;
}
- /* Skip white space before the patter, except for text and pattern search,
- * they may want to use the leading white space. */
+ // Skip white space before the patter, except for text and pattern search,
+ // they may want to use the leading white space.
pat = pattern;
if (search != 4 && search != 6)
while (ascii_iswhite(*pat))
@@ -658,7 +665,7 @@ static char *cs_create_cmd(char *csoption, char *pattern)
(void)sprintf(cmd, "%d%s", search, pat);
return cmd;
-} /* cs_create_cmd */
+}
/// This piece of code was taken/adapted from nvi. do we need to add
@@ -694,15 +701,18 @@ err_closing:
case -1:
(void)EMSG(_("E622: Could not fork for cscope"));
goto err_closing;
- case 0: /* child: run cscope. */
- if (dup2(to_cs[0], STDIN_FILENO) == -1)
+ case 0: // child: run cscope.
+ if (dup2(to_cs[0], STDIN_FILENO) == -1) {
PERROR("cs_create_connection 1");
- if (dup2(from_cs[1], STDOUT_FILENO) == -1)
+ }
+ if (dup2(from_cs[1], STDOUT_FILENO) == -1) {
PERROR("cs_create_connection 2");
- if (dup2(from_cs[1], STDERR_FILENO) == -1)
+ }
+ if (dup2(from_cs[1], STDERR_FILENO) == -1) {
PERROR("cs_create_connection 3");
+ }
- /* close unused */
+ // close unused
(void)close(to_cs[1]);
(void)close(from_cs[0]);
#else
@@ -735,14 +745,14 @@ err_closing:
return CSCOPE_FAILURE;
}
#endif
- /* expand the cscope exec for env var's */
+ // expand the cscope exec for env var's
prog = xmalloc(MAXPATHL + 1);
expand_env(p_csprg, (char_u *)prog, MAXPATHL);
- /* alloc space to hold the cscope command */
+ // alloc space to hold the cscope command
size_t len = strlen(prog) + strlen(csinfo[i].fname) + 32;
if (csinfo[i].ppath) {
- /* expand the prepend path for env var's */
+ // expand the prepend path for env var's
ppath = xmalloc(MAXPATHL + 1);
expand_env((char_u *)csinfo[i].ppath, (char_u *)ppath, MAXPATHL);
@@ -754,12 +764,12 @@ err_closing:
cmd = xmalloc(len);
- /* run the cscope command; is there execl for non-unix systems? */
+ // run the cscope command; is there execl for non-unix systems?
#if defined(UNIX)
- (void)sprintf(cmd, "exec %s -dl -f %s", prog, csinfo[i].fname);
+ (void)snprintf(cmd, len, "exec %s -dl -f %s", prog, csinfo[i].fname);
#else
- /* WIN32 */
- (void)sprintf(cmd, "%s -dl -f %s", prog, csinfo[i].fname);
+ // WIN32
+ (void)snprintf(cmd, len, "%s -dl -f %s", prog, csinfo[i].fname);
#endif
if (csinfo[i].ppath != NULL) {
(void)strcat(cmd, " -P");
@@ -770,14 +780,14 @@ err_closing:
(void)strcat(cmd, csinfo[i].flags);
}
# ifdef UNIX
- /* on Win32 we still need prog */
+ // on Win32 we still need prog
xfree(prog);
# endif
xfree(ppath);
#if defined(UNIX)
# if defined(HAVE_SETSID) || defined(HAVE_SETPGID)
- /* Change our process group to avoid cscope receiving SIGWINCH. */
+ // Change our process group to avoid cscope receiving SIGWINCH.
# if defined(HAVE_SETSID)
(void)setsid();
# else
@@ -789,18 +799,18 @@ err_closing:
PERROR(_("cs_create_connection exec failed"));
exit(127);
- /* NOTREACHED */
- default: /* parent. */
- /*
- * Save the file descriptors for later duplication, and
- * reopen as streams.
- */
- if ((csinfo[i].to_fp = fdopen(to_cs[1], "w")) == NULL)
+ // NOTREACHED
+ default: // parent.
+ // Save the file descriptors for later duplication, and
+ // reopen as streams.
+ if ((csinfo[i].to_fp = fdopen(to_cs[1], "w")) == NULL) {
PERROR(_("cs_create_connection: fdopen for to_fp failed"));
- if ((csinfo[i].fr_fp = fdopen(from_cs[0], "r")) == NULL)
+ }
+ if ((csinfo[i].fr_fp = fdopen(from_cs[0], "r")) == NULL) {
PERROR(_("cs_create_connection: fdopen for fr_fp failed"));
+ }
- /* close unused */
+ // close unused
(void)close(to_cs[0]);
(void)close(from_cs[1]);
@@ -808,11 +818,11 @@ err_closing:
}
#else
- /* WIN32 */
- /* Create a new process to run cscope and use pipes to talk with it */
+ // WIN32
+ // Create a new process to run cscope and use pipes to talk with it
GetStartupInfo(&si);
si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
- si.wShowWindow = SW_HIDE; /* Hide child application window */
+ si.wShowWindow = SW_HIDE; // Hide child application window
si.hStdOutput = stdout_wr;
si.hStdError = stdout_wr;
si.hStdInput = stdin_rd;
@@ -826,7 +836,7 @@ err_closing:
(void)EMSG(_("E623: Could not spawn cscope process"));
goto err_closing;
}
- /* else */
+ // else
csinfo[i].pid = pi.dwProcessId;
csinfo[i].hProc = pi.hProcess;
CloseHandle(pi.hThread);
@@ -844,10 +854,10 @@ err_closing:
CloseHandle(stdin_rd);
CloseHandle(stdout_wr);
-#endif /* !UNIX */
+#endif // !UNIX
return CSCOPE_SUCCESS;
-} /* cs_create_connection */
+}
/// Query cscope using command line interface. Parse the output and use tselect
@@ -882,9 +892,9 @@ static int cs_find(exarg_T *eap)
if (NUL == eap->arg[i])
eap->arg[i] = ' ';
- return cs_find_common(opt, pat, eap->forceit, TRUE,
- eap->cmdidx == CMD_lcscope, *eap->cmdlinep);
-} /* cs_find */
+ return cs_find_common(opt, pat, eap->forceit, true,
+ eap->cmdidx == CMD_lcscope, *eap->cmdlinep);
+}
/// Common code for cscope find, shared by cs_find() and ex_cstag().
@@ -897,7 +907,7 @@ static int cs_find_common(char *opt, char *pat, int forceit, int verbose,
char cmdletter;
char *qfpos;
- /* get cmd letter */
+ // get cmd letter
switch (opt[0]) {
case '0':
cmdletter = 's';
@@ -933,10 +943,10 @@ static int cs_find_common(char *opt, char *pat, int forceit, int verbose,
qfpos = (char *)vim_strchr(p_csqf, cmdletter);
if (qfpos != NULL) {
qfpos++;
- /* next symbol must be + or - */
+ // next symbol must be + or -
if (strchr(CSQF_FLAGS, *qfpos) == NULL) {
char *nf = _("E469: invalid cscopequickfix flag %c for %c");
- /* strlen will be enough because we use chars */
+ // strlen will be enough because we use chars
char *buf = xmalloc(strlen(nf));
sprintf(buf, nf, *qfpos, *(qfpos-1));
@@ -954,23 +964,24 @@ static int cs_find_common(char *opt, char *pat, int forceit, int verbose,
}
}
- /* create the actual command to send to cscope */
+ // create the actual command to send to cscope
cmd = cs_create_cmd(opt, pat);
if (cmd == NULL)
return FALSE;
nummatches = xmalloc(sizeof(int) * csinfo_size);
- /* Send query to all open connections, then count the total number
- * of matches so we can alloc all in one swell foop. */
- for (size_t i = 0; i < csinfo_size; i++)
+ // Send query to all open connections, then count the total number
+ // of matches so we can alloc all in one swell foop.
+ for (size_t i = 0; i < csinfo_size; i++) {
nummatches[i] = 0;
+ }
totmatches = 0;
for (size_t i = 0; i < csinfo_size; i++) {
if (csinfo[i].fname == NULL || csinfo[i].to_fp == NULL)
continue;
- /* send cmd to cscope */
+ // send cmd to cscope
(void)fprintf(csinfo[i].to_fp, "%s\n", cmd);
(void)fflush(csinfo[i].to_fp);
@@ -1014,8 +1025,9 @@ static int cs_find_common(char *opt, char *pat, int forceit, int verbose,
} else {
cs_file_results(f, nummatches);
fclose(f);
- if (use_ll) /* Use location list */
+ if (use_ll) { // Use location list
wp = curwin;
+ }
// '-' starts a new error list
if (qf_init(wp, tmp, (char_u *)"%f%*\\t%l%*\\t%m",
*qfpos == '-', cmdline, NULL) > 0) {
@@ -1046,7 +1058,7 @@ static int cs_find_common(char *opt, char *pat, int forceit, int verbose,
char **matches = NULL, **contexts = NULL;
size_t matched = 0;
- /* read output */
+ // read output
cs_fill_results((char *)pat, totmatches, nummatches, &matches,
&contexts, &matched);
xfree(nummatches);
@@ -1057,8 +1069,7 @@ static int cs_find_common(char *opt, char *pat, int forceit, int verbose,
return do_tag((char_u *)pat, DT_CSCOPE, 0, forceit, verbose);
}
-
-} /* cs_find_common */
+}
/// Print help.
static int cs_help(exarg_T *eap)
@@ -1070,9 +1081,10 @@ static int cs_help(exarg_T *eap)
char *help = _(cmdp->help);
int space_cnt = 30 - vim_strsize((char_u *)help);
- /* Use %*s rather than %30s to ensure proper alignment in utf-8 */
- if (space_cnt < 0)
+ // Use %*s rather than %30s to ensure proper alignment in utf-8
+ if (space_cnt < 0) {
space_cnt = 0;
+ }
(void)smsg(_("%-5s: %s%*s (Usage: %s)"),
cmdp->name,
help, space_cnt, " ",
@@ -1094,7 +1106,7 @@ static int cs_help(exarg_T *eap)
wait_return(TRUE);
return CSCOPE_SUCCESS;
-} /* cs_help */
+}
static void clear_csinfo(size_t i)
@@ -1124,7 +1136,7 @@ static int cs_insert_filelist(char *fname, char *ppath, char *flags,
}
if (csinfo[j].fname == NULL && !empty_found) {
- i = j; /* remember first empty entry */
+ i = j; // remember first empty entry
empty_found = true;
}
}
@@ -1132,13 +1144,13 @@ static int cs_insert_filelist(char *fname, char *ppath, char *flags,
if (!empty_found) {
i = csinfo_size;
if (csinfo_size == 0) {
- /* First time allocation: allocate only 1 connection. It should
- * be enough for most users. If more is needed, csinfo will be
- * reallocated. */
+ // First time allocation: allocate only 1 connection. It should
+ // be enough for most users. If more is needed, csinfo will be
+ // reallocated.
csinfo_size = 1;
csinfo = xcalloc(1, sizeof(csinfo_T));
} else {
- /* Reallocate space for more connections. */
+ // Reallocate space for more connections.
csinfo_size *= 2;
csinfo = xrealloc(csinfo, sizeof(csinfo_T)*csinfo_size);
}
@@ -1165,7 +1177,7 @@ static int cs_insert_filelist(char *fname, char *ppath, char *flags,
os_fileinfo_id(file_info, &(csinfo[i].file_id));
assert(i <= INT_MAX);
return (int)i;
-} /* cs_insert_filelist */
+}
/// Find cscope command in command table.
@@ -1178,7 +1190,7 @@ static cscmd_T * cs_lookup_cmd(exarg_T *eap)
if (eap->arg == NULL)
return NULL;
- /* Store length of eap->arg before it gets modified by strtok(). */
+ // Store length of eap->arg before it gets modified by strtok().
eap_arg_len = (int)STRLEN(eap->arg);
if ((stok = strtok((char *)(eap->arg), (const char *)" ")) == NULL)
@@ -1190,7 +1202,7 @@ static cscmd_T * cs_lookup_cmd(exarg_T *eap)
return cmdp;
}
return NULL;
-} /* cs_lookup_cmd */
+}
/// Nuke em.
@@ -1247,13 +1259,13 @@ static int cs_kill(exarg_T *eap)
}
return CSCOPE_SUCCESS;
-} /* cs_kill */
+}
/// Actually kills a specific cscope connection.
static void cs_kill_execute(
- size_t i, /* cscope table index */
- char *cname /* cscope database name */
+ size_t i, // cscope table index
+ char *cname // cscope database name
)
{
if (p_csverbose) {
@@ -1284,17 +1296,16 @@ static void cs_kill_execute(
static char *cs_make_vim_style_matches(char *fname, char *slno, char *search,
char *tagstr)
{
- /* vim style is ctags:
- *
- * <tagstr>\t<filename>\t<linenum_or_search>"\t<extra>
- *
- * but as mentioned above, we'll always use the line number and
- * put the search pattern (if one exists) as "extra"
- *
- * buf is used as part of vim's method of handling tags, and
- * (i think) vim frees it when you pop your tags and get replaced
- * by new ones on the tag stack.
- */
+ // vim style is ctags:
+ //
+ // <tagstr>\t<filename>\t<linenum_or_search>"\t<extra>
+ //
+ // but as mentioned above, we'll always use the line number and
+ // put the search pattern (if one exists) as "extra"
+ //
+ // buf is used as part of vim's method of handling tags, and
+ // (i think) vim frees it when you pop your tags and get replaced
+ // by new ones on the tag stack.
char *buf;
size_t amt;
@@ -1311,7 +1322,7 @@ static char *cs_make_vim_style_matches(char *fname, char *slno, char *search,
}
return buf;
-} /* cs_make_vim_style_matches */
+}
/// This is kind of hokey, but i don't see an easy way round this.
@@ -1381,7 +1392,7 @@ static char *cs_manage_matches(char **matches, char **contexts,
}
return p;
-} /* cs_manage_matches */
+}
/// Parse cscope output.
@@ -1408,7 +1419,7 @@ retry:
return NULL;
}
- /* If the line's too long for the buffer, discard it. */
+ // If the line's too long for the buffer, discard it.
if ((p = strchr(buf, '\n')) == NULL) {
while ((ch = getc(csinfo[cnumber].fr_fp)) != EOF && ch != '\n')
;
@@ -1427,15 +1438,15 @@ retry:
return NULL;
if ((*linenumber = strtok(NULL, (const char *)" ")) == NULL)
return NULL;
- *search = *linenumber + strlen(*linenumber) + 1; /* +1 to skip \0 */
+ *search = *linenumber + strlen(*linenumber) + 1; // +1 to skip \0
- /* --- nvi ---
- * If the file is older than the cscope database, that is,
- * the database was built since the file was last modified,
- * or there wasn't a search string, use the line number.
- */
- if (strcmp(*search, "<unknown>") == 0)
+ // --- nvi ---
+ // If the file is older than the cscope database, that is,
+ // the database was built since the file was last modified,
+ // or there wasn't a search string, use the line number.
+ if (strcmp(*search, "<unknown>") == 0) {
*search = NULL;
+ }
name = cs_resolve_file(cnumber, name);
return name;
@@ -1474,11 +1485,10 @@ static void cs_file_results(FILE *f, int *nummatches_a)
xfree(context);
xfree(fullname);
- } /* for all matches */
+ } // for all matches
(void)cs_read_prompt(i);
-
- } /* for all cscope connections */
+ } // for all cscope connections
xfree(buf);
}
@@ -1539,10 +1549,10 @@ static void cs_fill_results(char *tagstr, size_t totmatches, int *nummatches_a,
*cntxts_p = cntxts;
xfree(buf);
-} // cs_fill_results
+}
-/* get the requested path components */
+// get the requested path components
static char *cs_pathcomponents(char *path)
{
if (p_cspc == 0) {
@@ -1688,7 +1698,7 @@ static int cs_read_prompt(size_t i)
static char *eprompt = "Press the RETURN key to continue:";
size_t epromptlen = strlen(eprompt);
- /* compute maximum allowed len for Cscope error message */
+ // compute maximum allowed len for Cscope error message
assert(IOSIZE >= cs_emsg_len);
size_t maxlen = IOSIZE - cs_emsg_len;
@@ -1738,11 +1748,12 @@ static int cs_read_prompt(size_t i)
}
if (ch == EOF) {
PERROR("cs_read_prompt EOF");
- if (buf != NULL && buf[0] != NUL)
+ if (buf != NULL && buf[0] != NUL) {
(void)EMSG2(cs_emsg, buf);
- else if (p_csverbose)
- cs_reading_emsg(i); /* don't have additional information */
- cs_release_csp(i, TRUE);
+ } else if (p_csverbose) {
+ cs_reading_emsg(i); // don't have additional information
+ }
+ cs_release_csp(i, true);
xfree(buf);
return CSCOPE_FAILURE;
}
@@ -1753,9 +1764,10 @@ static int cs_read_prompt(size_t i)
}
}
- if (ch == EOF)
- continue; /* didn't find the prompt */
- break; /* did find the prompt */
+ if (ch == EOF) {
+ continue; // didn't find the prompt
+ }
+ break; // did find the prompt
}
xfree(buf);
@@ -1766,8 +1778,9 @@ static int cs_read_prompt(size_t i)
/*
* Used to catch and ignore SIGALRM below.
*/
-static void sig_handler(int s) {
- /* do nothing */
+static void sig_handler(int s)
+{
+ // do nothing
return;
}
@@ -1775,7 +1788,7 @@ static void sig_handler(int s) {
/// Does the actual free'ing for the cs ptr with an optional flag of whether
/// or not to free the filename. Called by cs_kill and cs_reset.
-static void cs_release_csp(size_t i, int freefnpp)
+static void cs_release_csp(size_t i, bool freefnpp)
{
// Trying to exit normally (not sure whether it is fit to Unix cscope)
if (csinfo[i].to_fp != NULL) {
@@ -1791,7 +1804,7 @@ static void cs_release_csp(size_t i, int freefnpp)
# if defined(HAVE_SIGACTION)
struct sigaction sa, old;
- /* Use sigaction() to limit the waiting time to two seconds. */
+ // Use sigaction() to limit the waiting time to two seconds.
sigemptyset(&sa.sa_mask);
sa.sa_handler = sig_handler;
# ifdef SA_NODEFER
@@ -1800,27 +1813,28 @@ static void cs_release_csp(size_t i, int freefnpp)
sa.sa_flags = 0;
# endif
sigaction(SIGALRM, &sa, &old);
- alarm(2); /* 2 sec timeout */
+ alarm(2); // 2 sec timeout
- /* Block until cscope exits or until timer expires */
+ // Block until cscope exits or until timer expires
pid = waitpid(csinfo[i].pid, &pstat, 0);
waitpid_errno = errno;
- /* cancel pending alarm if still there and restore signal */
+ // cancel pending alarm if still there and restore signal
alarm(0);
sigaction(SIGALRM, &old, NULL);
# else
int waited;
- /* Can't use sigaction(), loop for two seconds. First yield the CPU
- * to give cscope a chance to exit quickly. */
+ // Can't use sigaction(), loop for two seconds. First yield the CPU
+ // to give cscope a chance to exit quickly.
sleep(0);
for (waited = 0; waited < 40; ++waited) {
pid = waitpid(csinfo[i].pid, &pstat, WNOHANG);
waitpid_errno = errno;
- if (pid != 0)
- break; /* break unless the process is still running */
- os_delay(50L, false); /* sleep 50 ms */
+ if (pid != 0) {
+ break; // break unless the process is still running
+ }
+ os_delay(50L, false); // sleep 50 ms
}
# endif
/*
@@ -1830,7 +1844,7 @@ static void cs_release_csp(size_t i, int freefnpp)
*/
if (pid < 0 && csinfo[i].pid > 1) {
# ifdef ECHILD
- int alive = TRUE;
+ bool alive = true;
if (waitpid_errno == ECHILD) {
/*
@@ -1845,13 +1859,13 @@ static void cs_release_csp(size_t i, int freefnpp)
int waited;
sleep(0);
- for (waited = 0; waited < 40; ++waited) {
- /* Check whether cscope process is still alive */
+ for (waited = 0; waited < 40; waited++) {
+ // Check whether cscope process is still alive
if (kill(csinfo[i].pid, 0) != 0) {
- alive = FALSE; /* cscope process no longer exists */
+ alive = false; // cscope process no longer exists
break;
}
- os_delay(50L, false); /* sleep 50ms */
+ os_delay(50L, false); // sleep 50ms
}
}
if (alive)
@@ -1862,11 +1876,12 @@ static void cs_release_csp(size_t i, int freefnpp)
}
}
}
-#else /* !UNIX */
+#else // !UNIX
if (csinfo[i].hProc != NULL) {
- /* Give cscope a chance to exit normally */
- if (WaitForSingleObject(csinfo[i].hProc, 1000) == WAIT_TIMEOUT)
+ // Give cscope a chance to exit normally
+ if (WaitForSingleObject(csinfo[i].hProc, 1000) == WAIT_TIMEOUT) {
TerminateProcess(csinfo[i].hProc, 0);
+ }
CloseHandle(csinfo[i].hProc);
}
#endif
@@ -1883,7 +1898,7 @@ static void cs_release_csp(size_t i, int freefnpp)
}
clear_csinfo(i);
-} /* cs_release_csp */
+}
/// Calls cs_kill on all cscope connections then reinits.
@@ -1895,7 +1910,7 @@ static int cs_reset(exarg_T *eap)
if (csinfo_size == 0)
return CSCOPE_SUCCESS;
- /* malloc our db and ppath list */
+ // malloc our db and ppath list
dblist = xmalloc(csinfo_size * sizeof(char *));
pplist = xmalloc(csinfo_size * sizeof(char *));
fllist = xmalloc(csinfo_size * sizeof(char *));
@@ -1908,15 +1923,14 @@ static int cs_reset(exarg_T *eap)
cs_release_csp(i, FALSE);
}
- /* rebuild the cscope connection list */
+ // rebuild the cscope connection list
for (size_t i = 0; i < csinfo_size; i++) {
if (dblist[i] != NULL) {
cs_add_common(dblist[i], pplist[i], fllist[i]);
if (p_csverbose) {
- /* don't use smsg_attr() because we want to display the
- * connection number in the same line as
- * "Added cscope database..."
- */
+ // don't use smsg_attr() because we want to display the
+ // connection number in the same line as
+ // "Added cscope database..."
snprintf(buf, ARRAY_SIZE(buf), " (#%zu)", i);
MSG_PUTS_ATTR(buf, HL_ATTR(HLF_R));
}
@@ -1933,7 +1947,7 @@ static int cs_reset(exarg_T *eap)
msg_attr(_("All cscope databases reset"), HL_ATTR(HLF_R) | MSG_HIST);
}
return CSCOPE_SUCCESS;
-} /* cs_reset */
+}
/// Construct the full pathname to a file found in the cscope database.
@@ -1954,11 +1968,11 @@ static char *cs_resolve_file(size_t i, char *name)
* copied into the tag buffer used by Vim.
*/
size_t len = strlen(name) + 2;
- if (csinfo[i].ppath != NULL)
+ if (csinfo[i].ppath != NULL) {
len += strlen(csinfo[i].ppath);
- else if (p_csre && csinfo[i].fname != NULL) {
- /* If 'cscoperelative' is set and ppath is not set, use cscope.out
- * path in path resolution. */
+ } else if (p_csre && csinfo[i].fname != NULL) {
+ // If 'cscoperelative' is set and ppath is not set, use cscope.out
+ // path in path resolution.
csdir = xmalloc(MAXPATHL);
STRLCPY(csdir, csinfo[i].fname,
path_tail((char_u *)csinfo[i].fname)
@@ -1966,9 +1980,9 @@ static char *cs_resolve_file(size_t i, char *name)
len += STRLEN(csdir);
}
- /* Note/example: this won't work if the cscope output already starts
- * "../.." and the prefix path is also "../..". if something like this
- * happens, you are screwed up and need to fix how you're using cscope. */
+ // Note/example: this won't work if the cscope output already starts
+ // "../.." and the prefix path is also "../..". if something like this
+ // happens, you are screwed up and need to fix how you're using cscope.
if (csinfo[i].ppath != NULL
&& (strncmp(name, csinfo[i].ppath, strlen(csinfo[i].ppath)) != 0)
&& (name[0] != '/')
@@ -1976,9 +1990,9 @@ static char *cs_resolve_file(size_t i, char *name)
fullname = xmalloc(len);
(void)sprintf(fullname, "%s/%s", csinfo[i].ppath, name);
} else if (csdir != NULL && csinfo[i].fname != NULL && *csdir != NUL) {
- /* Check for csdir to be non empty to avoid empty path concatenated to
- * cscope output. */
- fullname = concat_fnames((char *)csdir, name, TRUE);
+ // Check for csdir to be non empty to avoid empty path concatenated to
+ // cscope output.
+ fullname = concat_fnames((char *)csdir, name, true);
} else {
fullname = xstrdup(name);
}
@@ -2013,7 +2027,7 @@ static int cs_show(exarg_T *eap)
wait_return(TRUE);
return CSCOPE_SUCCESS;
-} /* cs_show */
+}
/// Only called when VIM exits to quit any cscope sessions.
@@ -2025,4 +2039,4 @@ void cs_end(void)
csinfo_size = 0;
}
-/* the end */
+// the end
diff --git a/src/nvim/if_cscope_defs.h b/src/nvim/if_cscope_defs.h
index fa18866840..d2d8b0fb62 100644
--- a/src/nvim/if_cscope_defs.h
+++ b/src/nvim/if_cscope_defs.h
@@ -1,19 +1,16 @@
#ifndef NVIM_IF_CSCOPE_DEFS_H
#define NVIM_IF_CSCOPE_DEFS_H
-/*
- * CSCOPE support for Vim added by Andy Kahn <kahn@zk3.dec.com>
- * Ported to Win32 by Sergey Khorev <sergey.khorev@gmail.com>
- *
- * The basic idea/structure of cscope for Vim was borrowed from Nvi.
- * There might be a few lines of code that look similar to what Nvi
- * has. If this is a problem and requires inclusion of the annoying
- * BSD license, then sue me; I'm not worth much anyway.
- */
-
+// CSCOPE support for Vim added by Andy Kahn <kahn@zk3.dec.com>
+// Ported to Win32 by Sergey Khorev <sergey.khorev@gmail.com>
+//
+// The basic idea/structure of cscope for Vim was borrowed from Nvi.
+// There might be a few lines of code that look similar to what Nvi
+// has. If this is a problem and requires inclusion of the annoying
+// BSD license, then sue me; I'm not worth much anyway.
#if defined(UNIX)
-# include <sys/types.h> /* pid_t */
+# include <sys/types.h> // pid_t
#endif
#include "nvim/os/os_defs.h"
@@ -33,13 +30,13 @@ typedef struct {
int (*func)(exarg_T *eap);
char * help;
char * usage;
- int cansplit; /* if supports splitting window */
+ int cansplit; // if supports splitting window
} cscmd_T;
typedef struct csi {
- char * fname; /* cscope db name */
- char * ppath; /* path to prepend (the -P option) */
- char * flags; /* additional cscope flags/options (e.g, -p2) */
+ char * fname; // cscope db name
+ char * ppath; // path to prepend (the -P option)
+ char * flags; // additional cscope flags/options (e.g, -p2)
#if defined(UNIX)
pid_t pid; // PID of the connected cscope process
#else
@@ -51,8 +48,8 @@ typedef struct csi {
#endif
FileID file_id;
- FILE * fr_fp; /* from cscope: FILE. */
- FILE * to_fp; /* to cscope: FILE. */
+ FILE * fr_fp; // from cscope: FILE.
+ FILE * to_fp; // to cscope: FILE.
} csinfo_T;
typedef enum { Add, Find, Help, Kill, Reset, Show } csid_e;
diff --git a/src/nvim/indent.c b/src/nvim/indent.c
index f8018c039d..fb277b25fd 100644
--- a/src/nvim/indent.c
+++ b/src/nvim/indent.c
@@ -56,7 +56,8 @@ int get_indent_buf(buf_T *buf, linenr_T lnum)
// Count the size (in window cells) of the indent in line "ptr", with
// 'tabstop' at "ts".
// If @param list is TRUE, count only screen size for tabs.
-int get_indent_str(char_u *ptr, int ts, int list)
+int get_indent_str(const char_u *ptr, int ts, int list)
+ FUNC_ATTR_NONNULL_ALL
{
int count = 0;
@@ -375,12 +376,12 @@ int get_number_indent(linenr_T lnum)
* parameters into account. Window must be specified, since it is not
* necessarily always the current one.
*/
-int get_breakindent_win(win_T *wp, char_u *line)
- FUNC_ATTR_NONNULL_ARG(1)
+int get_breakindent_win(win_T *wp, const char_u *line)
+ FUNC_ATTR_NONNULL_ALL
{
static int prev_indent = 0; // Cached indent value.
static long prev_ts = 0; // Cached tabstop value.
- static char_u *prev_line = NULL; // cached pointer to line.
+ static const char_u *prev_line = NULL; // cached pointer to line.
static varnumber_T prev_tick = 0; // Changedtick of cached value.
int bri = 0;
// window width minus window margin space, i.e. what rests for text
@@ -389,7 +390,7 @@ int get_breakindent_win(win_T *wp, char_u *line)
&& (vim_strchr(p_cpo, CPO_NUMCOL) == NULL)
? number_width(wp) + 1 : 0);
- /* used cached indent, unless pointer or 'tabstop' changed */
+ // used cached indent, unless pointer or 'tabstop' changed
if (prev_line != line || prev_ts != wp->w_buffer->b_p_ts
|| prev_tick != buf_get_changedtick(wp->w_buffer)) {
prev_line = line;
@@ -397,23 +398,24 @@ int get_breakindent_win(win_T *wp, char_u *line)
prev_tick = buf_get_changedtick(wp->w_buffer);
prev_indent = get_indent_str(line, (int)wp->w_buffer->b_p_ts, wp->w_p_list);
}
- bri = prev_indent + wp->w_p_brishift;
+ bri = prev_indent + wp->w_briopt_shift;
- /* indent minus the length of the showbreak string */
- if (wp->w_p_brisbr)
+ // indent minus the length of the showbreak string
+ if (wp->w_briopt_sbr) {
bri -= vim_strsize(p_sbr);
-
- /* Add offset for number column, if 'n' is in 'cpoptions' */
+ }
+ // Add offset for number column, if 'n' is in 'cpoptions'
bri += win_col_off2(wp);
- /* never indent past left window margin */
- if (bri < 0)
+ // never indent past left window margin
+ if (bri < 0) {
bri = 0;
- /* always leave at least bri_min characters on the left,
- * if text width is sufficient */
- else if (bri > eff_wwidth - wp->w_p_brimin)
- bri = (eff_wwidth - wp->w_p_brimin < 0)
- ? 0 : eff_wwidth - wp->w_p_brimin;
+ } else if (bri > eff_wwidth - wp->w_briopt_min) {
+ // always leave at least bri_min characters on the left,
+ // if text width is sufficient
+ bri = (eff_wwidth - wp->w_briopt_min < 0)
+ ? 0 : eff_wwidth - wp->w_briopt_min;
+ }
return bri;
}
diff --git a/src/nvim/indent_c.c b/src/nvim/indent_c.c
index 67a7e58ed7..bb443161ef 100644
--- a/src/nvim/indent_c.c
+++ b/src/nvim/indent_c.c
@@ -3372,11 +3372,9 @@ term_again:
continue;
}
- /*
- * Are we at the start of a cpp base class declaration or
- * constructor initialization?
- */ /* XXX */
- n = false;
+ // Are we at the start of a cpp base class declaration or
+ // constructor initialization? XXX
+ n = 0;
if (curbuf->b_ind_cpp_baseclass != 0 && theline[0] != '{') {
n = cin_is_cpp_baseclass(&cache_cpp_baseclass);
l = get_cursor_line_ptr();
@@ -3409,7 +3407,6 @@ term_again:
* } foo,
* bar;
*/
- n = 0;
if (cin_ends_in(l, (char_u *)",", NULL)
|| (*l != NUL && (n = l[STRLEN(l) - 1]) == '\\')) {
/* take us back to opening paren */
diff --git a/src/nvim/keymap.h b/src/nvim/keymap.h
index cc02a6fb4f..ada9bc5780 100644
--- a/src/nvim/keymap.h
+++ b/src/nvim/keymap.h
@@ -13,7 +13,7 @@
* For MSDOS some keys produce codes larger than 0xff. They are split into two
* chars, the first one is K_NUL.
*/
-#define K_NUL (0xce) /* for MSDOS: special key follows */
+#define K_NUL (0xce) // for MSDOS: special key follows
/*
* K_SPECIAL is the first byte of a special key code and is always followed by
@@ -78,13 +78,13 @@
#define KS_SELECT 245
#define K_SELECT_STRING (char_u *)"\200\365X"
-/* Used a termcap entry that produces a normal character. */
+// Used a termcap entry that produces a normal character.
#define KS_KEY 242
-/* Used for click in a tab pages label. */
+// Used for click in a tab pages label.
#define KS_TABLINE 240
-/* Used for menu in a tab pages line. */
+// Used for menu in a tab pages line.
#define KS_TABMENU 239
/*
@@ -240,8 +240,8 @@ enum key_extra {
, KE_DROP = 95 // DnD data is available
// , KE_CURSORHOLD = 96 // CursorHold event
, KE_NOP = 97 // no-op: does nothing
- , KE_FOCUSGAINED = 98 // focus gained
- , KE_FOCUSLOST = 99 // focus lost
+ // , KE_FOCUSGAINED = 98 // focus gained
+ // , KE_FOCUSLOST = 99 // focus lost
// , KE_MOUSEMOVE = 100 // mouse moved with no button down
// , KE_CANCEL = 101 // return from vgetc
, KE_EVENT = 102 // event
@@ -274,13 +274,13 @@ enum key_extra {
#define K_TAB TERMCAP2KEY(KS_EXTRA, KE_TAB)
#define K_S_TAB TERMCAP2KEY('k', 'B')
-/* extra set of function keys F1-F4, for vt100 compatible xterm */
+// extra set of function keys F1-F4, for vt100 compatible xterm
#define K_XF1 TERMCAP2KEY(KS_EXTRA, KE_XF1)
#define K_XF2 TERMCAP2KEY(KS_EXTRA, KE_XF2)
#define K_XF3 TERMCAP2KEY(KS_EXTRA, KE_XF3)
#define K_XF4 TERMCAP2KEY(KS_EXTRA, KE_XF4)
-/* extra set of cursor keys for vt100 compatible xterm */
+// extra set of cursor keys for vt100 compatible xterm
#define K_XUP TERMCAP2KEY(KS_EXTRA, KE_XUP)
#define K_XDOWN TERMCAP2KEY(KS_EXTRA, KE_XDOWN)
#define K_XLEFT TERMCAP2KEY(KS_EXTRA, KE_XLEFT)
@@ -327,7 +327,7 @@ enum key_extra {
#define K_F36 TERMCAP2KEY('F', 'Q')
#define K_F37 TERMCAP2KEY('F', 'R')
-/* extra set of shifted function keys F1-F4, for vt100 compatible xterm */
+// extra set of shifted function keys F1-F4, for vt100 compatible xterm
#define K_S_XF1 TERMCAP2KEY(KS_EXTRA, KE_S_XF1)
#define K_S_XF2 TERMCAP2KEY(KS_EXTRA, KE_S_XF2)
#define K_S_XF3 TERMCAP2KEY(KS_EXTRA, KE_S_XF3)
@@ -346,7 +346,7 @@ enum key_extra {
#define K_S_F11 TERMCAP2KEY(KS_EXTRA, KE_S_F11)
#define K_S_F12 TERMCAP2KEY(KS_EXTRA, KE_S_F12)
-/* K_S_F13 to K_S_F37 are currently not used */
+// K_S_F13 to K_S_F37 are currently not used
#define K_HELP TERMCAP2KEY('%', '1')
#define K_UNDO TERMCAP2KEY('&', '8')
@@ -443,8 +443,8 @@ enum key_extra {
#define K_EVENT TERMCAP2KEY(KS_EXTRA, KE_EVENT)
#define K_COMMAND TERMCAP2KEY(KS_EXTRA, KE_COMMAND)
-/* Bits for modifier mask */
-/* 0x01 cannot be used, because the modifier must be 0x02 or higher */
+// Bits for modifier mask
+// 0x01 cannot be used, because the modifier must be 0x02 or higher
#define MOD_MASK_SHIFT 0x02
#define MOD_MASK_CTRL 0x04
#define MOD_MASK_ALT 0x08 // aka META
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c
index 242d4e18d1..144646fca2 100644
--- a/src/nvim/lua/executor.c
+++ b/src/nvim/lua/executor.c
@@ -11,6 +11,7 @@
#include "nvim/func_attr.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/api/private/handle.h"
#include "nvim/api/vim.h"
#include "nvim/msgpack_rpc/channel.h"
#include "nvim/vim.h"
@@ -19,12 +20,14 @@
#include "nvim/message.h"
#include "nvim/memline.h"
#include "nvim/buffer_defs.h"
+#include "nvim/regexp.h"
#include "nvim/macros.h"
#include "nvim/screen.h"
#include "nvim/cursor.h"
#include "nvim/undo.h"
#include "nvim/ascii.h"
#include "nvim/change.h"
+#include "nvim/eval/userfunc.h"
#ifdef WIN32
#include "nvim/os/os.h"
@@ -244,6 +247,14 @@ static int nlua_schedule(lua_State *const lstate)
return 0;
}
+static struct luaL_Reg regex_meta[] = {
+ { "__gc", regex_gc },
+ { "__tostring", regex_tostring },
+ { "match_str", regex_match_str },
+ { "match_line", regex_match_line },
+ { NULL, NULL }
+};
+
/// Initialize lua interpreter state
///
/// Called by lua interpreter itself to initialize state.
@@ -291,6 +302,15 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
// call
lua_pushcfunction(lstate, &nlua_call);
lua_setfield(lstate, -2, "call");
+ // regex
+ lua_pushcfunction(lstate, &nlua_regex);
+ lua_setfield(lstate, -2, "regex");
+
+ luaL_newmetatable(lstate, "nvim_regex");
+ luaL_register(lstate, NULL, regex_meta);
+ lua_pushvalue(lstate, -1); // [meta, meta]
+ lua_setfield(lstate, -2, "__index"); // [meta]
+ lua_pop(lstate, 1); // don't use metatable now
// rpcrequest
lua_pushcfunction(lstate, &nlua_rpcrequest);
@@ -528,7 +548,7 @@ int nlua_debug(lua_State *lstate)
for (;;) {
lua_settop(lstate, 0);
typval_T input;
- get_user_input(input_args, &input, false);
+ get_user_input(input_args, &input, false, false);
msg_putchar('\n'); // Avoid outputting on input line.
if (input.v_type != VAR_STRING
|| input.vval.v_string == NULL
@@ -1025,12 +1045,148 @@ static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
lua_pushcfunction(lstate, create_tslua_parser);
lua_setfield(lstate, -2, "_create_ts_parser");
- lua_pushcfunction(lstate, tslua_register_lang);
+ lua_pushcfunction(lstate, tslua_add_language);
lua_setfield(lstate, -2, "_ts_add_language");
+ lua_pushcfunction(lstate, tslua_has_language);
+ lua_setfield(lstate, -2, "_ts_has_language");
+
lua_pushcfunction(lstate, tslua_inspect_lang);
lua_setfield(lstate, -2, "_ts_inspect_language");
lua_pushcfunction(lstate, ts_lua_parse_query);
lua_setfield(lstate, -2, "_ts_parse_query");
}
+
+static int nlua_regex(lua_State *lstate)
+{
+ Error err = ERROR_INIT;
+ const char *text = luaL_checkstring(lstate, 1);
+ regprog_T *prog = NULL;
+
+ TRY_WRAP({
+ try_start();
+ prog = vim_regcomp((char_u *)text, RE_AUTO | RE_MAGIC | RE_STRICT);
+ try_end(&err);
+ });
+
+ if (ERROR_SET(&err)) {
+ return luaL_error(lstate, "couldn't parse regex: %s", err.msg);
+ }
+ assert(prog);
+
+ regprog_T **p = lua_newuserdata(lstate, sizeof(regprog_T *));
+ *p = prog;
+
+ lua_getfield(lstate, LUA_REGISTRYINDEX, "nvim_regex"); // [udata, meta]
+ lua_setmetatable(lstate, -2); // [udata]
+ return 1;
+}
+
+static regprog_T **regex_check(lua_State *L)
+{
+ return luaL_checkudata(L, 1, "nvim_regex");
+}
+
+
+static int regex_gc(lua_State *lstate)
+{
+ regprog_T **prog = regex_check(lstate);
+ vim_regfree(*prog);
+ return 0;
+}
+
+static int regex_tostring(lua_State *lstate)
+{
+ lua_pushstring(lstate, "<regex>");
+ return 1;
+}
+
+static int regex_match(lua_State *lstate, regprog_T **prog, char_u *str)
+{
+ regmatch_T rm;
+ rm.regprog = *prog;
+ rm.rm_ic = false;
+ bool match = vim_regexec(&rm, str, 0);
+ *prog = rm.regprog;
+
+ if (match) {
+ lua_pushinteger(lstate, (lua_Integer)(rm.startp[0]-str));
+ lua_pushinteger(lstate, (lua_Integer)(rm.endp[0]-str));
+ return 2;
+ }
+ return 0;
+}
+
+static int regex_match_str(lua_State *lstate)
+{
+ regprog_T **prog = regex_check(lstate);
+ const char *str = luaL_checkstring(lstate, 2);
+ int nret = regex_match(lstate, prog, (char_u *)str);
+
+ if (!*prog) {
+ return luaL_error(lstate, "regex: internal error");
+ }
+
+ return nret;
+}
+
+static int regex_match_line(lua_State *lstate)
+{
+ regprog_T **prog = regex_check(lstate);
+
+ int narg = lua_gettop(lstate);
+ if (narg < 3) {
+ return luaL_error(lstate, "not enough args");
+ }
+
+ long bufnr = luaL_checkinteger(lstate, 2);
+ long rownr = luaL_checkinteger(lstate, 3);
+ long start = 0, end = -1;
+ if (narg >= 4) {
+ start = luaL_checkinteger(lstate, 4);
+ }
+ if (narg >= 5) {
+ end = luaL_checkinteger(lstate, 5);
+ if (end < 0) {
+ return luaL_error(lstate, "invalid end");
+ }
+ }
+
+ buf_T *buf = bufnr ? handle_get_buffer((int)bufnr) : curbuf;
+ if (!buf || buf->b_ml.ml_mfp == NULL) {
+ return luaL_error(lstate, "invalid buffer");
+ }
+
+ if (rownr >= buf->b_ml.ml_line_count) {
+ return luaL_error(lstate, "invalid row");
+ }
+
+ char_u *line = ml_get_buf(buf, rownr+1, false);
+ size_t len = STRLEN(line);
+
+ if (start < 0 || (size_t)start > len) {
+ return luaL_error(lstate, "invalid start");
+ }
+
+ char_u save = NUL;
+ if (end >= 0) {
+ if ((size_t)end > len || end < start) {
+ return luaL_error(lstate, "invalid end");
+ }
+ save = line[end];
+ line[end] = NUL;
+ }
+
+ int nret = regex_match(lstate, prog, line+start);
+
+ if (end >= 0) {
+ line[end] = save;
+ }
+
+ if (!*prog) {
+ return luaL_error(lstate, "regex: internal error");
+ }
+
+ return nret;
+}
diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c
index 874fabd89f..51d9549033 100644
--- a/src/nvim/lua/treesitter.c
+++ b/src/nvim/lua/treesitter.c
@@ -93,10 +93,7 @@ static PMap(cstr_t) *langs;
static void build_meta(lua_State *L, const char *tname, const luaL_Reg *meta)
{
if (luaL_newmetatable(L, tname)) { // [meta]
- for (size_t i = 0; meta[i].name != NULL; i++) {
- lua_pushcfunction(L, meta[i].func); // [meta, func]
- lua_setfield(L, -2, meta[i].name); // [meta]
- }
+ luaL_register(L, NULL, meta);
lua_pushvalue(L, -1); // [meta, meta]
lua_setfield(L, -2, "__index"); // [meta]
@@ -119,7 +116,14 @@ void tslua_init(lua_State *L)
build_meta(L, "treesitter_querycursor", querycursor_meta);
}
-int tslua_register_lang(lua_State *L)
+int tslua_has_language(lua_State *L)
+{
+ const char *lang_name = luaL_checkstring(L, 1);
+ lua_pushboolean(L, pmap_has(cstr_t)(langs, lang_name));
+ return 1;
+}
+
+int tslua_add_language(lua_State *L)
{
if (lua_gettop(L) < 2 || !lua_isstring(L, 1) || !lua_isstring(L, 2)) {
return luaL_error(L, "string expected");
@@ -267,17 +271,22 @@ static const char *input_cb(void *payload, uint32_t byte_index,
}
char_u *line = ml_get_buf(bp, position.row+1, false);
size_t len = STRLEN(line);
- size_t tocopy = MIN(len-position.column, BUFSIZE);
-
- memcpy(buf, line+position.column, tocopy);
- // Translate embedded \n to NUL
- memchrsub(buf, '\n', '\0', tocopy);
- *bytes_read = (uint32_t)tocopy;
- if (tocopy < BUFSIZE) {
- // now add the final \n. If it didn't fit, input_cb will be called again
- // on the same line with advanced column.
- buf[tocopy] = '\n';
- (*bytes_read)++;
+
+ if (position.column > len) {
+ *bytes_read = 0;
+ } else {
+ size_t tocopy = MIN(len-position.column, BUFSIZE);
+
+ memcpy(buf, line+position.column, tocopy);
+ // Translate embedded \n to NUL
+ memchrsub(buf, '\n', '\0', tocopy);
+ *bytes_read = (uint32_t)tocopy;
+ if (tocopy < BUFSIZE) {
+ // now add the final \n. If it didn't fit, input_cb will be called again
+ // on the same line with advanced column.
+ buf[tocopy] = '\n';
+ (*bytes_read)++;
+ }
}
return buf;
#undef BUFSIZE
diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua
index 8ba550ea31..43a0a76b94 100644
--- a/src/nvim/lua/vim.lua
+++ b/src/nvim/lua/vim.lua
@@ -212,6 +212,18 @@ do
vim.api.nvim_put(lines, 'c', true, true)
-- XXX: Normal-mode: workaround bad cursor-placement after first chunk.
vim.api.nvim_command('normal! a')
+ elseif phase < 2 and mode == 'R' then
+ local nchars = 0
+ for _, line in ipairs(lines) do
+ nchars = nchars + line:len()
+ end
+ local row, col = unpack(vim.api.nvim_win_get_cursor(0))
+ local bufline = vim.api.nvim_buf_get_lines(0, row-1, row, true)[1]
+ local firstline = lines[1]
+ firstline = bufline:sub(1, col)..firstline
+ lines[1] = firstline
+ lines[#lines] = lines[#lines]..bufline:sub(col + nchars + 1, bufline:len())
+ vim.api.nvim_buf_set_lines(0, row-1, row, false, lines)
else
vim.api.nvim_put(lines, 'c', false, true)
end
@@ -320,9 +332,26 @@ do
return pcall_ret(pcall(fn, ...))
end
end
+
+ vim.b = make_meta_accessor(
+ nil_wrap(function(v) return a.nvim_buf_get_var(0, v) end),
+ function(v, k) return a.nvim_buf_set_var(0, v, k) end,
+ function(v) return a.nvim_buf_del_var(0, v) end
+ )
+ vim.w = make_meta_accessor(
+ nil_wrap(function(v) return a.nvim_win_get_var(0, v) end),
+ function(v, k) return a.nvim_win_set_var(0, v, k) end,
+ function(v) return a.nvim_win_del_var(0, v) end
+ )
+ vim.t = make_meta_accessor(
+ nil_wrap(function(v) return a.nvim_tabpage_get_var(0, v) end),
+ function(v, k) return a.nvim_tabpage_set_var(0, v, k) end,
+ function(v) return a.nvim_tabpage_del_var(0, v) end
+ )
vim.g = make_meta_accessor(nil_wrap(a.nvim_get_var), a.nvim_set_var, a.nvim_del_var)
vim.v = make_meta_accessor(nil_wrap(a.nvim_get_vvar), a.nvim_set_vvar)
vim.o = make_meta_accessor(a.nvim_get_option, a.nvim_set_option)
+
local function getenv(k)
local v = vim.fn.getenv(k)
if v == vim.NIL then
diff --git a/src/nvim/macros.h b/src/nvim/macros.h
index f6c8c0a4a0..3df7fa768d 100644
--- a/src/nvim/macros.h
+++ b/src/nvim/macros.h
@@ -36,38 +36,34 @@
#define BUFEMPTY() (curbuf->b_ml.ml_line_count == 1 && *ml_get((linenr_T)1) == \
NUL)
-/*
- * toupper() and tolower() that use the current locale.
- * Careful: Only call TOUPPER_LOC() and TOLOWER_LOC() with a character in the
- * range 0 - 255. toupper()/tolower() on some systems can't handle others.
- * Note: It is often better to use mb_tolower() and mb_toupper(), because many
- * toupper() and tolower() implementations only work for ASCII.
- */
+// toupper() and tolower() that use the current locale.
+// Careful: Only call TOUPPER_LOC() and TOLOWER_LOC() with a character in the
+// range 0 - 255. toupper()/tolower() on some systems can't handle others.
+// Note: It is often better to use mb_tolower() and mb_toupper(), because many
+// toupper() and tolower() implementations only work for ASCII.
#define TOUPPER_LOC toupper
#define TOLOWER_LOC tolower
-/* toupper() and tolower() for ASCII only and ignore the current locale. */
+// toupper() and tolower() for ASCII only and ignore the current locale.
# define TOUPPER_ASC(c) (((c) < 'a' || (c) > 'z') ? (c) : (c) - ('a' - 'A'))
# define TOLOWER_ASC(c) (((c) < 'A' || (c) > 'Z') ? (c) : (c) + ('a' - 'A'))
-/* Like isalpha() but reject non-ASCII characters. Can't be used with a
- * special key (negative value). */
+// Like isalpha() but reject non-ASCII characters. Can't be used with a
+// special key (negative value).
# define ASCII_ISLOWER(c) ((unsigned)(c) >= 'a' && (unsigned)(c) <= 'z')
# define ASCII_ISUPPER(c) ((unsigned)(c) >= 'A' && (unsigned)(c) <= 'Z')
# define ASCII_ISALPHA(c) (ASCII_ISUPPER(c) || ASCII_ISLOWER(c))
# define ASCII_ISALNUM(c) (ASCII_ISALPHA(c) || ascii_isdigit(c))
-/* Returns empty string if it is NULL. */
+// Returns empty string if it is NULL.
#define EMPTY_IF_NULL(x) ((x) ? (x) : (char_u *)"")
-/*
- * Adjust chars in a language according to 'langmap' option.
- * NOTE that there is no noticeable overhead if 'langmap' is not set.
- * When set the overhead for characters < 256 is small.
- * Don't apply 'langmap' if the character comes from the Stuff buffer or from a
- * mapping and the langnoremap option was set.
- * The do-while is just to ignore a ';' after the macro.
- */
+// Adjust chars in a language according to 'langmap' option.
+// NOTE that there is no noticeable overhead if 'langmap' is not set.
+// When set the overhead for characters < 256 is small.
+// Don't apply 'langmap' if the character comes from the Stuff buffer or from a
+// mapping and the langnoremap option was set.
+// The do-while is just to ignore a ';' after the macro.
# define LANGMAP_ADJUST(c, condition) \
do { \
if (*p_langmap \
@@ -83,12 +79,12 @@
} \
} while (0)
-#define WRITEBIN "wb" /* no CR-LF translation */
+#define WRITEBIN "wb" // no CR-LF translation
#define READBIN "rb"
#define APPENDBIN "ab"
-/* mch_open_rw(): invoke os_open() with third argument for user R/W. */
-#if defined(UNIX) /* open in rw------- mode */
+// mch_open_rw(): invoke os_open() with third argument for user R/W.
+#if defined(UNIX) // open in rw------- mode
# define mch_open_rw(n, f) os_open((n), (f), (mode_t)0600)
#elif defined(WIN32)
# define mch_open_rw(n, f) os_open((n), (f), S_IREAD | S_IWRITE)
@@ -100,7 +96,7 @@
# define UTF_COMPOSINGLIKE(p1, p2) utf_composinglike((p1), (p2))
-/* Whether to draw the vertical bar on the right side of the cell. */
+// Whether to draw the vertical bar on the right side of the cell.
# define CURSOR_BAR_RIGHT (curwin->w_p_rl && (!(State & CMDLINE) || cmdmsg_rl))
// MB_PTR_ADV(): advance a pointer to the next character, taking care of
diff --git a/src/nvim/main.c b/src/nvim/main.c
index a816221a9e..4a9f2371a2 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -10,6 +10,7 @@
#include <msgpack.h>
#include "nvim/ascii.h"
+#include "nvim/channel.h"
#include "nvim/vim.h"
#include "nvim/main.h"
#include "nvim/aucmd.h"
@@ -207,7 +208,7 @@ void early_init(void)
// Allocate the first window and buffer.
// Can't do anything without it, exit when it fails.
if (!win_alloc_first()) {
- mch_exit(0);
+ os_exit(0);
}
init_yank(); // init yank buffers
@@ -293,8 +294,8 @@ int main(int argc, char **argv)
if (params.diff_mode && params.window_count == -1)
params.window_count = 0; /* open up to 3 windows */
- /* Don't redraw until much later. */
- ++RedrawingDisabled;
+ // Don't redraw until much later.
+ RedrawingDisabled++;
setbuf(stdout, NULL);
@@ -384,23 +385,17 @@ int main(int argc, char **argv)
syn_maybe_on();
}
- /*
- * Read all the plugin files.
- * Only when compiled with +eval, since most plugins need it.
- */
+ // Read all the plugin files.
load_plugins();
// Decide about window layout for diff mode after reading vimrc.
set_window_layout(&params);
- /*
- * Recovery mode without a file name: List swap files.
- * This uses the 'dir' option, therefore it must be after the
- * initializations.
- */
+ // Recovery mode without a file name: List swap files.
+ // Uses the 'dir' option, therefore it must be after the initializations.
if (recoverymode && fname == NULL) {
- recover_names(NULL, TRUE, 0, NULL);
- mch_exit(0);
+ recover_names(NULL, true, 0, NULL);
+ os_exit(0);
}
// Set some option defaults after reading vimrc files.
@@ -431,17 +426,15 @@ int main(int argc, char **argv)
set_vim_var_list(VV_OLDFILES, tv_list_alloc(0));
}
- /*
- * "-q errorfile": Load the error file now.
- * If the error file can't be read, exit before doing anything else.
- */
+ // "-q errorfile": Load the error file now.
+ // If the error file can't be read, exit before doing anything else.
handle_quickfix(&params);
- /*
- * Start putting things on the screen.
- * Scroll screen down before drawing over it
- * Clear screen now, so file message will not be cleared.
- */
+ //
+ // Start putting things on the screen.
+ // Scroll screen down before drawing over it
+ // Clear screen now, so file message will not be cleared.
+ //
starting = NO_BUFFERS;
no_wait_return = false;
if (!exmode_active) {
@@ -473,27 +466,26 @@ int main(int argc, char **argv)
no_wait_return = true;
- /*
- * Create the requested number of windows and edit buffers in them.
- * Also does recovery if "recoverymode" set.
- */
+ //
+ // Create the requested number of windows and edit buffers in them.
+ // Also does recovery if "recoverymode" set.
+ //
create_windows(&params);
TIME_MSG("opening buffers");
- /* clear v:swapcommand */
+ // Clear v:swapcommand
set_vim_var_string(VV_SWAPCOMMAND, NULL, -1);
- /* Ex starts at last line of the file */
- if (exmode_active)
+ // Ex starts at last line of the file.
+ if (exmode_active) {
curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
+ }
apply_autocmds(EVENT_BUFENTER, NULL, NULL, FALSE, curbuf);
TIME_MSG("BufEnter autocommands");
setpcmark();
- /*
- * When started with "-q errorfile" jump to first error now.
- */
+ // When started with "-q errorfile" jump to first error now.
if (params.edit_type == EDIT_QF) {
qf_jump(NULL, 0, 0, FALSE);
TIME_MSG("jump to first error");
@@ -505,26 +497,23 @@ int main(int argc, char **argv)
xfree(cwd);
if (params.diff_mode) {
- /* set options in each window for "nvim -d". */
+ // set options in each window for "nvim -d".
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
diff_win_options(wp, TRUE);
}
}
- /*
- * Shorten any of the filenames, but only when absolute.
- */
- shorten_fnames(FALSE);
+ // Shorten any of the filenames, but only when absolute.
+ shorten_fnames(false);
- /*
- * Need to jump to the tag before executing the '-c command'.
- * Makes "vim -c '/return' -t main" work.
- */
+ // Need to jump to the tag before executing the '-c command'.
+ // Makes "vim -c '/return' -t main" work.
handle_tag(params.tagname);
- /* Execute any "+", "-c" and "-S" arguments. */
- if (params.n_commands > 0)
+ // Execute any "+", "-c" and "-S" arguments.
+ if (params.n_commands > 0) {
exe_commands(&params);
+ }
starting = 0;
@@ -535,9 +524,10 @@ int main(int argc, char **argv)
// 'autochdir' has been postponed.
do_autochdir();
- /* start in insert mode */
- if (p_im)
- need_start_insertmode = TRUE;
+ // start in insert mode
+ if (p_im) {
+ need_start_insertmode = true;
+ }
set_vim_var_nr(VV_VIM_DID_ENTER, 1L);
apply_autocmds(EVENT_VIMENTER, NULL, NULL, false, curbuf);
@@ -553,18 +543,19 @@ int main(int argc, char **argv)
// main loop.
set_reg_var(get_default_register_name());
- /* When a startup script or session file setup for diff'ing and
- * scrollbind, sync the scrollbind now. */
+ // When a startup script or session file setup for diff'ing and
+ // scrollbind, sync the scrollbind now.
if (curwin->w_p_diff && curwin->w_p_scb) {
update_topline();
check_scrollbind((linenr_T)0, 0L);
TIME_MSG("diff scrollbinding");
}
- /* If ":startinsert" command used, stuff a dummy command to be able to
- * call normal_cmd(), which will then start Insert mode. */
- if (restart_edit != 0)
+ // If ":startinsert" command used, stuff a dummy command to be able to
+ // call normal_cmd(), which will then start Insert mode.
+ if (restart_edit != 0) {
stuffcharReadbuff(K_NOP);
+ }
// WORKAROUND(mhi): #3023
if (cb_flags & CB_UNNAMEDMASK) {
@@ -574,9 +565,7 @@ int main(int argc, char **argv)
TIME_MSG("before starting main loop");
ILOG("starting main loop");
- /*
- * Call the main command loop. This never returns.
- */
+ // Main loop: never returns.
normal_enter(false, false);
#if defined(WIN32) && !defined(MAKE_LIB)
@@ -585,6 +574,31 @@ int main(int argc, char **argv)
return 0;
}
+void os_exit(int r)
+ FUNC_ATTR_NORETURN
+{
+ exiting = true;
+
+ ui_flush();
+ ui_call_stop();
+ ml_close_all(true); // remove all memfiles
+
+ if (!event_teardown() && r == 0) {
+ r = 1; // Exit with error if main_loop did not teardown gracefully.
+ }
+ if (input_global_fd() >= 0) {
+ stream_set_blocking(input_global_fd(), true); // normalize stream (#2598)
+ }
+
+ ILOG("Nvim exit: %d", r);
+
+#ifdef EXITFREE
+ free_all_mem();
+#endif
+
+ exit(r);
+}
+
/// Exit properly
void getout(int exitval)
FUNC_ATTR_NORETURN
@@ -679,7 +693,7 @@ void getout(int exitval)
garbage_collect(false);
}
- mch_exit(exitval);
+ os_exit(exitval);
}
/// Gets the integer value of a numeric command line argument if given,
@@ -799,10 +813,10 @@ static void command_line_scan(mparm_T *parmp)
// "--cmd <cmd>" execute cmd before vimrc
if (STRICMP(argv[0] + argv_idx, "help") == 0) {
usage();
- mch_exit(0);
+ os_exit(0);
} else if (STRICMP(argv[0] + argv_idx, "version") == 0) {
version();
- mch_exit(0);
+ os_exit(0);
} else if (STRICMP(argv[0] + argv_idx, "api-info") == 0) {
FileDescriptor fp;
const int fof_ret = file_open_fd(&fp, STDOUT_FILENO,
@@ -825,7 +839,7 @@ static void command_line_scan(mparm_T *parmp)
if (ff_ret < 0) {
msgpack_file_write_error(ff_ret);
}
- mch_exit(0);
+ os_exit(0);
} else if (STRICMP(argv[0] + argv_idx, "headless") == 0) {
headless_mode = true;
} else if (STRICMP(argv[0] + argv_idx, "embed") == 0) {
@@ -891,7 +905,7 @@ static void command_line_scan(mparm_T *parmp)
case '?': // "-?" give help message (for MS-Windows)
case 'h': { // "-h" give help message
usage();
- mch_exit(0);
+ os_exit(0);
}
case 'H': { // "-H" start in Hebrew mode: rl + hkmap set.
p_hkmap = true;
@@ -988,7 +1002,7 @@ static void command_line_scan(mparm_T *parmp)
}
case 'v': {
version();
- mch_exit(0);
+ os_exit(0);
}
case 'V': { // "-V{N}" Verbose level
// default is 10: a little bit verbose
@@ -1116,7 +1130,7 @@ scripterror:
_("Attempt to open script file again: \"%s %s\"\n"),
argv[-1], argv[0]);
mch_errmsg((const char *)IObuff);
- mch_exit(2);
+ os_exit(2);
}
int error;
if (strequal(argv[0], "-")) {
@@ -1135,7 +1149,7 @@ scripterror:
_("Cannot open for reading: \"%s\": %s\n"),
argv[0], os_strerror(error));
mch_errmsg((const char *)IObuff);
- mch_exit(2);
+ os_exit(2);
}
save_typebuf();
break;
@@ -1173,7 +1187,7 @@ scripterror:
mch_errmsg(_("Cannot open for script output: \""));
mch_errmsg(argv[0]);
mch_errmsg("\"\n");
- mch_exit(2);
+ os_exit(2);
}
break;
}
@@ -1380,7 +1394,7 @@ static void handle_quickfix(mparm_T *paramp)
vim_snprintf((char *)IObuff, IOSIZE, "cfile %s", p_ef);
if (qf_init(NULL, p_ef, p_efm, true, IObuff, p_menc) < 0) {
msg_putchar('\n');
- mch_exit(3);
+ os_exit(3);
}
TIME_MSG("reading errorfile");
}
@@ -1943,7 +1957,7 @@ static void mainerr(const char *errstr, const char *str)
mch_errmsg(prgname);
mch_errmsg(" -h\"\n");
- mch_exit(1);
+ os_exit(1);
}
/// Prints version information for "nvim -v" or "nvim --version".
diff --git a/src/nvim/marktree.c b/src/nvim/marktree.c
index 52e602cd94..6dd452b5af 100644
--- a/src/nvim/marktree.c
+++ b/src/nvim/marktree.c
@@ -197,7 +197,7 @@ static inline void split_node(MarkTree *b, mtnode_t *x, const int i)
// x must not be a full node (even if there might be internal space)
static inline void marktree_putp_aux(MarkTree *b, mtnode_t *x, mtkey_t k)
{
- int i = x->n - 1;
+ int i;
if (x->level == 0) {
i = marktree_getp_aux(x, k, 0);
if (i != x->n - 1) {
@@ -849,7 +849,8 @@ bool marktree_splice(MarkTree *b,
bool same_line = old_extent.row == 0 && new_extent.row == 0;
unrelative(start, &old_extent);
unrelative(start, &new_extent);
- MarkTreeIter itr[1], enditr[1];
+ MarkTreeIter itr[1] = { 0 };
+ MarkTreeIter enditr[1] = { 0 };
mtpos_t oldbase[MT_MAX_DEPTH];
@@ -905,7 +906,7 @@ continue_same_node:
refkey(b, itr->node, itr->i);
refkey(b, enditr->node, enditr->i);
} else {
- past_right = true;
+ past_right = true; // NOLINT
break;
}
}
@@ -1003,7 +1004,7 @@ void marktree_move_region(MarkTree *b,
mtpos_t start = { start_row, start_col }, size = { extent_row, extent_col };
mtpos_t end = size;
unrelative(start, &end);
- MarkTreeIter itr[1];
+ MarkTreeIter itr[1] = { 0 };
marktree_itr_get_ext(b, start, itr, false, true, NULL);
kvec_t(mtkey_t) saved = KV_INITIAL_VALUE;
while (itr->node) {
diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c
index 85e6697bfb..e67be60aa6 100644
--- a/src/nvim/mbyte.c
+++ b/src/nvim/mbyte.c
@@ -265,68 +265,70 @@ static struct
{ const char *name; int canon; }
enc_alias_table[] =
{
- {"ansi", IDX_LATIN_1},
- {"iso-8859-1", IDX_LATIN_1},
- {"latin2", IDX_ISO_2},
- {"latin3", IDX_ISO_3},
- {"latin4", IDX_ISO_4},
- {"cyrillic", IDX_ISO_5},
- {"arabic", IDX_ISO_6},
- {"greek", IDX_ISO_7},
- {"hebrew", IDX_ISO_8},
- {"latin5", IDX_ISO_9},
- {"turkish", IDX_ISO_9}, /* ? */
- {"latin6", IDX_ISO_10},
- {"nordic", IDX_ISO_10}, /* ? */
- {"thai", IDX_ISO_11}, /* ? */
- {"latin7", IDX_ISO_13},
- {"latin8", IDX_ISO_14},
- {"latin9", IDX_ISO_15},
- {"utf8", IDX_UTF8},
- {"unicode", IDX_UCS2},
- {"ucs2", IDX_UCS2},
- {"ucs2be", IDX_UCS2},
- {"ucs-2be", IDX_UCS2},
- {"ucs2le", IDX_UCS2LE},
- {"utf16", IDX_UTF16},
- {"utf16be", IDX_UTF16},
- {"utf-16be", IDX_UTF16},
- {"utf16le", IDX_UTF16LE},
- {"ucs4", IDX_UCS4},
- {"ucs4be", IDX_UCS4},
- {"ucs-4be", IDX_UCS4},
- {"ucs4le", IDX_UCS4LE},
- {"utf32", IDX_UCS4},
- {"utf-32", IDX_UCS4},
- {"utf32be", IDX_UCS4},
- {"utf-32be", IDX_UCS4},
- {"utf32le", IDX_UCS4LE},
- {"utf-32le", IDX_UCS4LE},
- {"932", IDX_CP932},
- {"949", IDX_CP949},
- {"936", IDX_CP936},
- {"gbk", IDX_CP936},
- {"950", IDX_CP950},
- {"eucjp", IDX_EUC_JP},
- {"unix-jis", IDX_EUC_JP},
- {"ujis", IDX_EUC_JP},
- {"shift-jis", IDX_SJIS},
- {"pck", IDX_SJIS}, /* Sun: PCK */
- {"euckr", IDX_EUC_KR},
- {"5601", IDX_EUC_KR}, /* Sun: KS C 5601 */
- {"euccn", IDX_EUC_CN},
- {"gb2312", IDX_EUC_CN},
- {"euctw", IDX_EUC_TW},
- {"japan", IDX_EUC_JP},
- {"korea", IDX_EUC_KR},
- {"prc", IDX_EUC_CN},
- {"chinese", IDX_EUC_CN},
- {"taiwan", IDX_EUC_TW},
- {"cp950", IDX_BIG5},
- {"950", IDX_BIG5},
- {"mac", IDX_MACROMAN},
- {"mac-roman", IDX_MACROMAN},
- {NULL, 0}
+ { "ansi", IDX_LATIN_1 },
+ { "iso-8859-1", IDX_LATIN_1 },
+ { "latin2", IDX_ISO_2 },
+ { "latin3", IDX_ISO_3 },
+ { "latin4", IDX_ISO_4 },
+ { "cyrillic", IDX_ISO_5 },
+ { "arabic", IDX_ISO_6 },
+ { "greek", IDX_ISO_7 },
+ { "hebrew", IDX_ISO_8 },
+ { "latin5", IDX_ISO_9 },
+ { "turkish", IDX_ISO_9 }, // ?
+ { "latin6", IDX_ISO_10 },
+ { "nordic", IDX_ISO_10 }, // ?
+ { "thai", IDX_ISO_11 }, // ?
+ { "latin7", IDX_ISO_13 },
+ { "latin8", IDX_ISO_14 },
+ { "latin9", IDX_ISO_15 },
+ { "utf8", IDX_UTF8 },
+ { "unicode", IDX_UCS2 },
+ { "ucs2", IDX_UCS2 },
+ { "ucs2be", IDX_UCS2 },
+ { "ucs-2be", IDX_UCS2 },
+ { "ucs2le", IDX_UCS2LE },
+ { "utf16", IDX_UTF16 },
+ { "utf16be", IDX_UTF16 },
+ { "utf-16be", IDX_UTF16 },
+ { "utf16le", IDX_UTF16LE },
+ { "ucs4", IDX_UCS4 },
+ { "ucs4be", IDX_UCS4 },
+ { "ucs-4be", IDX_UCS4 },
+ { "ucs4le", IDX_UCS4LE },
+ { "utf32", IDX_UCS4 },
+ { "utf-32", IDX_UCS4 },
+ { "utf32be", IDX_UCS4 },
+ { "utf-32be", IDX_UCS4 },
+ { "utf32le", IDX_UCS4LE },
+ { "utf-32le", IDX_UCS4LE },
+ { "932", IDX_CP932 },
+ { "949", IDX_CP949 },
+ { "936", IDX_CP936 },
+ { "gbk", IDX_CP936 },
+ { "950", IDX_CP950 },
+ { "eucjp", IDX_EUC_JP },
+ { "unix-jis", IDX_EUC_JP },
+ { "ujis", IDX_EUC_JP },
+ { "shift-jis", IDX_SJIS },
+ { "pck", IDX_SJIS }, // Sun: PCK
+ { "euckr", IDX_EUC_KR },
+ { "5601", IDX_EUC_KR }, // Sun: KS C 5601
+ { "euccn", IDX_EUC_CN },
+ { "gb2312", IDX_EUC_CN },
+ { "euctw", IDX_EUC_TW },
+ { "japan", IDX_EUC_JP },
+ { "korea", IDX_EUC_KR },
+ { "prc", IDX_EUC_CN },
+ { "zh-cn", IDX_EUC_CN },
+ { "chinese", IDX_EUC_CN },
+ { "zh-tw", IDX_EUC_TW },
+ { "taiwan", IDX_EUC_TW },
+ { "cp950", IDX_BIG5 },
+ { "950", IDX_BIG5 },
+ { "mac", IDX_MACROMAN },
+ { "mac-roman", IDX_MACROMAN },
+ { NULL, 0 }
};
/*
diff --git a/src/nvim/memline.c b/src/nvim/memline.c
index 922b684120..6e074b3249 100644
--- a/src/nvim/memline.c
+++ b/src/nvim/memline.c
@@ -1504,16 +1504,15 @@ static time_t swapfile_info(char_u *fname)
int fd;
struct block0 b0;
time_t x = (time_t)0;
- char *p;
#ifdef UNIX
char uname[B0_UNAME_SIZE];
#endif
- /* print the swap file date */
+ // print the swap file date
FileInfo file_info;
if (os_fileinfo((char *)fname, &file_info)) {
#ifdef UNIX
- /* print name of owner of the file */
+ // print name of owner of the file
if (os_get_uname(file_info.stat.st_uid, uname, B0_UNAME_SIZE) == OK) {
MSG_PUTS(_(" owned by: "));
msg_outtrans((char_u *)uname);
@@ -1522,11 +1521,8 @@ static time_t swapfile_info(char_u *fname)
#endif
MSG_PUTS(_(" dated: "));
x = file_info.stat.st_mtim.tv_sec;
- p = ctime(&x); // includes '\n'
- if (p == NULL)
- MSG_PUTS("(invalid)\n");
- else
- MSG_PUTS(p);
+ char ctime_buf[50];
+ MSG_PUTS(os_ctime_r(&x, ctime_buf, sizeof(ctime_buf)));
}
/*
@@ -1863,7 +1859,10 @@ errorret:
// Avoid giving this message for a recursive call, may happen
// when the GUI redraws part of the text.
recursive++;
- IEMSGN(_("E316: ml_get: cannot find line %" PRId64), lnum);
+ get_trans_bufname(buf);
+ shorten_dir(NameBuff);
+ iemsgf(_("E316: ml_get: cannot find line %" PRId64 " in buffer %d %s"),
+ lnum, buf->b_fnum, NameBuff);
recursive--;
}
goto errorret;
@@ -2391,21 +2390,32 @@ static int ml_append_int(
void ml_add_deleted_len(char_u *ptr, ssize_t len)
{
+ ml_add_deleted_len_buf(curbuf, ptr, len);
+}
+
+void ml_add_deleted_len_buf(buf_T *buf, char_u *ptr, ssize_t len)
+{
if (inhibit_delete_count) {
return;
}
if (len == -1) {
len = STRLEN(ptr);
}
- curbuf->deleted_bytes += len+1;
- if (curbuf->update_need_codepoints) {
- mb_utflen(ptr, len, &curbuf->deleted_codepoints,
- &curbuf->deleted_codeunits);
- curbuf->deleted_codepoints++; // NL char
- curbuf->deleted_codeunits++;
+ buf->deleted_bytes += len+1;
+ if (buf->update_need_codepoints) {
+ mb_utflen(ptr, len, &buf->deleted_codepoints,
+ &buf->deleted_codeunits);
+ buf->deleted_codepoints++; // NL char
+ buf->deleted_codeunits++;
}
}
+
+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.
*
@@ -2417,36 +2427,37 @@ void ml_add_deleted_len(char_u *ptr, ssize_t len)
*
* return FAIL for failure, OK otherwise
*/
-int ml_replace(linenr_T lnum, char_u *line, bool copy)
+int ml_replace_buf(buf_T *buf, linenr_T lnum, char_u *line, bool copy)
{
if (line == NULL) /* just checking... */
return FAIL;
- /* When starting up, we might still need to create the memfile */
- if (curbuf->b_ml.ml_mfp == NULL && open_buffer(FALSE, NULL, 0) == FAIL)
+ // When starting up, we might still need to create the memfile
+ if (buf->b_ml.ml_mfp == NULL && open_buffer(false, NULL, 0) == FAIL) {
return FAIL;
+ }
bool readlen = true;
if (copy) {
line = vim_strsave(line);
}
- if (curbuf->b_ml.ml_line_lnum != lnum) { // other line buffered
- ml_flush_line(curbuf); // flush it
- } else if (curbuf->b_ml.ml_flags & ML_LINE_DIRTY) { // same line allocated
- ml_add_deleted_len(curbuf->b_ml.ml_line_ptr, -1);
+ if (buf->b_ml.ml_line_lnum != lnum) { // other line buffered
+ ml_flush_line(buf); // flush it
+ } else if (buf->b_ml.ml_flags & ML_LINE_DIRTY) { // same line allocated
+ ml_add_deleted_len(buf->b_ml.ml_line_ptr, -1);
readlen = false; // already added the length
- xfree(curbuf->b_ml.ml_line_ptr); // free it
+ xfree(buf->b_ml.ml_line_ptr); // free it
}
- if (readlen && kv_size(curbuf->update_callbacks)) {
- ml_add_deleted_len(ml_get_buf(curbuf, lnum, false), -1);
+ if (readlen && kv_size(buf->update_callbacks)) {
+ ml_add_deleted_len(ml_get_buf(buf, lnum, false), -1);
}
- curbuf->b_ml.ml_line_ptr = line;
- curbuf->b_ml.ml_line_lnum = lnum;
- curbuf->b_ml.ml_flags = (curbuf->b_ml.ml_flags | ML_LINE_DIRTY) & ~ML_EMPTY;
+ buf->b_ml.ml_line_ptr = line;
+ buf->b_ml.ml_line_lnum = lnum;
+ buf->b_ml.ml_flags = (buf->b_ml.ml_flags | ML_LINE_DIRTY) & ~ML_EMPTY;
return OK;
}
@@ -3264,15 +3275,13 @@ attention_message (
)
{
assert(buf->b_fname != NULL);
- time_t x, sx;
- char *p;
++no_wait_return;
(void)EMSG(_("E325: ATTENTION"));
MSG_PUTS(_("\nFound a swap file by the name \""));
msg_home_replace(fname);
MSG_PUTS("\"\n");
- sx = swapfile_info(fname);
+ const time_t swap_mtime = swapfile_info(fname);
MSG_PUTS(_("While opening file \""));
msg_outtrans(buf->b_fname);
MSG_PUTS("\"\n");
@@ -3281,14 +3290,12 @@ attention_message (
MSG_PUTS(_(" CANNOT BE FOUND"));
} else {
MSG_PUTS(_(" dated: "));
- x = file_info.stat.st_mtim.tv_sec;
- p = ctime(&x); // includes '\n'
- if (p == NULL)
- MSG_PUTS("(invalid)\n");
- else
- MSG_PUTS(p);
- if (sx != 0 && x > sx)
+ time_t x = file_info.stat.st_mtim.tv_sec;
+ char ctime_buf[50];
+ MSG_PUTS(os_ctime_r(&x, ctime_buf, sizeof(ctime_buf)));
+ if (swap_mtime != 0 && x > swap_mtime) {
MSG_PUTS(_(" NEWER than swap file!\n"));
+ }
}
/* Some of these messages are long to allow translation to
* other languages. */
@@ -3994,8 +4001,8 @@ long ml_find_line_or_offset(buf_T *buf, linenr_T lnum, long *offp, bool no_ff)
int ffdos = !no_ff && (get_fileformat(buf) == EOL_DOS);
int extra = 0;
- /* take care of cached line first */
- ml_flush_line(curbuf);
+ // take care of cached line first
+ ml_flush_line(buf);
if (buf->b_ml.ml_usedchunks == -1
|| buf->b_ml.ml_chunksize == NULL
diff --git a/src/nvim/menu.c b/src/nvim/menu.c
index 18c4f1fff1..b060464383 100644
--- a/src/nvim/menu.c
+++ b/src/nvim/menu.c
@@ -27,6 +27,8 @@
#include "nvim/strings.h"
#include "nvim/ui.h"
#include "nvim/eval/typval.h"
+#include "nvim/screen.h"
+#include "nvim/window.h"
#define MENUDEPTH 10 /* maximum depth of menus */
@@ -46,6 +48,24 @@ static char_u e_notsubmenu[] = N_(
static char_u e_othermode[] = N_("E328: Menu only exists in another mode");
static char_u e_nomenu[] = N_("E329: No menu \"%s\"");
+// Return true if "name" is a window toolbar menu name.
+static bool menu_is_winbar(const char_u *const name)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
+{
+ return (STRNCMP(name, "WinBar", 6) == 0);
+}
+
+int winbar_height(const win_T *const wp)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
+{
+ return wp->w_winbar != NULL && wp->w_winbar->children != NULL ? 1 : 0;
+}
+
+static vimmenu_T **get_root_menu(const char_u *const name)
+ FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
+{
+ return menu_is_winbar(name) ? &curwin->w_winbar : &root_menu;
+}
/// Do the :menu command and relatives.
/// @param eap Ex command arguments
@@ -170,6 +190,12 @@ ex_menu(exarg_T *eap)
goto theend;
}
+ vimmenu_T **root_menu_ptr = get_root_menu(menu_path);
+ if (root_menu_ptr == &curwin->w_winbar) {
+ // Assume the window toolbar menu will change.
+ redraw_later(NOT_VALID);
+ }
+
if (enable != kNone) {
// Change sensitivity of the menu.
// For the PopUp menu, remove a menu for each mode separately.
@@ -182,11 +208,11 @@ ex_menu(exarg_T *eap)
for (i = 0; i < MENU_INDEX_TIP; ++i)
if (modes & (1 << i)) {
p = popup_mode_name(menu_path, i);
- menu_enable_recurse(root_menu, p, MENU_ALL_MODES, enable);
+ menu_enable_recurse(*root_menu_ptr, p, MENU_ALL_MODES, enable);
xfree(p);
}
}
- menu_enable_recurse(root_menu, menu_path, modes, enable);
+ menu_enable_recurse(*root_menu_ptr, menu_path, modes, enable);
} else if (unmenu) {
/*
* Delete menu(s).
@@ -201,13 +227,13 @@ ex_menu(exarg_T *eap)
for (i = 0; i < MENU_INDEX_TIP; ++i)
if (modes & (1 << i)) {
p = popup_mode_name(menu_path, i);
- remove_menu(&root_menu, p, MENU_ALL_MODES, TRUE);
+ remove_menu(root_menu_ptr, p, MENU_ALL_MODES, true);
xfree(p);
}
}
- /* Careful: remove_menu() changes menu_path */
- remove_menu(&root_menu, menu_path, modes, FALSE);
+ // Careful: remove_menu() changes menu_path
+ remove_menu(root_menu_ptr, menu_path, modes, false);
} else {
/*
* Add menu(s).
@@ -244,6 +270,19 @@ ex_menu(exarg_T *eap)
xfree(map_buf);
}
+ if (root_menu_ptr == &curwin->w_winbar) {
+ const int h = winbar_height(curwin);
+
+ if (h != curwin->w_winbar_height) {
+ if (h == 0) {
+ curwin->w_height++;
+ } else if (curwin->w_height > 0) {
+ curwin->w_height--;
+ }
+ curwin->w_winbar_height = h;
+ }
+ }
+
ui_call_update_menu();
theend:
@@ -266,7 +305,6 @@ add_menu_path(
{
char_u *path_name;
int modes = menuarg->modes;
- vimmenu_T **menup;
vimmenu_T *menu = NULL;
vimmenu_T *parent;
vimmenu_T **lower_pri;
@@ -285,7 +323,8 @@ add_menu_path(
/* Make a copy so we can stuff around with it, since it could be const */
path_name = vim_strsave(menu_path);
- menup = &root_menu;
+ vimmenu_T **root_menu_ptr = get_root_menu(menu_path);
+ vimmenu_T **menup = root_menu_ptr;
parent = NULL;
name = path_name;
while (*name) {
@@ -468,14 +507,16 @@ erret:
/* Delete any empty submenu we added before discovering the error. Repeat
* for higher levels. */
while (parent != NULL && parent->children == NULL) {
- if (parent->parent == NULL)
- menup = &root_menu;
- else
+ if (parent->parent == NULL) {
+ menup = root_menu_ptr;
+ } else {
menup = &parent->parent->children;
- for (; *menup != NULL && *menup != parent; menup = &((*menup)->next))
- ;
- if (*menup == NULL) /* safety check */
+ }
+ for (; *menup != NULL && *menup != parent; menup = &((*menup)->next)) {
+ }
+ if (*menup == NULL) { // safety check
break;
+ }
parent = parent->parent;
free_menu(menup);
}
@@ -620,6 +661,14 @@ remove_menu (
return OK;
}
+// Remove the WinBar menu from window "wp".
+void remove_winbar(win_T *wp)
+ FUNC_ATTR_NONNULL_ALL
+{
+ remove_menu(&wp->w_winbar, (char_u *)"", MENU_ALL_MODES, true);
+ xfree(wp->w_winbar_items);
+}
+
/*
* Free the given menu structure and remove it from the linked list.
*/
@@ -740,7 +789,7 @@ static dict_T *menu_get_recursive(const vimmenu_T *menu, int modes)
/// @return false if could not find path_name
bool menu_get(char_u *const path_name, int modes, list_T *list)
{
- vimmenu_T *menu = find_menu(root_menu, path_name, modes);
+ vimmenu_T *menu = find_menu(*get_root_menu(path_name), path_name, modes);
if (!menu) {
return false;
}
@@ -802,10 +851,8 @@ static vimmenu_T *find_menu(vimmenu_T *menu, char_u *name, int modes)
/// Show the mapping associated with a menu item or hierarchy in a sub-menu.
static int show_menus(char_u *const path_name, int modes)
{
- vimmenu_T *menu;
-
// First, find the (sub)menu with the given name
- menu = find_menu(root_menu, path_name, modes);
+ vimmenu_T *menu = find_menu(*get_root_menu(path_name), path_name, modes);
if (!menu) {
return FAIL;
}
@@ -868,7 +915,7 @@ static void show_menus_recursive(vimmenu_T *menu, int modes, int depth)
if (*menu->strings[bit] == NUL) {
msg_puts_attr("<Nop>", HL_ATTR(HLF_8));
} else {
- msg_outtrans_special(menu->strings[bit], false);
+ msg_outtrans_special(menu->strings[bit], false, 0);
}
}
} else {
@@ -890,6 +937,7 @@ static void show_menus_recursive(vimmenu_T *menu, int modes, int depth)
* Used when expanding menu names.
*/
static vimmenu_T *expand_menu = NULL;
+static vimmenu_T *expand_menu_alt = NULL;
static int expand_modes = 0x0;
static int expand_emenu; /* TRUE for ":emenu" command */
@@ -940,14 +988,15 @@ char_u *set_context_in_menu_cmd(expand_T *xp, char_u *cmd, char_u *arg, int forc
// ":popup" only uses menues, not entries
expand_menus = !((*cmd == 't' && cmd[1] == 'e') || *cmd == 'p');
expand_emenu = (*cmd == 'e');
- if (expand_menus && ascii_iswhite(*p))
- return NULL; /* TODO: check for next command? */
- if (*p == NUL) { /* Complete the menu name */
- /*
- * With :unmenu, you only want to match menus for the appropriate mode.
- * With :menu though you might want to add a menu with the same name as
- * one in another mode, so match menus from other modes too.
- */
+ if (expand_menus && ascii_iswhite(*p)) {
+ return NULL; // TODO(vim): check for next command?
+ }
+ if (*p == NUL) { // Complete the menu name
+ bool try_alt_menu = true;
+
+ // With :unmenu, you only want to match menus for the appropriate mode.
+ // With :menu though you might want to add a menu with the same name as
+ // one in another mode, so match menus from other modes too.
expand_modes = get_menu_cmd_modes(cmd, forceit, NULL, &unmenu);
if (!unmenu)
expand_modes = MENU_ALL_MODES;
@@ -976,6 +1025,10 @@ char_u *set_context_in_menu_cmd(expand_T *xp, char_u *cmd, char_u *arg, int forc
break;
}
menu = menu->next;
+ if (menu == NULL && try_alt_menu) {
+ menu = curwin->w_winbar;
+ try_alt_menu = false;
+ }
}
if (menu == NULL) {
/* No menu found with the name we were looking for */
@@ -984,14 +1037,21 @@ char_u *set_context_in_menu_cmd(expand_T *xp, char_u *cmd, char_u *arg, int forc
}
name = p;
menu = menu->children;
+ try_alt_menu = false;
}
xfree(path_name);
xp->xp_context = expand_menus ? EXPAND_MENUNAMES : EXPAND_MENUS;
xp->xp_pattern = after_dot;
expand_menu = menu;
- } else /* We're in the mapping part */
+ if (expand_menu == root_menu) {
+ expand_menu_alt = curwin->w_winbar;
+ } else {
+ expand_menu_alt = NULL;
+ }
+ } else { // We're in the mapping part
xp->xp_context = EXPAND_NOTHING;
+ }
return NULL;
}
@@ -1002,19 +1062,26 @@ char_u *set_context_in_menu_cmd(expand_T *xp, char_u *cmd, char_u *arg, int forc
char_u *get_menu_name(expand_T *xp, int idx)
{
static vimmenu_T *menu = NULL;
+ static bool did_alt_menu = false;
char_u *str;
static int should_advance = FALSE;
if (idx == 0) { /* first call: start at first item */
menu = expand_menu;
- should_advance = FALSE;
+ did_alt_menu = false;
+ should_advance = false;
}
/* Skip PopUp[nvoci]. */
while (menu != NULL && (menu_is_hidden(menu->dname)
|| menu_is_separator(menu->dname)
- || menu->children == NULL))
+ || menu->children == NULL)) {
menu = menu->next;
+ if (menu == NULL && !did_alt_menu) {
+ menu = expand_menu_alt;
+ did_alt_menu = true;
+ }
+ }
if (menu == NULL) /* at end of linked list */
return NULL;
@@ -1030,9 +1097,14 @@ char_u *get_menu_name(expand_T *xp, int idx)
else
str = (char_u *)"";
- if (should_advance)
- /* Advance to next menu entry. */
+ if (should_advance) {
+ // Advance to next menu entry.
menu = menu->next;
+ if (menu == NULL && !did_alt_menu) {
+ menu = expand_menu_alt;
+ did_alt_menu = true;
+ }
+ }
should_advance = !should_advance;
@@ -1046,6 +1118,7 @@ char_u *get_menu_name(expand_T *xp, int idx)
char_u *get_menu_names(expand_T *xp, int idx)
{
static vimmenu_T *menu = NULL;
+ static bool did_alt_menu = false;
#define TBUFFER_LEN 256
static char_u tbuffer[TBUFFER_LEN]; /*hack*/
char_u *str;
@@ -1053,16 +1126,21 @@ char_u *get_menu_names(expand_T *xp, int idx)
if (idx == 0) { /* first call: start at first item */
menu = expand_menu;
- should_advance = FALSE;
+ did_alt_menu = false;
+ should_advance = false;
}
/* Skip Browse-style entries, popup menus and separators. */
while (menu != NULL
- && ( menu_is_hidden(menu->dname)
- || (expand_emenu && menu_is_separator(menu->dname))
- || menu->dname[STRLEN(menu->dname) - 1] == '.'
- ))
+ && (menu_is_hidden(menu->dname)
+ || (expand_emenu && menu_is_separator(menu->dname))
+ || menu->dname[STRLEN(menu->dname) - 1] == '.')) {
menu = menu->next;
+ if (menu == NULL && !did_alt_menu) {
+ menu = expand_menu_alt;
+ did_alt_menu = true;
+ }
+ }
if (menu == NULL) /* at end of linked list */
return NULL;
@@ -1092,9 +1170,14 @@ char_u *get_menu_names(expand_T *xp, int idx)
} else
str = (char_u *)"";
- if (should_advance)
- /* Advance to next menu entry. */
+ if (should_advance) {
+ // Advance to next menu entry.
menu = menu->next;
+ if (menu == NULL && !did_alt_menu) {
+ menu = expand_menu_alt;
+ did_alt_menu = true;
+ }
+ }
should_advance = !should_advance;
@@ -1279,29 +1362,27 @@ static char_u *menu_text(const char_u *str, int *mnemonic, char_u **actext)
return text;
}
-/*
- * Return TRUE if "name" can be a menu in the MenuBar.
- */
-int menu_is_menubar(char_u *name)
+// Return true if "name" can be a menu in the MenuBar.
+bool menu_is_menubar(const char_u *const name)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
return !menu_is_popup(name)
&& !menu_is_toolbar(name)
+ && !menu_is_winbar(name)
&& *name != MNU_HIDDEN_CHAR;
}
-/*
- * Return TRUE if "name" is a popup menu name.
- */
-int menu_is_popup(char_u *name)
+// Return true if "name" is a popup menu name.
+bool menu_is_popup(const char_u *const name)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
return STRNCMP(name, "PopUp", 5) == 0;
}
-/*
- * Return TRUE if "name" is a toolbar menu name.
- */
-int menu_is_toolbar(char_u *name)
+// Return true if "name" is a toolbar menu name.
+bool menu_is_toolbar(const char_u *const name)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
return STRNCMP(name, "ToolBar", 7) == 0;
}
@@ -1325,53 +1406,15 @@ static int menu_is_hidden(char_u *name)
|| (menu_is_popup(name) && name[5] != NUL);
}
-/*
- * Given a menu descriptor, e.g. "File.New", find it in the menu hierarchy and
- * execute it.
- */
-void ex_emenu(exarg_T *eap)
+// Execute "menu". Use by ":emenu" and the window toolbar.
+// "eap" is NULL for the window toolbar.
+static void execute_menu(const exarg_T *eap, vimmenu_T *menu)
+ FUNC_ATTR_NONNULL_ARG(2)
{
- vimmenu_T *menu;
- char_u *name;
- char_u *saved_name;
- char_u *p;
- int idx;
- char_u *mode;
-
- saved_name = vim_strsave(eap->arg);
-
- menu = root_menu;
- name = saved_name;
- while (*name) {
- /* Find in the menu hierarchy */
- p = menu_name_skip(name);
+ int idx = -1;
+ char_u *mode;
- while (menu != NULL) {
- if (menu_name_equal(name, menu)) {
- if (*p == NUL && menu->children != NULL) {
- EMSG(_("E333: Menu path must lead to a menu item"));
- menu = NULL;
- } else if (*p != NUL && menu->children == NULL) {
- EMSG(_(e_notsubmenu));
- menu = NULL;
- }
- break;
- }
- menu = menu->next;
- }
- if (menu == NULL || *p == NUL)
- break;
- menu = menu->children;
- name = p;
- }
- xfree(saved_name);
- if (menu == NULL) {
- EMSG2(_("E334: Menu not found: %s"), eap->arg);
- return;
- }
-
- /* Found the menu, so execute.
- * Use the Insert mode entry when returning to Insert mode. */
+ // Use the Insert mode entry when returning to Insert mode.
if (((State & INSERT) || restart_edit) && !current_sctx.sc_sid) {
mode = (char_u *)"Insert";
idx = MENU_INDEX_INSERT;
@@ -1384,7 +1427,7 @@ void ex_emenu(exarg_T *eap)
* is. Just execute the visual binding for the menu. */
mode = (char_u *)"Visual";
idx = MENU_INDEX_VISUAL;
- } else if (eap->addr_count) {
+ } else if (eap != NULL && eap->addr_count) {
pos_T tpos;
mode = (char_u *)"Visual";
@@ -1422,9 +1465,13 @@ void ex_emenu(exarg_T *eap)
/* Adjust the cursor to make sure it is in the correct pos
* for exclusive mode */
- if (*p_sel == 'e' && gchar_cursor() != NUL)
- ++curwin->w_cursor.col;
- } else {
+ if (*p_sel == 'e' && gchar_cursor() != NUL) {
+ curwin->w_cursor.col++;
+ }
+ }
+
+ // For the WinBar menu always use the Normal mode menu.
+ if (idx == -1 || eap == NULL) {
mode = (char_u *)"Normal";
idx = MENU_INDEX_NORMAL;
}
@@ -1432,19 +1479,114 @@ void ex_emenu(exarg_T *eap)
assert(idx != MENU_INDEX_INVALID);
if (menu->strings[idx] != NULL) {
// When executing a script or function execute the commands right now.
+ // Also for the window toolbar
// Otherwise put them in the typeahead buffer.
- if (current_sctx.sc_sid != 0) {
- exec_normal_cmd(menu->strings[idx], menu->noremap[idx],
- menu->silent[idx]);
+ if (eap == NULL || current_sctx.sc_sid != 0) {
+ save_state_T save_state;
+
+ ex_normal_busy++;
+ if (save_current_state(&save_state)) {
+ exec_normal_cmd(menu->strings[idx], menu->noremap[idx],
+ menu->silent[idx]);
+ }
+ restore_current_state(&save_state);
+ ex_normal_busy--;
} else {
ins_typebuf(menu->strings[idx], menu->noremap[idx], 0, true,
menu->silent[idx]);
}
- } else {
+ } else if (eap != NULL) {
EMSG2(_("E335: Menu not defined for %s mode"), mode);
}
}
+// Given a menu descriptor, e.g. "File.New", find it in the menu hierarchy and
+// execute it.
+void ex_emenu(exarg_T *eap)
+{
+ char_u *saved_name = vim_strsave(eap->arg);
+ vimmenu_T *menu = *get_root_menu(saved_name);
+ char_u *name = saved_name;
+ while (*name) {
+ // Find in the menu hierarchy
+ char_u *p = menu_name_skip(name);
+
+ while (menu != NULL) {
+ if (menu_name_equal(name, menu)) {
+ if (*p == NUL && menu->children != NULL) {
+ EMSG(_("E333: Menu path must lead to a menu item"));
+ menu = NULL;
+ } else if (*p != NUL && menu->children == NULL) {
+ EMSG(_(e_notsubmenu));
+ menu = NULL;
+ }
+ break;
+ }
+ menu = menu->next;
+ }
+ if (menu == NULL || *p == NUL) {
+ break;
+ }
+ menu = menu->children;
+ name = p;
+ }
+ xfree(saved_name);
+ if (menu == NULL) {
+ EMSG2(_("E334: Menu not found: %s"), eap->arg);
+ return;
+ }
+
+ // Found the menu, so execute.
+ execute_menu(eap, menu);
+}
+
+// Handle a click in the window toolbar of "wp" at column "col".
+void winbar_click(win_T *wp, int col)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (wp->w_winbar_items == NULL) {
+ return;
+ }
+ for (int idx = 0; wp->w_winbar_items[idx].wb_menu != NULL; idx++) {
+ winbar_item_T *item = &wp->w_winbar_items[idx];
+
+ if (col >= item->wb_startcol && col <= item->wb_endcol) {
+ win_T *save_curwin = NULL;
+ const pos_T save_visual = VIsual;
+ const int save_visual_active = VIsual_active;
+ const int save_visual_select = VIsual_select;
+ const int save_visual_reselect = VIsual_reselect;
+ const int save_visual_mode = VIsual_mode;
+
+ if (wp != curwin) {
+ // Clicking in the window toolbar of a not-current window.
+ // Make that window the current one and save Visual mode.
+ save_curwin = curwin;
+ VIsual_active = false;
+ curwin = wp;
+ curbuf = curwin->w_buffer;
+ check_cursor();
+ }
+
+ // Note: the command might close the current window.
+ execute_menu(NULL, item->wb_menu);
+
+ if (save_curwin != NULL && win_valid(save_curwin)) {
+ curwin = save_curwin;
+ curbuf = curwin->w_buffer;
+ VIsual = save_visual;
+ VIsual_active = save_visual_active;
+ VIsual_select = save_visual_select;
+ VIsual_reselect = save_visual_reselect;
+ VIsual_mode = save_visual_mode;
+ }
+ if (!win_valid(wp)) {
+ break;
+ }
+ }
+ }
+}
+
/*
* Translation of menu names. Just a simple lookup table.
*/
diff --git a/src/nvim/menu.h b/src/nvim/menu.h
index 5ff979f2bf..642d9aafac 100644
--- a/src/nvim/menu.h
+++ b/src/nvim/menu.h
@@ -6,18 +6,6 @@
#include "nvim/types.h" // for char_u and expand_T
#include "nvim/ex_cmds_defs.h" // for exarg_T
-/// Indices into vimmenu_T->strings[] and vimmenu_T->noremap[] for each mode
-/// \addtogroup MENU_INDEX
-/// @{
-#define MENU_INDEX_INVALID -1
-#define MENU_INDEX_NORMAL 0
-#define MENU_INDEX_VISUAL 1
-#define MENU_INDEX_SELECT 2
-#define MENU_INDEX_OP_PENDING 3
-#define MENU_INDEX_INSERT 4
-#define MENU_INDEX_CMDLINE 5
-#define MENU_INDEX_TIP 6
-#define MENU_MODES 7
/// @}
/// note MENU_INDEX_TIP is not a 'real' mode
@@ -37,28 +25,6 @@
/// Start a menu name with this to not include it on the main menu bar
#define MNU_HIDDEN_CHAR ']'
-typedef struct VimMenu vimmenu_T;
-
-struct VimMenu {
- int modes; ///< Which modes is this menu visible for
- int enabled; ///< for which modes the menu is enabled
- char_u *name; ///< Name of menu, possibly translated
- char_u *dname; ///< Displayed Name ("name" without '&')
- char_u *en_name; ///< "name" untranslated, NULL when
- ///< was not translated
- char_u *en_dname; ///< NULL when "dname" untranslated
- int mnemonic; ///< mnemonic key (after '&')
- char_u *actext; ///< accelerator text (after TAB)
- long priority; ///< Menu order priority
- char_u *strings[MENU_MODES]; ///< Mapped string for each mode
- int noremap[MENU_MODES]; ///< A \ref REMAP_VALUES flag for each mode
- bool silent[MENU_MODES]; ///< A silent flag for each mode
- vimmenu_T *children; ///< Children of sub-menu
- vimmenu_T *parent; ///< Parent of menu
- vimmenu_T *next; ///< Next item in menu
-};
-
-
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "menu.h.generated.h"
#endif
diff --git a/src/nvim/message.c b/src/nvim/message.c
index 94729dfd2a..a12e665099 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -1521,7 +1521,8 @@ void msg_make(char_u *arg)
/// the character/string -- webb
int msg_outtrans_special(
const char_u *strstart,
- int from ///< true for LHS of a mapping
+ bool from, ///< true for LHS of a mapping
+ int maxlen ///< screen columns, 0 for unlimeted
)
{
if (strstart == NULL) {
@@ -1541,6 +1542,9 @@ int msg_outtrans_special(
string = str2special((const char **)&str, from, false);
}
const int len = vim_strsize((char_u *)string);
+ if (maxlen > 0 && retval + len >= maxlen) {
+ break;
+ }
// Highlight special keys
msg_puts_attr(string, (len > 1
&& (*mb_ptr2len)((char_u *)string) <= 1
diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c
index 8c19a2de66..e10770b6bd 100644
--- a/src/nvim/misc1.c
+++ b/src/nvim/misc1.c
@@ -73,7 +73,8 @@ static garray_T ga_users = GA_EMPTY_INIT_VALUE;
* If "include_space" is set, include trailing whitespace while calculating the
* length.
*/
-int get_leader_len(char_u *line, char_u **flags, int backward, int include_space)
+int get_leader_len(char_u *line, char_u **flags,
+ bool backward, bool include_space)
{
int i, j;
int result;
@@ -1161,3 +1162,26 @@ int goto_im(void)
{
return p_im && stuff_empty() && typebuf_typed();
}
+
+/// Put the timestamp of an undo header in "buf[buflen]" in a nice format.
+void add_time(char_u *buf, size_t buflen, time_t tt)
+{
+ struct tm curtime;
+
+ if (time(NULL) - tt >= 100) {
+ os_localtime_r(&tt, &curtime);
+ if (time(NULL) - tt < (60L * 60L * 12L)) {
+ // within 12 hours
+ (void)strftime((char *)buf, buflen, "%H:%M:%S", &curtime);
+ } else {
+ // longer ago
+ (void)strftime((char *)buf, buflen, "%Y/%m/%d %H:%M:%S", &curtime);
+ }
+ } else {
+ int64_t seconds = time(NULL) - tt;
+ vim_snprintf((char *)buf, buflen,
+ NGETTEXT("%" PRId64 " second ago",
+ "%" PRId64 " seconds ago", (uint32_t)seconds),
+ seconds);
+ }
+}
diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c
index dab2e44890..fcd9ee4f75 100644
--- a/src/nvim/mouse.c
+++ b/src/nvim/mouse.c
@@ -60,6 +60,7 @@ int jump_to_mouse(int flags,
{
static int on_status_line = 0; // #lines below bottom of window
static int on_sep_line = 0; // on separator right of window
+ static bool in_winbar = false;
static int prev_row = -1;
static int prev_col = -1;
static win_T *dragwin = NULL; // window being dragged
@@ -73,6 +74,7 @@ int jump_to_mouse(int flags,
int col = mouse_col;
int grid = mouse_grid;
int mouse_char;
+ int fdc = 0;
mouse_past_bottom = false;
mouse_past_eol = false;
@@ -92,10 +94,24 @@ int jump_to_mouse(int flags,
retnomove:
// before moving the cursor for a left click which is NOT in a status
// line, stop Visual mode
- if (on_status_line)
+ if (on_status_line) {
return IN_STATUS_LINE;
- if (on_sep_line)
+ }
+ if (on_sep_line) {
return IN_SEP_LINE;
+ }
+ if (in_winbar) {
+ // A quick second click may arrive as a double-click, but we use it
+ // as a second click in the WinBar.
+ if ((mod_mask & MOD_MASK_MULTI_CLICK) && !(flags & MOUSE_RELEASED)) {
+ wp = mouse_find_win(&grid, &row, &col);
+ if (wp == NULL) {
+ return IN_UNKNOWN;
+ }
+ winbar_click(wp, col);
+ }
+ return IN_OTHER_WIN | MOUSE_WINBAR;
+ }
if (flags & MOUSE_MAY_STOP_VIS) {
end_visual_mode();
redraw_curbuf_later(INVERTED); // delete the inversion
@@ -109,12 +125,12 @@ retnomove:
if (flags & MOUSE_SETPOS)
goto retnomove; // ugly goto...
- // Remember the character under the mouse, it might be a '-' or '+' in the
- // fold column. NB: only works for ASCII chars!
+ // Remember the character under the mouse, might be one of foldclose or
+ // foldopen fillchars in the fold column.
if (row >= 0 && row < Rows && col >= 0 && col <= Columns
&& default_grid.chars != NULL) {
- mouse_char = default_grid.chars[default_grid.line_offset[row]
- + (unsigned)col][0];
+ mouse_char = utf_ptr2char(default_grid.chars[default_grid.line_offset[row]
+ + (unsigned)col]);
} else {
mouse_char = ' ';
}
@@ -131,7 +147,18 @@ retnomove:
if (wp == NULL) {
return IN_UNKNOWN;
}
+ fdc = win_fdccol_count(wp);
dragwin = NULL;
+
+ if (row == -1) {
+ // A click in the window toolbar does not enter another window or
+ // change Visual highlighting.
+ winbar_click(wp, col);
+ in_winbar = true;
+ return IN_OTHER_WIN | MOUSE_WINBAR;
+ }
+ in_winbar = false;
+
// winpos and height may change in win_enter()!
if (grid == DEFAULT_GRID_HANDLE && row >= wp->w_height) {
// In (or below) status line
@@ -165,9 +192,8 @@ retnomove:
|| (!on_status_line
&& !on_sep_line
&& (wp->w_p_rl
- ? col < wp->w_width_inner - wp->w_p_fdc
- : col >= wp->w_p_fdc + (cmdwin_type == 0 && wp == curwin
- ? 0 : 1))
+ ? col < wp->w_width_inner - fdc
+ : col >= fdc + (cmdwin_type == 0 && wp == curwin ? 0 : 1))
&& (flags & MOUSE_MAY_STOP_VIS)))) {
end_visual_mode();
redraw_curbuf_later(INVERTED); // delete the inversion
@@ -222,6 +248,9 @@ retnomove:
did_drag |= count;
}
return IN_SEP_LINE; // Cursor didn't move
+ } else if (in_winbar) {
+ // After a click on the window toolbar don't start Visual mode.
+ return IN_OTHER_WIN | MOUSE_WINBAR;
} else {
// keep_window_focus must be true
// before moving the cursor for a left click, stop Visual mode
@@ -305,8 +334,8 @@ retnomove:
}
// Check for position outside of the fold column.
- if (curwin->w_p_rl ? col < curwin->w_width_inner - curwin->w_p_fdc :
- col >= curwin->w_p_fdc + (cmdwin_type == 0 ? 0 : 1)) {
+ if (curwin->w_p_rl ? col < curwin->w_width_inner - fdc :
+ col >= fdc + (cmdwin_type == 0 ? 0 : 1)) {
mouse_char = ' ';
}
@@ -470,6 +499,7 @@ win_T *mouse_find_win(int *gridp, int *rowp, int *colp)
// exist.
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (wp == fp->fr_win) {
+ *rowp -= wp->w_winbar_height;
return wp;
}
}
diff --git a/src/nvim/mouse.h b/src/nvim/mouse.h
index 0149f7c7c0..6c5bc5dc0e 100644
--- a/src/nvim/mouse.h
+++ b/src/nvim/mouse.h
@@ -16,6 +16,7 @@
#define CURSOR_MOVED 0x100
#define MOUSE_FOLD_CLOSE 0x200 // clicked on '-' in fold column
#define MOUSE_FOLD_OPEN 0x400 // clicked on '+' in fold column
+#define MOUSE_WINBAR 0x800 // in window toolbar
// flags for jump_to_mouse()
#define MOUSE_FOCUS 0x01 // need to stay in this window
diff --git a/src/nvim/move.c b/src/nvim/move.c
index 3ae4f32a83..d4f82bc601 100644
--- a/src/nvim/move.c
+++ b/src/nvim/move.c
@@ -86,6 +86,7 @@ static void comp_botline(win_T *wp)
/* wp->w_botline is the line that is just below the window */
wp->w_botline = lnum;
wp->w_valid |= VALID_BOTLINE|VALID_BOTLINE_AP;
+ wp->w_viewport_invalid = true;
set_empty_rows(wp, done);
@@ -142,7 +143,8 @@ void update_topline(void)
int old_topfill;
bool check_topline = false;
bool check_botline = false;
- long save_so = p_so;
+ long *so_ptr = curwin->w_p_so >= 0 ? &curwin->w_p_so : &p_so;
+ long save_so = *so_ptr;
// If there is no valid screen and when the window height is zero just use
// the cursor line.
@@ -150,6 +152,7 @@ void update_topline(void)
curwin->w_topline = curwin->w_cursor.lnum;
curwin->w_botline = curwin->w_topline;
curwin->w_valid |= VALID_BOTLINE|VALID_BOTLINE_AP;
+ curwin->w_viewport_invalid = true;
curwin->w_scbind_pos = 1;
return;
}
@@ -158,9 +161,10 @@ void update_topline(void)
if (curwin->w_valid & VALID_TOPLINE)
return;
- /* When dragging with the mouse, don't scroll that quickly */
- if (mouse_dragging > 0)
- p_so = mouse_dragging - 1;
+ // When dragging with the mouse, don't scroll that quickly
+ if (mouse_dragging > 0) {
+ *so_ptr = mouse_dragging - 1;
+ }
old_topline = curwin->w_topline;
old_topfill = curwin->w_topfill;
@@ -173,6 +177,7 @@ void update_topline(void)
curwin->w_topline = 1;
curwin->w_botline = 2;
curwin->w_valid |= VALID_BOTLINE|VALID_BOTLINE_AP;
+ curwin->w_viewport_invalid = true;
curwin->w_scbind_pos = 1;
}
/*
@@ -206,15 +211,17 @@ void update_topline(void)
* scrolled). */
n = 0;
for (linenr_T lnum = curwin->w_cursor.lnum;
- lnum < curwin->w_topline + p_so; ++lnum) {
- ++n;
- /* stop at end of file or when we know we are far off */
- if (lnum >= curbuf->b_ml.ml_line_count || n >= halfheight)
+ lnum < curwin->w_topline + *so_ptr; lnum++) {
+ n++;
+ // stop at end of file or when we know we are far off
+ if (lnum >= curbuf->b_ml.ml_line_count || n >= halfheight) {
break;
+ }
(void)hasFolding(lnum, NULL, &lnum);
}
- } else
- n = curwin->w_topline + p_so - curwin->w_cursor.lnum;
+ } else {
+ n = curwin->w_topline + *so_ptr - curwin->w_cursor.lnum;
+ }
/* If we weren't very close to begin with, we scroll to put the
* cursor in the middle of the window. Otherwise put the cursor
@@ -247,7 +254,7 @@ void update_topline(void)
if (curwin->w_botline <= curbuf->b_ml.ml_line_count) {
if (curwin->w_cursor.lnum < curwin->w_botline) {
if (((long)curwin->w_cursor.lnum
- >= (long)curwin->w_botline - p_so
+ >= (long)curwin->w_botline - *so_ptr
|| hasAnyFolding(curwin)
)) {
lineoff_T loff;
@@ -266,13 +273,15 @@ void update_topline(void)
&& (loff.lnum + 1 < curwin->w_botline || loff.fill == 0)
) {
n += loff.height;
- if (n >= p_so)
+ if (n >= *so_ptr) {
break;
+ }
botline_forw(&loff);
}
- if (n >= p_so)
- /* sufficient context, no need to scroll */
+ if (n >= *so_ptr) {
+ // sufficient context, no need to scroll
check_botline = false;
+ }
} else {
/* sufficient context, no need to scroll */
check_botline = false;
@@ -285,7 +294,7 @@ void update_topline(void)
* botline - p_so (approximation of how much will be
* scrolled). */
for (linenr_T lnum = curwin->w_cursor.lnum;
- lnum >= curwin->w_botline - p_so; lnum--) {
+ lnum >= curwin->w_botline - *so_ptr; lnum--) {
line_count++;
// stop at end of file or when we know we are far off
if (lnum <= 0 || line_count > curwin->w_height_inner + 1) {
@@ -295,7 +304,7 @@ void update_topline(void)
}
} else
line_count = curwin->w_cursor.lnum - curwin->w_botline
- + 1 + p_so;
+ + 1 + *so_ptr;
if (line_count <= curwin->w_height_inner + 1) {
scroll_cursor_bot(scrolljump_value(), false);
} else {
@@ -305,6 +314,7 @@ void update_topline(void)
}
}
curwin->w_valid |= VALID_TOPLINE;
+ curwin->w_viewport_invalid = true;
win_check_anchored_floats(curwin);
/*
@@ -324,7 +334,7 @@ void update_topline(void)
validate_cursor();
}
- p_so = save_so;
+ *so_ptr = save_so;
}
/*
@@ -356,25 +366,28 @@ static int scrolljump_value(void)
*/
static bool check_top_offset(void)
{
- if (curwin->w_cursor.lnum < curwin->w_topline + p_so
+ long so = get_scrolloff_value();
+ if (curwin->w_cursor.lnum < curwin->w_topline + so
|| hasAnyFolding(curwin)
) {
lineoff_T loff;
loff.lnum = curwin->w_cursor.lnum;
loff.fill = 0;
- int n = curwin->w_topfill; /* always have this context */
- /* Count the visible screen lines above the cursor line. */
- while (n < p_so) {
+ int n = curwin->w_topfill; // always have this context
+ // Count the visible screen lines above the cursor line.
+ while (n < so) {
topline_back(&loff);
- /* Stop when included a line above the window. */
+ // Stop when included a line above the window.
if (loff.lnum < curwin->w_topline
|| (loff.lnum == curwin->w_topline && loff.fill > 0)
- )
+ ) {
break;
+ }
n += loff.height;
}
- if (n < p_so)
+ if (n < so) {
return true;
+ }
}
return false;
}
@@ -398,6 +411,7 @@ void check_cursor_moved(win_T *wp)
|VALID_CHEIGHT|VALID_CROW|VALID_TOPLINE);
wp->w_valid_cursor = wp->w_cursor;
wp->w_valid_leftcol = wp->w_leftcol;
+ wp->w_viewport_invalid = true;
} else if (wp->w_cursor.col != wp->w_valid_cursor.col
|| wp->w_leftcol != wp->w_valid_leftcol
|| wp->w_cursor.coladd != wp->w_valid_cursor.coladd
@@ -406,6 +420,7 @@ void check_cursor_moved(win_T *wp)
wp->w_valid_cursor.col = wp->w_cursor.col;
wp->w_valid_leftcol = wp->w_leftcol;
wp->w_valid_cursor.coladd = wp->w_cursor.coladd;
+ wp->w_viewport_invalid = true;
}
}
@@ -674,7 +689,7 @@ int win_col_off(win_T *wp)
{
return ((wp->w_p_nu || wp->w_p_rnu) ? number_width(wp) + 1 : 0)
+ (cmdwin_type == 0 || wp != curwin ? 0 : 1)
- + (int)wp->w_p_fdc
+ + win_fdccol_count(wp)
+ (win_signcol_count(wp) * win_signcol_width(wp));
}
@@ -714,6 +729,8 @@ void curs_columns(
colnr_T startcol;
colnr_T endcol;
colnr_T prev_skipcol;
+ long so = get_scrolloff_value();
+ long siso = get_sidescrolloff_value();
/*
* First make sure that w_topline is valid (after moving the cursor).
@@ -785,10 +802,10 @@ void curs_columns(
* If we get closer to the edge than 'sidescrolloff', scroll a little
* extra
*/
- assert(p_siso <= INT_MAX);
- int off_left = startcol - curwin->w_leftcol - (int)p_siso;
+ assert(siso <= INT_MAX);
+ int off_left = startcol - curwin->w_leftcol - (int)siso;
int off_right =
- endcol - curwin->w_leftcol - curwin->w_width_inner + (int)p_siso + 1;
+ endcol - curwin->w_leftcol - curwin->w_width_inner + (int)siso + 1;
if (off_left < 0 || off_right > 0) {
int diff = (off_left < 0) ? -off_left: off_right;
@@ -834,7 +851,7 @@ void curs_columns(
int plines = 0;
if ((curwin->w_wrow >= curwin->w_height_inner
|| ((prev_skipcol > 0
- || curwin->w_wrow + p_so >= curwin->w_height_inner)
+ || curwin->w_wrow + so >= curwin->w_height_inner)
&& (plines =
plines_win_nofill(curwin, curwin->w_cursor.lnum, false)) - 1
>= curwin->w_height_inner))
@@ -850,17 +867,18 @@ void curs_columns(
* 2: Less than "p_so" lines below
* 3: both of them */
extra = 0;
- if (curwin->w_skipcol + p_so * width > curwin->w_virtcol)
+ if (curwin->w_skipcol + so * width > curwin->w_virtcol) {
extra = 1;
- /* Compute last display line of the buffer line that we want at the
- * bottom of the window. */
+ }
+ // Compute last display line of the buffer line that we want at the
+ // bottom of the window.
if (plines == 0) {
plines = plines_win(curwin, curwin->w_cursor.lnum, false);
}
plines--;
- if (plines > curwin->w_wrow + p_so) {
- assert(p_so <= INT_MAX);
- n = curwin->w_wrow + (int)p_so;
+ if (plines > curwin->w_wrow + so) {
+ assert(so <= INT_MAX);
+ n = curwin->w_wrow + (int)so;
} else {
n = plines;
}
@@ -868,7 +886,7 @@ void curs_columns(
extra += 2;
}
- if (extra == 3 || plines < p_so * 2) {
+ if (extra == 3 || plines < so * 2) {
// not enough room for 'scrolloff', put cursor in the middle
n = curwin->w_virtcol / width;
if (n > curwin->w_height_inner / 2) {
@@ -882,9 +900,9 @@ void curs_columns(
}
curwin->w_skipcol = n * width;
} else if (extra == 1) {
- /* less then 'scrolloff' lines above, decrease skipcol */
- assert(p_so <= INT_MAX);
- extra = (curwin->w_skipcol + (int)p_so * width - curwin->w_virtcol
+ // less then 'scrolloff' lines above, decrease skipcol
+ assert(so <= INT_MAX);
+ extra = (curwin->w_skipcol + (int)so * width - curwin->w_virtcol
+ width - 1) / width;
if (extra > 0) {
if ((colnr_T)(extra * width) > curwin->w_skipcol)
@@ -1206,7 +1224,7 @@ void scrolldown_clamp(void)
end_row += curwin->w_cline_height - 1 -
curwin->w_virtcol / curwin->w_width_inner;
}
- if (end_row < curwin->w_height_inner - p_so) {
+ if (end_row < curwin->w_height_inner - get_scrolloff_value()) {
if (can_fill) {
++curwin->w_topfill;
check_topfill(curwin, true);
@@ -1246,14 +1264,14 @@ void scrollup_clamp(void)
validate_virtcol();
start_row -= curwin->w_virtcol / curwin->w_width_inner;
}
- if (start_row >= p_so) {
- if (curwin->w_topfill > 0)
- --curwin->w_topfill;
- else {
+ if (start_row >= get_scrolloff_value()) {
+ if (curwin->w_topfill > 0) {
+ curwin->w_topfill--;
+ } else {
(void)hasFolding(curwin->w_topline, NULL, &curwin->w_topline);
- ++curwin->w_topline;
+ curwin->w_topline++;
}
- ++curwin->w_botline; /* approximate w_botline */
+ curwin->w_botline++; // approximate w_botline
curwin->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE);
}
}
@@ -1349,8 +1367,7 @@ void scroll_cursor_top(int min_scroll, int always)
linenr_T old_topline = curwin->w_topline;
linenr_T old_topfill = curwin->w_topfill;
linenr_T new_topline;
- assert(p_so <= INT_MAX);
- int off = (int)p_so;
+ int off = (int)get_scrolloff_value();
if (mouse_dragging > 0)
off = mouse_dragging - 1;
@@ -1447,6 +1464,7 @@ void scroll_cursor_top(int min_scroll, int always)
curwin->w_valid &=
~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP);
curwin->w_valid |= VALID_TOPLINE;
+ curwin->w_viewport_invalid = true;
}
}
@@ -1492,7 +1510,8 @@ void scroll_cursor_bot(int min_scroll, int set_topbot)
linenr_T old_botline = curwin->w_botline;
int old_valid = curwin->w_valid;
int old_empty_rows = curwin->w_empty_rows;
- linenr_T cln = curwin->w_cursor.lnum; /* Cursor Line Number */
+ linenr_T cln = curwin->w_cursor.lnum; // Cursor Line Number
+ long so = get_scrolloff_value();
if (set_topbot) {
used = 0;
@@ -1551,7 +1570,7 @@ void scroll_cursor_bot(int min_scroll, int set_topbot)
/* Stop when scrolled nothing or at least "min_scroll", found "extra"
* context for 'scrolloff' and counted all lines below the window. */
if ((((scrolled <= 0 || scrolled >= min_scroll)
- && extra >= (mouse_dragging > 0 ? mouse_dragging - 1 : p_so))
+ && extra >= (mouse_dragging > 0 ? mouse_dragging - 1 : so))
|| boff.lnum + 1 > curbuf->b_ml.ml_line_count)
&& loff.lnum <= curwin->w_botline
&& (loff.lnum < curwin->w_botline
@@ -1589,7 +1608,7 @@ void scroll_cursor_bot(int min_scroll, int set_topbot)
if (used > curwin->w_height_inner) {
break;
}
- if (extra < (mouse_dragging > 0 ? mouse_dragging - 1 : p_so)
+ if (extra < (mouse_dragging > 0 ? mouse_dragging - 1 : so)
|| scrolled < min_scroll) {
extra += boff.height;
if (boff.lnum >= curwin->w_botline
@@ -1650,6 +1669,7 @@ void scroll_cursor_bot(int min_scroll, int set_topbot)
curwin->w_valid = old_valid;
}
curwin->w_valid |= VALID_TOPLINE;
+ curwin->w_viewport_invalid = true;
}
/// Recompute topline to put the cursor halfway across the window
@@ -1724,9 +1744,8 @@ void cursor_correct(void)
* How many lines we would like to have above/below the cursor depends on
* whether the first/last line of the file is on screen.
*/
- assert(p_so <= INT_MAX);
- int above_wanted = (int)p_so;
- int below_wanted = (int)p_so;
+ int above_wanted = (int)get_scrolloff_value();
+ int below_wanted = (int)get_scrolloff_value();
if (mouse_dragging > 0) {
above_wanted = mouse_dragging - 1;
below_wanted = mouse_dragging - 1;
@@ -1807,6 +1826,7 @@ void cursor_correct(void)
}
}
curwin->w_valid |= VALID_TOPLINE;
+ curwin->w_viewport_invalid = true;
}
@@ -1821,6 +1841,7 @@ int onepage(Direction dir, long count)
int retval = OK;
lineoff_T loff;
linenr_T old_topline = curwin->w_topline;
+ long so = get_scrolloff_value();
if (curbuf->b_ml.ml_line_count == 1) { /* nothing to do */
beep_flush();
@@ -1836,7 +1857,7 @@ int onepage(Direction dir, long count)
* last line.
*/
if (dir == FORWARD
- ? ((curwin->w_topline >= curbuf->b_ml.ml_line_count - p_so)
+ ? ((curwin->w_topline >= curbuf->b_ml.ml_line_count - so)
&& curwin->w_botline > curbuf->b_ml.ml_line_count)
: (curwin->w_topline == 1
&& curwin->w_topfill ==
diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c
index 0c874d7922..92ca29209e 100644
--- a/src/nvim/msgpack_rpc/channel.c
+++ b/src/nvim/msgpack_rpc/channel.c
@@ -564,7 +564,7 @@ void rpc_close(Channel *channel)
static void exit_event(void **argv)
{
if (!exiting) {
- mch_exit(0);
+ os_exit(0);
}
}
diff --git a/src/nvim/msgpack_rpc/channel.h b/src/nvim/msgpack_rpc/channel.h
index 9ff5abdc5f..90e1c7d48b 100644
--- a/src/nvim/msgpack_rpc/channel.h
+++ b/src/nvim/msgpack_rpc/channel.h
@@ -15,7 +15,7 @@
/// HACK: os/input.c drains this queue immediately before blocking for input.
/// Events on this queue are async-safe, but they need the resolved state
/// of os_inchar(), so they are processed "just-in-time".
-MultiQueue *ch_before_blocking_events;
+EXTERN MultiQueue *ch_before_blocking_events INIT(= NULL);
#ifdef INCLUDE_GENERATED_DECLARATIONS
diff --git a/src/nvim/msgpack_rpc/server.c b/src/nvim/msgpack_rpc/server.c
index 6168f097a7..062ea784ca 100644
--- a/src/nvim/msgpack_rpc/server.c
+++ b/src/nvim/msgpack_rpc/server.c
@@ -151,7 +151,7 @@ int server_start(const char *endpoint)
result = socket_watcher_start(watcher, MAX_CONNECTIONS, connection_cb);
if (result < 0) {
- WLOG("Failed to start server: %s", uv_strerror(result));
+ WLOG("Failed to start server: %s: %s", uv_strerror(result), watcher->addr);
socket_watcher_close(watcher, free_server);
return result;
}
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index dac4c8f527..87d687198d 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -24,7 +24,7 @@
#include "nvim/diff.h"
#include "nvim/digraph.h"
#include "nvim/edit.h"
-#include "nvim/eval.h"
+#include "nvim/eval/userfunc.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds2.h"
#include "nvim/ex_docmd.h"
@@ -615,7 +615,9 @@ static void normal_redraw_mode_message(NormalState *s)
kmsg = keep_msg;
keep_msg = NULL;
- // showmode() will clear keep_msg, but we want to use it anyway
+ // Showmode() will clear keep_msg, but we want to use it anyway.
+ // First update w_topline.
+ setcursor();
update_screen(0);
// now reset it, otherwise it's put in the history again
keep_msg = kmsg;
@@ -623,6 +625,7 @@ static void normal_redraw_mode_message(NormalState *s)
xfree(kmsg);
}
setcursor();
+ ui_cursor_shape(); // show different cursor shape
ui_flush();
if (msg_scroll || emsg_on_display) {
os_delay(1000L, true); // wait at least one second
@@ -1257,6 +1260,8 @@ static void normal_redraw(NormalState *s)
maketitle();
}
+ curbuf->b_last_used = time(NULL);
+
// Display message after redraw. If an external message is still visible,
// it contains the kept message already.
if (keep_msg != NULL && !msg_ext_is_visible()) {
@@ -1964,8 +1969,8 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
break;
case OP_FOLD:
- VIsual_reselect = false; /* don't reselect now */
- foldCreate(oap->start.lnum, oap->end.lnum);
+ VIsual_reselect = false; // don't reselect now
+ foldCreate(curwin, oap->start.lnum, oap->end.lnum);
break;
case OP_FOLDOPEN:
@@ -1983,9 +1988,9 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
case OP_FOLDDEL:
case OP_FOLDDELREC:
- VIsual_reselect = false; /* don't reselect now */
- deleteFold(oap->start.lnum, oap->end.lnum,
- oap->op_type == OP_FOLDDELREC, oap->is_VIsual);
+ VIsual_reselect = false; // don't reselect now
+ deleteFold(curwin, oap->start.lnum, oap->end.lnum,
+ oap->op_type == OP_FOLDDELREC, oap->is_VIsual);
break;
case OP_NR_ADD:
@@ -2556,7 +2561,14 @@ do_mouse (
* JUMP!
*/
jump_flags = jump_to_mouse(jump_flags,
- oap == NULL ? NULL : &(oap->inclusive), which_button);
+ oap == NULL ? NULL : &(oap->inclusive),
+ which_button);
+
+ // A click in the window toolbar has no side effects.
+ if (jump_flags & MOUSE_WINBAR) {
+ return false;
+ }
+
moved = (jump_flags & CURSOR_MOVED);
in_status_line = (jump_flags & IN_STATUS_LINE);
in_sep_line = (jump_flags & IN_SEP_LINE);
@@ -2584,12 +2596,13 @@ do_mouse (
/* Set global flag that we are extending the Visual area with mouse
* dragging; temporarily minimize 'scrolloff'. */
- if (VIsual_active && is_drag && p_so) {
- /* In the very first line, allow scrolling one line */
- if (mouse_row == 0)
+ if (VIsual_active && is_drag && get_scrolloff_value()) {
+ // In the very first line, allow scrolling one line
+ if (mouse_row == 0) {
mouse_dragging = 2;
- else
+ } else {
mouse_dragging = 1;
+ }
}
/* When dragging the mouse above the window, scroll down. */
@@ -3642,7 +3655,9 @@ static void nv_help(cmdarg_T *cap)
*/
static void nv_addsub(cmdarg_T *cap)
{
- if (!VIsual_active && cap->oap->op_type == OP_NOP) {
+ if (bt_prompt(curbuf) && !prompt_curpos_editable()) {
+ clearopbeep(cap->oap);
+ } else if (!VIsual_active && cap->oap->op_type == OP_NOP) {
prep_redo_cmd(cap);
cap->oap->op_type = cap->cmdchar == Ctrl_A ? OP_NR_ADD : OP_NR_SUB;
op_addsub(cap->oap, cap->count1, cap->arg);
@@ -4087,9 +4102,9 @@ void scroll_redraw(int up, long count)
scrollup(count, true);
else
scrolldown(count, true);
- if (p_so) {
- /* Adjust the cursor position for 'scrolloff'. Mark w_topline as
- * valid, otherwise the screen jumps back at the end of the file. */
+ if (get_scrolloff_value()) {
+ // Adjust the cursor position for 'scrolloff'. Mark w_topline as
+ // valid, otherwise the screen jumps back at the end of the file.
cursor_correct();
check_cursor_moved(curwin);
curwin->w_valid |= VALID_TOPLINE;
@@ -4118,6 +4133,7 @@ void scroll_redraw(int up, long count)
}
if (curwin->w_cursor.lnum != prev_lnum)
coladvance(curwin->w_curswant);
+ curwin->w_viewport_invalid = true;
redraw_later(VALID);
}
@@ -4133,8 +4149,8 @@ static void nv_zet(cmdarg_T *cap)
int old_fen = curwin->w_p_fen;
bool undo = false;
- assert(p_siso <= INT_MAX);
- int l_p_siso = (int)p_siso;
+ int l_p_siso = (int)get_sidescrolloff_value();
+ assert(l_p_siso <= INT_MAX);
if (ascii_isdigit(nchar)) {
/*
@@ -4338,11 +4354,12 @@ dozet:
/* "zD": delete fold at cursor recursively */
case 'd':
case 'D': if (foldManualAllowed(false)) {
- if (VIsual_active)
+ if (VIsual_active) {
nv_operator(cap);
- else
- deleteFold(curwin->w_cursor.lnum,
- curwin->w_cursor.lnum, nchar == 'D', false);
+ } else {
+ deleteFold(curwin, curwin->w_cursor.lnum,
+ curwin->w_cursor.lnum, nchar == 'D', false);
+ }
}
break;
@@ -4350,11 +4367,11 @@ dozet:
case 'E': if (foldmethodIsManual(curwin)) {
clearFolding(curwin);
changed_window_setting();
- } else if (foldmethodIsMarker(curwin))
- deleteFold((linenr_T)1, curbuf->b_ml.ml_line_count,
- true, false);
- else
+ } else if (foldmethodIsMarker(curwin)) {
+ deleteFold(curwin, (linenr_T)1, curbuf->b_ml.ml_line_count, true, false);
+ } else {
EMSG(_("E352: Cannot erase folds with current 'foldmethod'"));
+ }
break;
/* "zn": fold none: reset 'foldenable' */
@@ -4456,16 +4473,16 @@ dozet:
case 'r':
curwin->w_p_fdl += cap->count1;
{
- int d = getDeepestNesting();
+ int d = getDeepestNesting(curwin);
if (curwin->w_p_fdl >= d) {
curwin->w_p_fdl = d;
}
}
break;
- /* "zR": open all folds */
- case 'R': curwin->w_p_fdl = getDeepestNesting();
- old_fdl = -1; /* force an update */
+ case 'R': // "zR": open all folds
+ curwin->w_p_fdl = getDeepestNesting(curwin);
+ old_fdl = -1; // force an update
break;
case 'j': /* "zj" move to next fold downwards */
@@ -5239,6 +5256,13 @@ static void nv_down(cmdarg_T *cap)
// In the cmdline window a <CR> executes the command.
if (cmdwin_type != 0 && cap->cmdchar == CAR) {
cmdwin_result = CAR;
+ } else if (bt_prompt(curbuf) && cap->cmdchar == CAR
+ && curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count) {
+ // In a prompt buffer a <CR> in the last line invokes the callback.
+ invoke_prompt_callback();
+ if (restart_edit == 0) {
+ restart_edit = 'a';
+ }
} else {
cap->oap->motion_type = kMTLineWise;
if (cursor_down(cap->count1, cap->oap->op_type == OP_NOP) == false) {
@@ -5831,6 +5855,10 @@ static void nv_undo(cmdarg_T *cap)
static void nv_kundo(cmdarg_T *cap)
{
if (!checkclearopq(cap->oap)) {
+ if (bt_prompt(curbuf)) {
+ clearopbeep(cap->oap);
+ return;
+ }
u_undo((int)cap->count1);
curwin->w_set_curswant = true;
}
@@ -5844,8 +5872,13 @@ static void nv_replace(cmdarg_T *cap)
char_u *ptr;
int had_ctrl_v;
- if (checkclearop(cap->oap))
+ if (checkclearop(cap->oap)) {
+ return;
+ }
+ if (bt_prompt(curbuf) && !prompt_curpos_editable()) {
+ clearopbeep(cap->oap);
return;
+ }
/* get another character */
if (cap->nchar == Ctrl_V) {
@@ -6182,7 +6215,11 @@ static void v_visop(cmdarg_T *cap)
*/
static void nv_subst(cmdarg_T *cap)
{
- if (VIsual_active) { /* "vs" and "vS" are the same as "vc" */
+ if (bt_prompt(curbuf) && !prompt_curpos_editable()) {
+ clearopbeep(cap->oap);
+ return;
+ }
+ if (VIsual_active) { // "vs" and "vS" are the same as "vc"
if (cap->cmdchar == 'S') {
VIsual_mode_orig = VIsual_mode;
VIsual_mode = 'V';
@@ -7120,10 +7157,15 @@ static void nv_tilde(cmdarg_T *cap)
{
if (!p_to
&& !VIsual_active
- && cap->oap->op_type != OP_TILDE)
+ && cap->oap->op_type != OP_TILDE) {
+ if (bt_prompt(curbuf) && !prompt_curpos_editable()) {
+ clearopbeep(cap->oap);
+ return;
+ }
n_swapchar(cap);
- else
+ } else {
nv_operator(cap);
+ }
}
/*
@@ -7136,6 +7178,12 @@ static void nv_operator(cmdarg_T *cap)
op_type = get_op_type(cap->cmdchar, cap->nchar);
+ if (bt_prompt(curbuf) && op_is_change(op_type)
+ && !prompt_curpos_editable()) {
+ clearopbeep(cap->oap);
+ return;
+ }
+
if (op_type == cap->oap->op_type) /* double operator works on lines */
nv_lineop(cap);
else if (!checkclearop(cap->oap)) {
@@ -7796,8 +7844,11 @@ static void nv_put_opt(cmdarg_T *cap, bool fix_indent)
clearop(cap->oap);
assert(cap->opcount >= 0);
nv_diffgetput(true, (size_t)cap->opcount);
- } else
+ } else {
clearopbeep(cap->oap);
+ }
+ } else if (bt_prompt(curbuf) && !prompt_curpos_editable()) {
+ clearopbeep(cap->oap);
} else {
if (fix_indent) {
dir = (cap->cmdchar == ']' && cap->nchar == 'p')
@@ -7809,8 +7860,9 @@ static void nv_put_opt(cmdarg_T *cap, bool fix_indent)
? BACKWARD : FORWARD;
}
prep_redo_cmd(cap);
- if (cap->cmdchar == 'g')
+ if (cap->cmdchar == 'g') {
flags |= PUT_CURSEND;
+ }
if (VIsual_active) {
/* Putting in Visual mode: The put text replaces the selected
@@ -7916,10 +7968,14 @@ static void nv_open(cmdarg_T *cap)
clearop(cap->oap);
assert(cap->opcount >= 0);
nv_diffgetput(false, (size_t)cap->opcount);
- } else if (VIsual_active) /* switch start and end of visual */
+ } else if (VIsual_active) {
+ // switch start and end of visual/
v_swap_corners(cap->cmdchar);
- else
+ } else if (bt_prompt(curbuf)) {
+ clearopbeep(cap->oap);
+ } else {
n_opencmd(cap);
+ }
}
// Calculate start/end virtual columns for operating in block mode.
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index db5c98ed78..a70224f98b 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -89,6 +89,10 @@ struct block_def {
# include "ops.c.generated.h"
#endif
+// Flags for third item in "opchars".
+#define OPF_LINES 1 // operator always works on lines
+#define OPF_CHANGE 2 // operator changes text
+
/*
* The names of operators.
* IMPORTANT: Index must correspond with defines in vim.h!!!
@@ -96,36 +100,36 @@ struct block_def {
*/
static char opchars[][3] =
{
- { NUL, NUL, false }, // OP_NOP
- { 'd', NUL, false }, // OP_DELETE
- { 'y', NUL, false }, // OP_YANK
- { 'c', NUL, false }, // OP_CHANGE
- { '<', NUL, true }, // OP_LSHIFT
- { '>', NUL, true }, // OP_RSHIFT
- { '!', NUL, true }, // OP_FILTER
- { 'g', '~', false }, // OP_TILDE
- { '=', NUL, true }, // OP_INDENT
- { 'g', 'q', true }, // OP_FORMAT
- { ':', NUL, true }, // OP_COLON
- { 'g', 'U', false }, // OP_UPPER
- { 'g', 'u', false }, // OP_LOWER
- { 'J', NUL, true }, // DO_JOIN
- { 'g', 'J', true }, // DO_JOIN_NS
- { 'g', '?', false }, // OP_ROT13
- { 'r', NUL, false }, // OP_REPLACE
- { 'I', NUL, false }, // OP_INSERT
- { 'A', NUL, false }, // OP_APPEND
- { 'z', 'f', true }, // OP_FOLD
- { 'z', 'o', true }, // OP_FOLDOPEN
- { 'z', 'O', true }, // OP_FOLDOPENREC
- { 'z', 'c', true }, // OP_FOLDCLOSE
- { 'z', 'C', true }, // OP_FOLDCLOSEREC
- { 'z', 'd', true }, // OP_FOLDDEL
- { 'z', 'D', true }, // OP_FOLDDELREC
- { 'g', 'w', true }, // OP_FORMAT2
- { 'g', '@', false }, // OP_FUNCTION
- { Ctrl_A, NUL, false }, // OP_NR_ADD
- { Ctrl_X, NUL, false }, // OP_NR_SUB
+ { NUL, NUL, 0 }, // OP_NOP
+ { 'd', NUL, OPF_CHANGE }, // OP_DELETE
+ { 'y', NUL, 0 }, // OP_YANK
+ { 'c', NUL, OPF_CHANGE }, // OP_CHANGE
+ { '<', NUL, OPF_LINES | OPF_CHANGE }, // OP_LSHIFT
+ { '>', NUL, OPF_LINES | OPF_CHANGE }, // OP_RSHIFT
+ { '!', NUL, OPF_LINES | OPF_CHANGE }, // OP_FILTER
+ { 'g', '~', OPF_CHANGE }, // OP_TILDE
+ { '=', NUL, OPF_LINES | OPF_CHANGE }, // OP_INDENT
+ { 'g', 'q', OPF_LINES | OPF_CHANGE }, // OP_FORMAT
+ { ':', NUL, OPF_LINES }, // OP_COLON
+ { 'g', 'U', OPF_CHANGE }, // OP_UPPER
+ { 'g', 'u', OPF_CHANGE }, // OP_LOWER
+ { 'J', NUL, OPF_LINES | OPF_CHANGE }, // DO_JOIN
+ { 'g', 'J', OPF_LINES | OPF_CHANGE }, // DO_JOIN_NS
+ { 'g', '?', OPF_CHANGE }, // OP_ROT13
+ { 'r', NUL, OPF_CHANGE }, // OP_REPLACE
+ { 'I', NUL, OPF_CHANGE }, // OP_INSERT
+ { 'A', NUL, OPF_CHANGE }, // OP_APPEND
+ { 'z', 'f', OPF_LINES }, // OP_FOLD
+ { 'z', 'o', OPF_LINES }, // OP_FOLDOPEN
+ { 'z', 'O', OPF_LINES }, // OP_FOLDOPENREC
+ { 'z', 'c', OPF_LINES }, // OP_FOLDCLOSE
+ { 'z', 'C', OPF_LINES }, // OP_FOLDCLOSEREC
+ { 'z', 'd', OPF_LINES }, // OP_FOLDDEL
+ { 'z', 'D', OPF_LINES }, // OP_FOLDDELREC
+ { 'g', 'w', OPF_LINES | OPF_CHANGE }, // OP_FORMAT2
+ { 'g', '@', OPF_CHANGE }, // OP_FUNCTION
+ { Ctrl_A, NUL, OPF_CHANGE }, // OP_NR_ADD
+ { Ctrl_X, NUL, OPF_CHANGE }, // OP_NR_SUB
};
/*
@@ -169,7 +173,13 @@ int get_op_type(int char1, int char2)
*/
int op_on_lines(int op)
{
- return opchars[op][2];
+ return opchars[op][2] & OPF_LINES;
+}
+
+// Return TRUE if operator "op" changes text.
+int op_is_change(int op)
+{
+ return opchars[op][2] & OPF_CHANGE;
}
/*
@@ -221,8 +231,6 @@ void op_shift(oparg_T *oap, int curs_top, int amount)
++curwin->w_cursor.lnum;
}
- changed_lines(oap->start.lnum, 0, oap->end.lnum + 1, 0L, true);
-
if (oap->motion_type == kMTBlockWise) {
curwin->w_cursor.lnum = oap->start.lnum;
curwin->w_cursor.col = block_col;
@@ -253,7 +261,7 @@ void op_shift(oparg_T *oap, int curs_top, int amount)
sprintf((char *)IObuff, _("%" PRId64 " lines %sed %d times"),
(int64_t)oap->line_count, s, amount);
}
- msg(IObuff);
+ msg_attr_keep(IObuff, 0, true, false);
}
/*
@@ -262,8 +270,11 @@ void op_shift(oparg_T *oap, int curs_top, int amount)
curbuf->b_op_start = oap->start;
curbuf->b_op_end.lnum = oap->end.lnum;
curbuf->b_op_end.col = (colnr_T)STRLEN(ml_get(oap->end.lnum));
- if (curbuf->b_op_end.col > 0)
- --curbuf->b_op_end.col;
+ if (curbuf->b_op_end.col > 0) {
+ curbuf->b_op_end.col--;
+ }
+
+ changed_lines(oap->start.lnum, 0, oap->end.lnum + 1, 0L, true);
}
// Shift the current line one shiftwidth left (if left != 0) or right
@@ -831,6 +842,15 @@ static bool is_append_register(int regname)
return ASCII_ISUPPER(regname);
}
+/// @see get_yank_register
+/// @returns true when register should be inserted literally
+/// (selection or clipboard)
+static inline bool is_literal_register(int regname)
+ FUNC_ATTR_CONST
+{
+ return regname == '*' || regname == '+';
+}
+
/// Returns a copy of contents in register `name`
/// for use in do_put. Should be freed by caller.
yankreg_T *copy_register(int name)
@@ -1141,11 +1161,12 @@ static int put_in_typebuf(
*/
int insert_reg(
int regname,
- int literally /* insert literally, not as if typed */
+ bool literally_arg // insert literally, not as if typed
)
{
int retval = OK;
bool allocated;
+ const bool literally = literally_arg || is_literal_register(regname);
/*
* It is possible to get into an endless loop by having CTRL-R a in
@@ -1315,12 +1336,14 @@ bool get_spec_reg(
/// register contents will be interpreted as commands.
///
/// @param regname Register name.
-/// @param literally Insert text literally instead of "as typed".
+/// @param literally_arg Insert text literally instead of "as typed".
/// @param remcr When true, don't add CR characters.
///
/// @returns FAIL for failure, OK otherwise
-bool cmdline_paste_reg(int regname, bool literally, bool remcr)
+bool cmdline_paste_reg(int regname, bool literally_arg, bool remcr)
{
+ const bool literally = literally_arg || is_literal_register(regname);
+
yankreg_T *reg = get_yank_register(regname, YREG_PASTE);
if (reg->y_array == NULL)
return FAIL;
@@ -2523,7 +2546,7 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append)
case kMTCharWise:
{
colnr_T startcol = 0, endcol = MAXCOL;
- int is_oneChar = FALSE;
+ int is_oneChar = false;
colnr_T cs, ce;
p = ml_get(lnum);
bd.startspaces = 0;
@@ -2554,8 +2577,8 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append)
&& utf_head_off(p, p + endcol) == 0)) {
if (oap->start.lnum == oap->end.lnum
&& oap->start.col == oap->end.col) {
- /* Special case: inside a single char */
- is_oneChar = TRUE;
+ // Special case: inside a single char
+ is_oneChar = true;
bd.startspaces = oap->end.coladd
- oap->start.coladd + oap->inclusive;
endcol = startcol;
@@ -4414,8 +4437,8 @@ static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum,
bdp->textlen = 0;
bdp->start_vcol = 0;
bdp->end_vcol = 0;
- bdp->is_short = FALSE;
- bdp->is_oneChar = FALSE;
+ bdp->is_short = false;
+ bdp->is_oneChar = false;
bdp->pre_whitesp = 0;
bdp->pre_whitesp_c = 0;
bdp->end_char_vcols = 0;
@@ -4441,9 +4464,10 @@ static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum,
bdp->start_char_vcols = incr;
if (bdp->start_vcol < oap->start_vcol) { /* line too short */
bdp->end_vcol = bdp->start_vcol;
- bdp->is_short = TRUE;
- if (!is_del || oap->op_type == OP_APPEND)
+ bdp->is_short = true;
+ if (!is_del || oap->op_type == OP_APPEND) {
bdp->endspaces = oap->end_vcol - oap->start_vcol + 1;
+ }
} else {
/* notice: this converts partly selected Multibyte characters to
* spaces, too. */
@@ -4452,11 +4476,11 @@ static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum,
bdp->startspaces = bdp->start_char_vcols - bdp->startspaces;
pend = pstart;
bdp->end_vcol = bdp->start_vcol;
- if (bdp->end_vcol > oap->end_vcol) { /* it's all in one character */
- bdp->is_oneChar = TRUE;
- if (oap->op_type == OP_INSERT)
+ if (bdp->end_vcol > oap->end_vcol) { // it's all in one character
+ bdp->is_oneChar = true;
+ if (oap->op_type == OP_INSERT) {
bdp->endspaces = bdp->start_char_vcols - bdp->startspaces;
- else if (oap->op_type == OP_APPEND) {
+ } else if (oap->op_type == OP_APPEND) {
bdp->startspaces += oap->end_vcol - oap->start_vcol + 1;
bdp->endspaces = bdp->start_char_vcols - bdp->startspaces;
} else {
@@ -4481,17 +4505,16 @@ static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum,
if (bdp->end_vcol <= oap->end_vcol
&& (!is_del
|| oap->op_type == OP_APPEND
- || oap->op_type == OP_REPLACE)) { /* line too short */
- bdp->is_short = TRUE;
- /* Alternative: include spaces to fill up the block.
- * Disadvantage: can lead to trailing spaces when the line is
- * short where the text is put */
- /* if (!is_del || oap->op_type == OP_APPEND) */
- if (oap->op_type == OP_APPEND || virtual_op)
+ || oap->op_type == OP_REPLACE)) { // line too short
+ bdp->is_short = true;
+ // Alternative: include spaces to fill up the block.
+ // Disadvantage: can lead to trailing spaces when the line is
+ // short where the text is put
+ // if (!is_del || oap->op_type == OP_APPEND)
+ if (oap->op_type == OP_APPEND || virtual_op) {
bdp->endspaces = oap->end_vcol - bdp->end_vcol
+ oap->inclusive;
- else
- bdp->endspaces = 0; /* replace doesn't add characters */
+ }
} else if (bdp->end_vcol > oap->end_vcol) {
bdp->endspaces = bdp->end_vcol - oap->end_vcol - 1;
if (!is_del && bdp->endspaces) {
diff --git a/src/nvim/option.c b/src/nvim/option.c
index f03dcc2bf2..96f8e1529a 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -66,6 +66,7 @@
#include "nvim/path.h"
#include "nvim/popupmnu.h"
#include "nvim/regexp.h"
+#include "nvim/ex_session.h"
#include "nvim/screen.h"
#include "nvim/spell.h"
#include "nvim/spellfile.h"
@@ -298,7 +299,8 @@ static char *(p_scbopt_values[]) = { "ver", "hor", "jump", NULL };
static char *(p_debug_values[]) = { "msg", "throw", "beep", NULL };
static char *(p_ead_values[]) = { "both", "ver", "hor", NULL };
static char *(p_buftype_values[]) = { "nofile", "nowrite", "quickfix",
- "help", "acwrite", "terminal", NULL };
+ "help", "acwrite", "terminal",
+ "prompt", NULL };
static char *(p_bufhidden_values[]) = { "hide", "unload", "delete",
"wipe", NULL };
@@ -313,6 +315,9 @@ static char *(p_scl_values[]) = { "yes", "no", "auto", "auto:1", "auto:2",
"auto:3", "auto:4", "auto:5", "auto:6", "auto:7", "auto:8", "auto:9",
"yes:1", "yes:2", "yes:3", "yes:4", "yes:5", "yes:6", "yes:7", "yes:8",
"yes:9", NULL };
+static char *(p_fdc_values[]) = { "auto:1", "auto:2",
+ "auto:3", "auto:4", "auto:5", "auto:6", "auto:7", "auto:8", "auto:9",
+ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", NULL };
/// All possible flags for 'shm'.
static char_u SHM_ALL[] = {
@@ -497,6 +502,24 @@ static inline char *add_dir(char *dest, const char *const dir,
return dest;
}
+char *get_lib_dir(void)
+{
+ // TODO(bfredl): too fragile? Ideally default_lib_dir would be made empty
+ // in an appimage build
+ if (strlen(default_lib_dir) != 0
+ && os_isdir((const char_u *)default_lib_dir)) {
+ return xstrdup(default_lib_dir);
+ }
+
+ // Find library path relative to the nvim binary: ../lib/nvim/
+ char exe_name[MAXPATHL];
+ vim_get_prefix_from_exepath(exe_name);
+ if (append_path(exe_name, "lib" _PATHSEPSTR "nvim", MAXPATHL) == OK) {
+ return xstrdup(exe_name);
+ }
+ return NULL;
+}
+
/// Sets &runtimepath to default value.
///
/// Windows: Uses "…/nvim-data" for kXDGDataHome to avoid storing
@@ -507,6 +530,7 @@ static void set_runtimepath_default(void)
char *const data_home = stdpaths_get_xdg_var(kXDGDataHome);
char *const config_home = stdpaths_get_xdg_var(kXDGConfigHome);
char *const vimruntime = vim_getenv("VIMRUNTIME");
+ char *const libdir = get_lib_dir();
char *const data_dirs = stdpaths_get_xdg_var(kXDGDataDirs);
char *const config_dirs = stdpaths_get_xdg_var(kXDGConfigDirs);
#define SITE_SIZE (sizeof("site") - 1)
@@ -514,6 +538,7 @@ static void set_runtimepath_default(void)
size_t data_len = 0;
size_t config_len = 0;
size_t vimruntime_len = 0;
+ size_t libdir_len = 0;
if (data_home != NULL) {
data_len = strlen(data_home);
if (data_len != 0) {
@@ -543,6 +568,12 @@ static void set_runtimepath_default(void)
rtp_size += vimruntime_len + memcnt(vimruntime, ',', vimruntime_len) + 1;
}
}
+ if (libdir != NULL) {
+ libdir_len = strlen(libdir);
+ if (libdir_len != 0) {
+ rtp_size += libdir_len + memcnt(libdir, ',', libdir_len) + 1;
+ }
+ }
rtp_size += compute_double_colon_len(data_dirs, NVIM_SIZE + 1 + SITE_SIZE + 1,
AFTER_SIZE + 1);
rtp_size += compute_double_colon_len(config_dirs, NVIM_SIZE + 1,
@@ -561,6 +592,7 @@ static void set_runtimepath_default(void)
true);
rtp_cur = add_dir(rtp_cur, vimruntime, vimruntime_len, kXDGNone,
NULL, 0, NULL, 0);
+ rtp_cur = add_dir(rtp_cur, libdir, libdir_len, kXDGNone, NULL, 0, NULL, 0);
rtp_cur = add_colon_dirs(rtp_cur, data_dirs, "site", SITE_SIZE,
"after", AFTER_SIZE, false);
rtp_cur = add_dir(rtp_cur, data_home, data_len, kXDGDataHome,
@@ -582,15 +614,14 @@ static void set_runtimepath_default(void)
xfree(data_home);
xfree(config_home);
xfree(vimruntime);
+ xfree(libdir);
}
#undef NVIM_SIZE
-/*
- * Initialize the options, first part.
- *
- * Called only once from main(), just after creating the first buffer.
- */
+/// Initialize the options, first part.
+///
+/// Called only once from main(), just after creating the first buffer.
void set_init_1(void)
{
int opt_idx;
@@ -835,10 +866,8 @@ void set_init_1(void)
set_helplang_default(get_mess_lang());
}
-/*
- * Set an option to its default value.
- * This does not take care of side effects!
- */
+/// Set an option to its default value.
+/// This does not take care of side effects!
static void
set_option_default(
int opt_idx,
@@ -871,11 +900,19 @@ set_option_default(
if (options[opt_idx].indir == PV_SCROLL) {
win_comp_scroll(curwin);
} else {
- *(long *)varp = (long)(intptr_t)options[opt_idx].def_val[dvi];
+ long def_val = (long)options[opt_idx].def_val[dvi];
+ if ((long *)varp == &curwin->w_p_so
+ || (long *)varp == &curwin->w_p_siso) {
+ // 'scrolloff' and 'sidescrolloff' local values have a
+ // different default value than the global default.
+ *(long *)varp = -1;
+ } else {
+ *(long *)varp = def_val;
+ }
// May also set global value for local option.
if (both) {
*(long *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL) =
- *(long *)varp;
+ def_val;
}
}
} else { // P_BOOL
@@ -901,9 +938,7 @@ set_option_default(
set_option_sctx_idx(opt_idx, opt_flags, current_sctx);
}
-/*
- * Set all options (except terminal options) to their default value.
- */
+/// Set all options (except terminal options) to their default value.
static void
set_options_default(
int opt_flags // OPT_FREE, OPT_LOCAL and/or OPT_GLOBAL
@@ -946,10 +981,8 @@ static void set_string_default(const char *name, char *val, bool allocated)
}
}
-/*
- * Set the Vi-default value of a number option.
- * Used for 'lines' and 'columns'.
- */
+/// Set the Vi-default value of a number option.
+/// Used for 'lines' and 'columns'.
void set_number_default(char *name, long val)
{
int opt_idx;
@@ -1076,10 +1109,8 @@ void set_init_3(void)
set_title_defaults(); // 'title', 'icon'
}
-/*
- * When 'helplang' is still at its default value, set it to "lang".
- * Only the first two characters of "lang" are used.
- */
+/// When 'helplang' is still at its default value, set it to "lang".
+/// Only the first two characters of "lang" are used.
void set_helplang_default(const char *lang)
{
if (lang == NULL) {
@@ -1111,13 +1142,11 @@ void set_helplang_default(const char *lang)
}
-/*
- * 'title' and 'icon' only default to true if they have not been set or reset
- * in .vimrc and we can read the old value.
- * When 'title' and 'icon' have been reset in .vimrc, we won't even check if
- * they can be reset. This reduces startup time when using X on a remote
- * machine.
- */
+/// 'title' and 'icon' only default to true if they have not been set or reset
+/// in .vimrc and we can read the old value.
+/// When 'title' and 'icon' have been reset in .vimrc, we won't even check if
+/// they can be reset. This reduces startup time when using X on a remote
+/// machine.
void set_title_defaults(void)
{
int idx1;
@@ -1982,10 +2011,8 @@ static char_u *illegal_char(char_u *errbuf, size_t errbuflen, int c)
return errbuf;
}
-/*
- * Convert a key name or string into a key value.
- * Used for 'wildchar' and 'cedit' options.
- */
+/// Convert a key name or string into a key value.
+/// Used for 'wildchar' and 'cedit' options.
static int string_to_key(char_u *arg)
{
if (*arg == '<') {
@@ -1997,10 +2024,8 @@ static int string_to_key(char_u *arg)
return *arg;
}
-/*
- * Check value of 'cedit' and set cedit_key.
- * Returns NULL if value is OK, error message otherwise.
- */
+/// Check value of 'cedit' and set cedit_key.
+/// Returns NULL if value is OK, error message otherwise.
static char_u *check_cedit(void)
{
int n;
@@ -2084,13 +2109,11 @@ void set_options_bin(
}
}
-/*
- * Find the parameter represented by the given character (eg ', :, ", or /),
- * and return its associated value in the 'shada' string.
- * Only works for number parameters, not for 'r' or 'n'.
- * If the parameter is not specified in the string or there is no following
- * number, return -1.
- */
+/// Find the parameter represented by the given character (eg ', :, ", or /),
+/// and return its associated value in the 'shada' string.
+/// Only works for number parameters, not for 'r' or 'n'.
+/// If the parameter is not specified in the string or there is no following
+/// number, return -1.
int get_shada_parameter(int type)
{
char_u *p;
@@ -2102,11 +2125,9 @@ int get_shada_parameter(int type)
return -1;
}
-/*
- * Find the parameter represented by the given character (eg ''', ':', '"', or
- * '/') in the 'shada' option and return a pointer to the string after it.
- * Return NULL if the parameter is not specified in the string.
- */
+/// Find the parameter represented by the given character (eg ''', ':', '"', or
+/// '/') in the 'shada' option and return a pointer to the string after it.
+/// Return NULL if the parameter is not specified in the string.
char_u *find_shada_parameter(int type)
{
char_u *p;
@@ -2126,12 +2147,10 @@ char_u *find_shada_parameter(int type)
return NULL;
}
-/*
- * Expand environment variables for some string options.
- * These string options cannot be indirect!
- * If "val" is NULL expand the current value of the option.
- * Return pointer to NameBuff, or NULL when not expanded.
- */
+/// Expand environment variables for some string options.
+/// These string options cannot be indirect!
+/// If "val" is NULL expand the current value of the option.
+/// Return pointer to NameBuff, or NULL when not expanded.
static char_u *option_expand(int opt_idx, char_u *val)
{
// if option doesn't need expansion nothing to do
@@ -2215,9 +2234,7 @@ static void didset_options2(void)
check_opt_wim();
}
-/*
- * Check for string options that are NULL (normally only termcap options).
- */
+/// Check for string options that are NULL (normally only termcap options).
void check_options(void)
{
int opt_idx;
@@ -2229,9 +2246,7 @@ void check_options(void)
}
}
-/*
- * Check string options in a buffer for NULL value.
- */
+/// Check string options in a buffer for NULL value.
void check_buf_options(buf_T *buf)
{
check_string_option(&buf->b_p_bh);
@@ -2284,13 +2299,11 @@ void check_buf_options(buf_T *buf)
check_string_option(&buf->b_p_menc);
}
-/*
- * Free the string allocated for an option.
- * Checks for the string being empty_option. This may happen if we're out of
- * memory, vim_strsave() returned NULL, which was replaced by empty_option by
- * check_options().
- * Does NOT check for P_ALLOCED flag!
- */
+/// Free the string allocated for an option.
+/// Checks for the string being empty_option. This may happen if we're out of
+/// memory, vim_strsave() returned NULL, which was replaced by empty_option by
+/// check_options().
+/// Does NOT check for P_ALLOCED flag!
void free_string_option(char_u *p)
{
if (p != empty_option) {
@@ -2328,10 +2341,8 @@ int was_set_insecurely(char_u *opt, int opt_flags)
return -1;
}
-/*
- * Get a pointer to the flags used for the P_INSECURE flag of option
- * "opt_idx". For some local options a local flags field is used.
- */
+/// Get a pointer to the flags used for the P_INSECURE flag of option
+/// "opt_idx". For some local options a local flags field is used.
static uint32_t *insecure_flag(int opt_idx, int opt_flags)
{
if (opt_flags & OPT_LOCAL)
@@ -2349,9 +2360,7 @@ static uint32_t *insecure_flag(int opt_idx, int opt_flags)
}
-/*
- * Redraw the window title and/or tab page text later.
- */
+/// Redraw the window title and/or tab page text later.
static void redraw_titles(void)
{
need_maketitle = true;
@@ -2432,9 +2441,7 @@ set_string_option_direct(
}
}
-/*
- * Set global value for string option when it's a local option.
- */
+/// Set global value for string option when it's a local option.
static void
set_string_option_global(
int opt_idx, // option index
@@ -2509,12 +2516,41 @@ static char *set_string_option(const int opt_idx, const char *const value,
return r;
}
+/// Return true if "val" is a valid name: only consists of alphanumeric ASCII
+/// characters or characters in "allowed".
+static bool valid_name(const char_u *val, const char *allowed)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ for (const char_u *s = val; *s != NUL; s++) {
+ if (!ASCII_ISALNUM(*s)
+ && vim_strchr((const char_u *)allowed, *s) == NULL) {
+ return false;
+ }
+ }
+ return true;
+}
+
/// Return true if "val" is a valid 'filetype' name.
/// Also used for 'syntax' and 'keymap'.
-static bool valid_filetype(char_u *val)
+static bool valid_filetype(const char_u *val)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
- for (char_u *s = val; *s != NUL; s++) {
- if (!ASCII_ISALNUM(*s) && vim_strchr((char_u *)".-_", *s) == NULL) {
+ return valid_name(val, ".-_");
+}
+
+/// Return true if "val" is a valid 'spellang' value.
+bool valid_spellang(const char_u *val)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ return valid_name(val, ".-_,@");
+}
+
+/// Return true if "val" is a valid 'spellfile' value.
+static bool valid_spellfile(const char_u *val)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ for (const char_u *s = val; *s != NUL; s++) {
+ if (!vim_isfilec(*s) && *s != ',') {
return false;
}
}
@@ -3032,7 +3068,14 @@ ambw_end:
|| varp == &(curwin->w_s->b_p_spf)) {
// When 'spelllang' or 'spellfile' is set and there is a window for this
// buffer in which 'spell' is set load the wordlists.
- errmsg = did_set_spell_option(varp == &(curwin->w_s->b_p_spf));
+ const bool is_spellfile = varp == &(curwin->w_s->b_p_spf);
+
+ if ((is_spellfile && !valid_spellfile(*varp))
+ || (!is_spellfile && !valid_spellang(*varp))) {
+ errmsg = e_invarg;
+ } else {
+ errmsg = did_set_spell_option(is_spellfile);
+ }
} else if (varp == &(curwin->w_s->b_p_spc)) {
// When 'spellcapcheck' is set compile the regexp program.
errmsg = compile_cap_prog(curwin->w_s);
@@ -3133,6 +3176,11 @@ ambw_end:
if (check_opt_strings(*varp, p_scl_values, false) != OK) {
errmsg = e_invarg;
}
+ } else if (varp == &curwin->w_p_fdc || varp == &curwin->w_allbuf_opt.wo_fdc) {
+ // 'foldcolumn'
+ if (check_opt_strings(*varp, p_fdc_values, false) != OK) {
+ errmsg = e_invarg;
+ }
} else if (varp == &p_pt) {
// 'pastetoggle': translate key codes like in a mapping
if (*p_pt) {
@@ -3586,7 +3634,7 @@ static char_u *set_chars_option(win_T *wp, char_u **varp, bool set)
}
// first round: check for valid value, second round: assign values
- for (round = 0; round <= set ? 1 : 0; round++) {
+ for (round = 0; round <= (set ? 1 : 0); round++) {
if (round > 0) {
// After checking that the value is valid: set defaults
for (i = 0; i < entries; i++) {
@@ -3660,10 +3708,8 @@ static char_u *set_chars_option(win_T *wp, char_u **varp, bool set)
return NULL; // no error
}
-/*
- * Check validity of options with the 'statusline' format.
- * Return error message or NULL.
- */
+/// Check validity of options with the 'statusline' format.
+/// Return error message or NULL.
char_u *check_stl_option(char_u *s)
{
int itemcnt = 0;
@@ -3756,16 +3802,15 @@ static char_u *did_set_spell_option(bool is_spellfile)
return errmsg;
}
-/*
- * Set curbuf->b_cap_prog to the regexp program for 'spellcapcheck'.
- * Return error message when failed, NULL when OK.
- */
+/// Set curbuf->b_cap_prog to the regexp program for 'spellcapcheck'.
+/// Return error message when failed, NULL when OK.
static char_u *compile_cap_prog(synblock_T *synblock)
+ FUNC_ATTR_NONNULL_ALL
{
regprog_T *rp = synblock->b_cap_prog;
char_u *re;
- if (*synblock->b_p_spc == NUL) {
+ if (synblock->b_p_spc == NULL || *synblock->b_p_spc == NUL) {
synblock->b_cap_prog = NULL;
} else {
// Prepend a ^ so that we only match at one column
@@ -3805,7 +3850,8 @@ static bool parse_winhl_opt(win_T *wp)
w_hl_id_normal = hl_id;
} else {
for (hlf = 0; hlf < (int)HLF_COUNT; hlf++) {
- if (strncmp(hlf_names[hlf], p, nlen) == 0) {
+ if (strlen(hlf_names[hlf]) == nlen
+ && strncmp(hlf_names[hlf], p, nlen) == 0) {
w_hl_ids[hlf] = hl_id;
break;
}
@@ -4274,7 +4320,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value,
}
} else if (pp == &p_so) {
if (value < 0 && full_screen) {
- errmsg = e_scroll;
+ errmsg = e_positive;
}
} else if (pp == &p_siso) {
if (value < 0 && full_screen) {
@@ -4296,12 +4342,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value,
if (value < 0) {
errmsg = e_positive;
}
- } else if (pp == &curwin->w_p_fdc || pp == &curwin->w_allbuf_opt.wo_fdc) {
- if (value < 0) {
- errmsg = e_positive;
- } else if (value > 12) {
- errmsg = e_invarg;
- }
} else if (pp == &curwin->w_p_cole || pp == &curwin->w_allbuf_opt.wo_cole) {
if (value < 0) {
errmsg = e_positive;
@@ -4605,9 +4645,7 @@ static void trigger_optionsset_string(int opt_idx, int opt_flags,
}
}
-/*
- * Called after an option changed: check if something needs to be redrawn.
- */
+/// Called after an option changed: check if something needs to be redrawn.
static void check_redraw(uint32_t flags)
{
// Careful: P_RCLR and P_RALL are a combination of other P_ flags
@@ -5062,10 +5100,8 @@ static int find_key_option(const char_u *arg, bool has_lt)
return find_key_option_len(arg, STRLEN(arg), has_lt);
}
-/*
- * if 'all' == 0: show changed options
- * if 'all' == 1: show all normal options
- */
+/// if 'all' == 0: show changed options
+/// if 'all' == 1: show all normal options
static void
showoptions(
int all,
@@ -5214,10 +5250,8 @@ void ui_refresh_options(void)
}
}
-/*
- * showoneopt: show the value of one option
- * must not be called with a hidden option!
- */
+/// showoneopt: show the value of one option
+/// must not be called with a hidden option!
static void
showoneopt(
vimoption_T *p,
@@ -5253,28 +5287,26 @@ showoneopt(
info_message = false;
}
-/*
- * Write modified options as ":set" commands to a file.
- *
- * There are three values for "opt_flags":
- * OPT_GLOBAL: Write global option values and fresh values of
- * buffer-local options (used for start of a session
- * file).
- * OPT_GLOBAL + OPT_LOCAL: Idem, add fresh values of window-local options for
- * curwin (used for a vimrc file).
- * OPT_LOCAL: Write buffer-local option values for curbuf, fresh
- * and local values for window-local options of
- * curwin. Local values are also written when at the
- * default value, because a modeline or autocommand
- * may have set them when doing ":edit file" and the
- * user has set them back at the default or fresh
- * value.
- * When "local_only" is true, don't write fresh
- * values, only local values (for ":mkview").
- * (fresh value = value used for a new buffer or window for a local option).
- *
- * Return FAIL on error, OK otherwise.
- */
+/// Write modified options as ":set" commands to a file.
+///
+/// There are three values for "opt_flags":
+/// OPT_GLOBAL: Write global option values and fresh values of
+/// buffer-local options (used for start of a session
+/// file).
+/// OPT_GLOBAL + OPT_LOCAL: Idem, add fresh values of window-local options for
+/// curwin (used for a vimrc file).
+/// OPT_LOCAL: Write buffer-local option values for curbuf, fresh
+/// and local values for window-local options of
+/// curwin. Local values are also written when at the
+/// default value, because a modeline or autocommand
+/// may have set them when doing ":edit file" and the
+/// user has set them back at the default or fresh
+/// value.
+/// When "local_only" is true, don't write fresh
+/// values, only local values (for ":mkview").
+/// (fresh value = value used for a new buffer or window for a local option).
+///
+/// Return FAIL on error, OK otherwise.
int makeset(FILE *fd, int opt_flags, int local_only)
{
vimoption_T *p;
@@ -5370,8 +5402,9 @@ int makeset(FILE *fd, int opt_flags, int local_only)
do_endif = true;
}
if (put_setstring(fd, cmd, p->fullname, (char_u **)varp,
- (p->flags & P_EXPAND) != 0) == FAIL)
+ p->flags) == FAIL) {
return FAIL;
+ }
if (do_endif) {
if (put_line(fd, "endif") == FAIL) {
return FAIL;
@@ -5385,18 +5418,16 @@ int makeset(FILE *fd, int opt_flags, int local_only)
return OK;
}
-/*
- * Generate set commands for the local fold options only. Used when
- * 'sessionoptions' or 'viewoptions' contains "folds" but not "options".
- */
+/// Generate set commands for the local fold options only. Used when
+/// 'sessionoptions' or 'viewoptions' contains "folds" but not "options".
int makefoldset(FILE *fd)
{
- if (put_setstring(fd, "setlocal", "fdm", &curwin->w_p_fdm, false) == FAIL
- || put_setstring(fd, "setlocal", "fde", &curwin->w_p_fde, false)
+ if (put_setstring(fd, "setlocal", "fdm", &curwin->w_p_fdm, 0) == FAIL
+ || put_setstring(fd, "setlocal", "fde", &curwin->w_p_fde, 0)
== FAIL
- || put_setstring(fd, "setlocal", "fmr", &curwin->w_p_fmr, false)
+ || put_setstring(fd, "setlocal", "fmr", &curwin->w_p_fmr, 0)
== FAIL
- || put_setstring(fd, "setlocal", "fdi", &curwin->w_p_fdi, false)
+ || put_setstring(fd, "setlocal", "fdi", &curwin->w_p_fdi, 0)
== FAIL
|| put_setnum(fd, "setlocal", "fdl", &curwin->w_p_fdl) == FAIL
|| put_setnum(fd, "setlocal", "fml", &curwin->w_p_fml) == FAIL
@@ -5409,10 +5440,13 @@ int makefoldset(FILE *fd)
return OK;
}
-static int put_setstring(FILE *fd, char *cmd, char *name, char_u **valuep, int expand)
+static int put_setstring(FILE *fd, char *cmd, char *name,
+ char_u **valuep, uint64_t flags)
{
char_u *s;
- char_u *buf;
+ char_u *buf = NULL;
+ char_u *part = NULL;
+ char_u *p;
if (fprintf(fd, "%s %s=", cmd, name) < 0) {
return FAIL;
@@ -5430,9 +5464,46 @@ static int put_setstring(FILE *fd, char *cmd, char *name, char_u **valuep, int e
return FAIL;
}
}
- } else if (expand) {
- buf = xmalloc(MAXPATHL);
- home_replace(NULL, *valuep, buf, MAXPATHL, false);
+ } else if ((flags & P_EXPAND) != 0) {
+ size_t size = (size_t)STRLEN(*valuep) + 1;
+
+ // replace home directory in the whole option value into "buf"
+ buf = xmalloc(size);
+ if (buf == NULL) {
+ goto fail;
+ }
+ home_replace(NULL, *valuep, buf, size, false);
+
+ // If the option value is longer than MAXPATHL, we need to append
+ // earch comma separated part of the option sperately, so that it
+ // can be expanded when read back.
+ if (size >= MAXPATHL && (flags & P_COMMA) != 0
+ && vim_strchr(*valuep, ',') != NULL) {
+ part = xmalloc(size);
+ if (part == NULL) {
+ goto fail;
+ }
+
+ // write line break to clear the option, e.g. ':set rtp='
+ if (put_eol(fd) == FAIL) {
+ goto fail;
+ }
+ p = buf;
+ while (*p != NUL) {
+ // for each comma seperated option part, append value to
+ // the option, :set rtp+=value
+ if (fprintf(fd, "%s %s+=", cmd, name) < 0) {
+ goto fail;
+ }
+ (void)copy_option_part(&p, part, size, ",");
+ if (put_escstr(fd, part, 2) == FAIL || put_eol(fd) == FAIL) {
+ goto fail;
+ }
+ }
+ xfree(buf);
+ xfree(part);
+ return OK;
+ }
if (put_escstr(fd, buf, 2) == FAIL) {
xfree(buf);
return FAIL;
@@ -5446,6 +5517,10 @@ static int put_setstring(FILE *fd, char *cmd, char *name, char_u **valuep, int e
return FAIL;
}
return OK;
+fail:
+ xfree(buf);
+ xfree(part);
+ return FAIL;
}
static int put_setnum(FILE *fd, char *cmd, char *name, long *valuep)
@@ -5481,12 +5556,10 @@ static int put_setbool(FILE *fd, char *cmd, char *name, int value)
return OK;
}
-/*
- * Compute columns for ruler and shown command. 'sc_col' is also used to
- * decide what the maximum length of a message on the status line can be.
- * If there is a status line for the last window, 'sc_col' is independent
- * of 'ru_col'.
- */
+/// Compute columns for ruler and shown command. 'sc_col' is also used to
+/// decide what the maximum length of a message on the status line can be.
+/// If there is a status line for the last window, 'sc_col' is independent
+/// of 'ru_col'.
#define COL_RULER 17 // columns needed by standard ruler
@@ -5565,6 +5638,12 @@ void unset_global_local_option(char *name, void *from)
clear_string_option(&buf->b_p_tc);
buf->b_tc_flags = 0;
break;
+ case PV_SISO:
+ curwin->w_p_siso = -1;
+ break;
+ case PV_SO:
+ curwin->w_p_so = -1;
+ break;
case PV_DEF:
clear_string_option(&buf->b_p_def);
break;
@@ -5614,9 +5693,7 @@ void unset_global_local_option(char *name, void *from)
}
}
-/*
- * Get pointer to option variable, depending on local or global scope.
- */
+/// Get pointer to option variable, depending on local or global scope.
static char_u *get_varp_scope(vimoption_T *p, int opt_flags)
{
if ((opt_flags & OPT_GLOBAL) && p->indir != PV_NONE) {
@@ -5637,6 +5714,8 @@ static char_u *get_varp_scope(vimoption_T *p, int opt_flags)
case PV_AR: return (char_u *)&(curbuf->b_p_ar);
case PV_TAGS: return (char_u *)&(curbuf->b_p_tags);
case PV_TC: return (char_u *)&(curbuf->b_p_tc);
+ case PV_SISO: return (char_u *)&(curwin->w_p_siso);
+ case PV_SO: return (char_u *)&(curwin->w_p_so);
case PV_DEF: return (char_u *)&(curbuf->b_p_def);
case PV_INC: return (char_u *)&(curbuf->b_p_inc);
case PV_DICT: return (char_u *)&(curbuf->b_p_dict);
@@ -5655,9 +5734,7 @@ static char_u *get_varp_scope(vimoption_T *p, int opt_flags)
return get_varp(p);
}
-/*
- * Get pointer to option variable.
- */
+/// Get pointer to option variable.
static char_u *get_varp(vimoption_T *p)
{
// hidden option, always return NULL
@@ -5681,6 +5758,10 @@ static char_u *get_varp(vimoption_T *p)
? (char_u *)&(curbuf->b_p_tags) : p->var;
case PV_TC: return *curbuf->b_p_tc != NUL
? (char_u *)&(curbuf->b_p_tc) : p->var;
+ case PV_SISO: return curwin->w_p_siso >= 0
+ ? (char_u *)&(curwin->w_p_siso) : p->var;
+ case PV_SO: return curwin->w_p_so >= 0
+ ? (char_u *)&(curwin->w_p_so) : p->var;
case PV_BKC: return *curbuf->b_p_bkc != NUL
? (char_u *)&(curbuf->b_p_bkc) : p->var;
case PV_DEF: return *curbuf->b_p_def != NUL
@@ -5815,9 +5896,7 @@ static char_u *get_varp(vimoption_T *p)
return (char_u *)&(curbuf->b_p_wm);
}
-/*
- * Get the value of 'equalprg', either the buffer-local one or the global one.
- */
+/// Get the value of 'equalprg', either the buffer-local one or the global one.
char_u *get_equalprg(void)
{
if (*curbuf->b_p_ep == NUL) {
@@ -5826,22 +5905,18 @@ char_u *get_equalprg(void)
return curbuf->b_p_ep;
}
-/*
- * Copy options from one window to another.
- * Used when splitting a window.
- */
+/// Copy options from one window to another.
+/// Used when splitting a window.
void win_copy_options(win_T *wp_from, win_T *wp_to)
{
copy_winopt(&wp_from->w_onebuf_opt, &wp_to->w_onebuf_opt);
copy_winopt(&wp_from->w_allbuf_opt, &wp_to->w_allbuf_opt);
}
-/*
- * Copy the options from one winopt_T to another.
- * Doesn't free the old option values in "to", use clear_winopt() for that.
- * The 'scroll' option is not copied, because it depends on the window height.
- * The 'previewwindow' option is reset, there can be only one preview window.
- */
+/// Copy the options from one winopt_T to another.
+/// Doesn't free the old option values in "to", use clear_winopt() for that.
+/// The 'scroll' option is not copied, because it depends on the window height.
+/// The 'previewwindow' option is reset, there can be only one preview window.
void copy_winopt(winopt_T *from, winopt_T *to)
{
to->wo_arab = from->wo_arab;
@@ -5869,8 +5944,9 @@ void copy_winopt(winopt_T *from, winopt_T *to)
to->wo_diff_saved = from->wo_diff_saved;
to->wo_cocu = vim_strsave(from->wo_cocu);
to->wo_cole = from->wo_cole;
- to->wo_fdc = from->wo_fdc;
- to->wo_fdc_save = from->wo_fdc_save;
+ to->wo_fdc = vim_strsave(from->wo_fdc);
+ to->wo_fdc_save = from->wo_diff_saved
+ ? vim_strsave(from->wo_fdc_save) : empty_option;
to->wo_fen = from->wo_fen;
to->wo_fen_save = from->wo_fen_save;
to->wo_fdi = vim_strsave(from->wo_fdi);
@@ -5892,20 +5968,18 @@ void copy_winopt(winopt_T *from, winopt_T *to)
check_winopt(to); // don't want NULL pointers
}
-/*
- * Check string options in a window for a NULL value.
- */
+/// Check string options in a window for a NULL value.
void check_win_options(win_T *win)
{
check_winopt(&win->w_onebuf_opt);
check_winopt(&win->w_allbuf_opt);
}
-/*
- * Check for NULL pointers in a winopt_T and replace them with empty_option.
- */
+/// Check for NULL pointers in a winopt_T and replace them with empty_option.
static void check_winopt(winopt_T *wop)
{
+ check_string_option(&wop->wo_fdc);
+ check_string_option(&wop->wo_fdc_save);
check_string_option(&wop->wo_fdi);
check_string_option(&wop->wo_fdm);
check_string_option(&wop->wo_fdm_save);
@@ -5923,11 +5997,11 @@ static void check_winopt(winopt_T *wop)
check_string_option(&wop->wo_lcs);
}
-/*
- * Free the allocated memory inside a winopt_T.
- */
+/// Free the allocated memory inside a winopt_T.
void clear_winopt(winopt_T *wop)
{
+ clear_string_option(&wop->wo_fdc);
+ clear_string_option(&wop->wo_fdc_save);
clear_string_option(&wop->wo_fdi);
clear_string_option(&wop->wo_fdm);
clear_string_option(&wop->wo_fdm_save);
@@ -5956,15 +6030,13 @@ void didset_window_options(win_T *wp)
}
-/*
- * Copy global option values to local options for one buffer.
- * Used when creating a new buffer and sometimes when entering a buffer.
- * flags:
- * BCO_ENTER We will enter the buf buffer.
- * BCO_ALWAYS Always copy the options, but only set b_p_initialized when
- * appropriate.
- * BCO_NOHELP Don't copy the values to a help buffer.
- */
+/// Copy global option values to local options for one buffer.
+/// Used when creating a new buffer and sometimes when entering a buffer.
+/// flags:
+/// BCO_ENTER We will enter the buf buffer.
+/// BCO_ALWAYS Always copy the options, but only set b_p_initialized when
+/// appropriate.
+/// BCO_NOHELP Don't copy the values to a help buffer.
void buf_copy_options(buf_T *buf, int flags)
{
int should_copy = true;
@@ -6006,10 +6078,8 @@ void buf_copy_options(buf_T *buf, int flags)
save_p_isk = buf->b_p_isk;
buf->b_p_isk = NULL;
}
- /*
- * Always free the allocated strings.
- * If not already initialized, set 'readonly' and copy 'fileformat'.
- */
+ // Always free the allocated strings. If not already initialized,
+ // reset 'readonly' and copy 'fileformat'.
if (!buf->b_p_initialized) {
free_buf_options(buf, true);
buf->b_p_ro = false; // don't copy readonly
@@ -6161,9 +6231,7 @@ void buf_copy_options(buf_T *buf, int flags)
}
}
-/*
- * Reset the 'modifiable' option and its default value.
- */
+/// Reset the 'modifiable' option and its default value.
void reset_modifiable(void)
{
int opt_idx;
@@ -6176,17 +6244,13 @@ void reset_modifiable(void)
}
}
-/*
- * Set the global value for 'iminsert' to the local value.
- */
+/// Set the global value for 'iminsert' to the local value.
void set_iminsert_global(void)
{
p_iminsert = curbuf->b_p_iminsert;
}
-/*
- * Set the global value for 'imsearch' to the local value.
- */
+/// Set the global value for 'imsearch' to the local value.
void set_imsearch_global(void)
{
p_imsearch = curbuf->b_p_imsearch;
@@ -6488,10 +6552,8 @@ void ExpandOldSetting(int *num_file, char_u ***file)
*num_file = 1;
}
-/*
- * Get the value for the numeric or string option *opp in a nice format into
- * NameBuff[]. Must not be called with a hidden option!
- */
+/// Get the value for the numeric or string option///opp in a nice format into
+/// NameBuff[]. Must not be called with a hidden option!
static void
option_value2string(
vimoption_T *opp,
@@ -6544,21 +6606,18 @@ static int wc_use_keyname(char_u *varp, long *wcp)
return false;
}
-/*
- * Any character has an equivalent 'langmap' character. This is used for
- * keyboards that have a special language mode that sends characters above
- * 128 (although other characters can be translated too). The "to" field is a
- * Vim command character. This avoids having to switch the keyboard back to
- * ASCII mode when leaving Insert mode.
- *
- * langmap_mapchar[] maps any of 256 chars to an ASCII char used for Vim
- * commands.
- * langmap_mapga.ga_data is a sorted table of langmap_entry_T.
- * This does the same as langmap_mapchar[] for characters >= 256.
- */
-/*
- * With multi-byte support use growarray for 'langmap' chars >= 256
- */
+/// Any character has an equivalent 'langmap' character. This is used for
+/// keyboards that have a special language mode that sends characters above
+/// 128 (although other characters can be translated too). The "to" field is a
+/// Vim command character. This avoids having to switch the keyboard back to
+/// ASCII mode when leaving Insert mode.
+///
+/// langmap_mapchar[] maps any of 256 chars to an ASCII char used for Vim
+/// commands.
+/// langmap_mapga.ga_data is a sorted table of langmap_entry_T.
+/// This does the same as langmap_mapchar[] for characters >= 256.
+///
+/// With multi-byte support use growarray for 'langmap' chars >= 256
typedef struct {
int from;
int to;
@@ -6566,10 +6625,8 @@ typedef struct {
static garray_T langmap_mapga = GA_EMPTY_INIT_VALUE;
-/*
- * Search for an entry in "langmap_mapga" for "from". If found set the "to"
- * field. If not found insert a new entry at the appropriate location.
- */
+/// Search for an entry in "langmap_mapga" for "from". If found set the "to"
+/// field. If not found insert a new entry at the appropriate location.
static void langmap_set_entry(int from, int to)
{
langmap_entry_T *entries = (langmap_entry_T *)(langmap_mapga.ga_data);
@@ -6604,9 +6661,7 @@ static void langmap_set_entry(int from, int to)
entries[0].to = to;
}
-/*
- * Apply 'langmap' to multi-byte character "c" and return the result.
- */
+/// Apply 'langmap' to multi-byte character "c" and return the result.
int langmap_adjust_mb(int c)
{
langmap_entry_T *entries = (langmap_entry_T *)(langmap_mapga.ga_data);
@@ -6637,10 +6692,8 @@ static void langmap_init(void)
ga_init(&langmap_mapga, sizeof(langmap_entry_T), 8);
}
-/*
- * Called when langmap option is set; the language map can be
- * changed at any time!
- */
+/// Called when langmap option is set; the language map can be
+/// changed at any time!
static void langmap_set(void)
{
char_u *p;
@@ -6743,9 +6796,7 @@ bool shortmess(int x)
&& vim_strchr((char_u *)SHM_ALL_ABBREVIATIONS, x) != NULL)));
}
-/*
- * paste_option_changed() - Called after p_paste was set or reset.
- */
+/// paste_option_changed() - Called after p_paste was set or reset.
static void paste_option_changed(void)
{
static int old_p_paste = false;
@@ -6892,9 +6943,7 @@ void reset_option_was_set(const char *name)
}
}
-/*
- * fill_breakat_flags() -- called when 'breakat' changes value.
- */
+/// fill_breakat_flags() -- called when 'breakat' changes value.
static void fill_breakat_flags(void)
{
char_u *p;
@@ -6911,12 +6960,10 @@ static void fill_breakat_flags(void)
}
}
-/*
- * Check an option that can be a range of string values.
- *
- * Return OK for correct value, FAIL otherwise.
- * Empty is always OK.
- */
+/// Check an option that can be a range of string values.
+///
+/// Return OK for correct value, FAIL otherwise.
+/// Empty is always OK.
static int check_opt_strings(
char_u *val,
char **values,
@@ -6926,13 +6973,11 @@ static int check_opt_strings(
return opt_strings_flags(val, values, NULL, list);
}
-/*
- * Handle an option that can be a range of string values.
- * Set a flag in "*flagp" for each string present.
- *
- * Return OK for correct value, FAIL otherwise.
- * Empty is always OK.
- */
+/// Handle an option that can be a range of string values.
+/// Set a flag in "*flagp" for each string present.
+///
+/// Return OK for correct value, FAIL otherwise.
+/// Empty is always OK.
static int opt_strings_flags(
char_u *val, // new value
char **values, // array of valid string values
@@ -6965,9 +7010,7 @@ static int opt_strings_flags(
return OK;
}
-/*
- * Read the 'wildmode' option, fill wim_flags[].
- */
+/// Read the 'wildmode' option, fill wim_flags[].
static int check_opt_wim(void)
{
char_u new_wim_flags[4];
@@ -6990,6 +7033,8 @@ static int check_opt_wim(void)
new_wim_flags[idx] |= WIM_FULL;
} else if (i == 4 && STRNCMP(p, "list", 4) == 0) {
new_wim_flags[idx] |= WIM_LIST;
+ } else if (i == 8 && STRNCMP(p, "lastused", 8) == 0) {
+ new_wim_flags[idx] |= WIM_BUFLASTUSED;
} else {
return FAIL;
}
@@ -7018,25 +7063,24 @@ static int check_opt_wim(void)
return OK;
}
-/*
- * Check if backspacing over something is allowed.
- * The parameter what is one of the following: whatBS_INDENT, BS_EOL
- * or BS_START
- */
+/// Check if backspacing over something is allowed.
+/// The parameter what is one of the following: whatBS_INDENT, BS_EOL
+/// or BS_START
bool can_bs(int what)
{
+ if (what == BS_START && bt_prompt(curbuf)) {
+ return false;
+ }
switch (*p_bs) {
- case '2': return true;
- case '1': return what != BS_START;
- case '0': return false;
+ case '2': return true;
+ case '1': return what != BS_START;
+ case '0': return false;
}
return vim_strchr(p_bs, what) != NULL;
}
-/*
- * Save the current values of 'fileformat' and 'fileencoding', so that we know
- * the file must be considered changed when the value is different.
- */
+/// Save the current values of 'fileformat' and 'fileencoding', so that we know
+/// the file must be considered changed when the value is different.
void save_file_ff(buf_T *buf)
{
buf->b_start_ffc = *buf->b_p_ff;
@@ -7086,18 +7130,14 @@ bool file_ff_differs(buf_T *buf, bool ignore_empty)
return STRCMP(buf->b_start_fenc, buf->b_p_fenc) != 0;
}
-/*
- * return OK if "p" is a valid fileformat name, FAIL otherwise.
- */
+/// return OK if "p" is a valid fileformat name, FAIL otherwise.
int check_ff_value(char_u *p)
{
return check_opt_strings(p, p_ff_values, false);
}
-/*
- * Return the effective shiftwidth value for current buffer, using the
- * 'tabstop' value when 'shiftwidth' is zero.
- */
+/// Return the effective shiftwidth value for current buffer, using the
+/// 'tabstop' value when 'shiftwidth' is zero.
int get_sw_value(buf_T *buf)
{
long result = buf->b_p_sw ? buf->b_p_sw : buf->b_p_ts;
@@ -7105,8 +7145,8 @@ int get_sw_value(buf_T *buf)
return (int)result;
}
-// Return the effective softtabstop value for the current buffer,
-// using the effective shiftwidth value when 'softtabstop' is negative.
+/// Return the effective softtabstop value for the current buffer,
+/// using the effective shiftwidth value when 'softtabstop' is negative.
int get_sts_value(void)
{
long result = curbuf->b_p_sts < 0 ? get_sw_value(curbuf) : curbuf->b_p_sts;
@@ -7114,12 +7154,10 @@ int get_sts_value(void)
return (int)result;
}
-/*
- * Check matchpairs option for "*initc".
- * If there is a match set "*initc" to the matching character and "*findc" to
- * the opposite character. Set "*backwards" to the direction.
- * When "switchit" is true swap the direction.
- */
+/// Check matchpairs option for "*initc".
+/// If there is a match set "*initc" to the matching character and "*findc" to
+/// the opposite character. Set "*backwards" to the direction.
+/// When "switchit" is true swap the direction.
void find_mps_values(int *initc, int *findc, int *backwards, int switchit)
{
char_u *ptr = curbuf->b_p_mps;
@@ -7191,9 +7229,9 @@ static bool briopt_check(win_T *wp)
}
}
- wp->w_p_brishift = bri_shift;
- wp->w_p_brimin = bri_min;
- wp->w_p_brisbr = bri_sbr;
+ wp->w_briopt_shift = bri_shift;
+ wp->w_briopt_min = bri_min;
+ wp->w_briopt_sbr = bri_sbr;
return true;
}
@@ -7224,12 +7262,13 @@ int get_fileformat(buf_T *buf)
/// argument.
///
/// @param eap can be NULL!
-int get_fileformat_force(buf_T *buf, exarg_T *eap)
+int get_fileformat_force(const buf_T *buf, const exarg_T *eap)
+ FUNC_ATTR_NONNULL_ARG(1)
{
int c;
if (eap != NULL && eap->force_ff != 0) {
- c = eap->cmd[eap->force_ff];
+ c = eap->force_ff;
} else {
if ((eap != NULL && eap->force_bin != 0)
? (eap->force_bin == FORCE_BIN) : buf->b_p_bin) {
@@ -7409,3 +7448,21 @@ dict_T *get_winbuf_options(const int bufopt)
return d;
}
+
+/// Return the effective 'scrolloff' value for the current window, using the
+/// global value when appropriate.
+long get_scrolloff_value(void)
+{
+ // Disallow scrolloff in terminal-mode. #11915
+ if (State & TERM_FOCUS) {
+ return 0;
+ }
+ return curwin->w_p_so < 0 ? p_so : curwin->w_p_so;
+}
+
+/// Return the effective 'sidescrolloff' value for the current window, using the
+/// global value when appropriate.
+long get_sidescrolloff_value(void)
+{
+ return curwin->w_p_siso < 0 ? p_siso : curwin->w_p_siso;
+}
diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h
index fcad6836bf..ecaa941082 100644
--- a/src/nvim/option_defs.h
+++ b/src/nvim/option_defs.h
@@ -277,6 +277,7 @@ enum {
#define WIM_FULL 1
#define WIM_LONGEST 2
#define WIM_LIST 4
+#define WIM_BUFLASTUSED 8
// arguments for can_bs()
#define BS_INDENT 'i' // "Indent"
@@ -577,8 +578,8 @@ static char *(p_ssop_values[]) = {
# define SSOP_HELP 0x040
# define SSOP_BLANK 0x080
# define SSOP_GLOBALS 0x100
-# define SSOP_SLASH 0x200
-# define SSOP_UNIX 0x400
+# define SSOP_SLASH 0x200 // Deprecated, always set.
+# define SSOP_UNIX 0x400 // Deprecated, always set.
# define SSOP_SESDIR 0x800
# define SSOP_CURDIR 0x1000
# define SSOP_FOLDS 0x2000
@@ -835,6 +836,8 @@ enum {
, WV_RLC
, WV_SCBIND
, WV_SCROLL
+ , WV_SISO
+ , WV_SO
, WV_SPELL
, WV_CUC
, WV_CUL
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index 7d080b8d56..e7c1a3fe88 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -831,10 +831,11 @@ return {
},
{
full_name='foldcolumn', abbreviation='fdc',
- type='number', scope={'window'},
+ type='string', scope={'window'},
vi_def=true,
+ alloced=true,
redraw={'current_window'},
- defaults={if_true={vi=false}}
+ defaults={if_true={vi="0"}}
},
{
full_name='foldenable', abbreviation='fen',
@@ -1989,7 +1990,7 @@ return {
},
{
full_name='scrolloff', abbreviation='so',
- type='number', scope={'global'},
+ type='number', scope={'global', 'window'},
vi_def=true,
vim=true,
redraw={'all_windows'},
@@ -2228,10 +2229,10 @@ return {
},
{
full_name='sidescrolloff', abbreviation='siso',
- type='number', scope={'global'},
+ type='number', scope={'global', 'window'},
vi_def=true,
vim=true,
- redraw={'current_buffer'},
+ redraw={'all_windows'},
varname='p_siso',
defaults={if_true={vi=0}}
},
@@ -2586,6 +2587,7 @@ return {
type='bool', scope={'global'},
vi_def=true,
vim=true,
+ redraw={'ui_option'},
varname='p_ttimeout',
defaults={if_true={vi=true}}
},
@@ -2593,6 +2595,7 @@ return {
full_name='ttimeoutlen', abbreviation='ttm',
type='number', scope={'global'},
vi_def=true,
+ redraw={'ui_option'},
varname='p_ttm',
defaults={if_true={vi=50}}
},
diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c
index ec266796a8..082ad58223 100644
--- a/src/nvim/os/env.c
+++ b/src/nvim/os/env.c
@@ -847,6 +847,20 @@ const void *vim_env_iter_rev(const char delim,
}
}
+
+/// @param[out] exe_name should be at least MAXPATHL in size
+void vim_get_prefix_from_exepath(char *exe_name)
+{
+ // TODO(bfredl): param could have been written as "char exe_name[MAXPATHL]"
+ // but c_grammar.lua does not recognize it (yet).
+ xstrlcpy(exe_name, (char *)get_vim_var_str(VV_PROGPATH),
+ MAXPATHL * sizeof(*exe_name));
+ char *path_end = (char *)path_tail_with_sep((char_u *)exe_name);
+ *path_end = '\0'; // remove the trailing "nvim.exe"
+ path_end = (char *)path_tail((char_u *)exe_name);
+ *path_end = '\0'; // remove the trailing "bin/"
+}
+
/// Vim getenv() wrapper with special handling of $HOME, $VIM, $VIMRUNTIME,
/// allowing the user to override the Nvim runtime directory at runtime.
/// Result must be freed by the caller.
@@ -902,12 +916,7 @@ char *vim_getenv(const char *name)
char exe_name[MAXPATHL];
// Find runtime path relative to the nvim binary: ../share/nvim/runtime
if (vim_path == NULL) {
- xstrlcpy(exe_name, (char *)get_vim_var_str(VV_PROGPATH),
- sizeof(exe_name));
- char *path_end = (char *)path_tail_with_sep((char_u *)exe_name);
- *path_end = '\0'; // remove the trailing "nvim.exe"
- path_end = (char *)path_tail((char_u *)exe_name);
- *path_end = '\0'; // remove the trailing "bin/"
+ vim_get_prefix_from_exepath(exe_name);
if (append_path(
exe_name,
"share" _PATHSEPSTR "nvim" _PATHSEPSTR "runtime" _PATHSEPSTR,
diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c
index ae922e4040..873b611151 100644
--- a/src/nvim/os/fs.c
+++ b/src/nvim/os/fs.c
@@ -1147,6 +1147,30 @@ bool os_fileid_equal_fileinfo(const FileID *file_id,
&& file_id->device_id == file_info->stat.st_dev;
}
+/// Return the canonicalized absolute pathname.
+///
+/// @param[in] name Filename to be canonicalized.
+/// @param[out] buf Buffer to store the canonicalized values. A minimum length
+// of MAXPATHL+1 is required. If it is NULL, memory is
+// allocated. In that case, the caller should deallocate this
+// buffer.
+///
+/// @return pointer to the buf on success, or NULL.
+char *os_realpath(const char *name, char *buf)
+ FUNC_ATTR_NONNULL_ARG(1)
+{
+ uv_fs_t request;
+ int result = uv_fs_realpath(&fs_loop, &request, name, NULL);
+ if (result == kLibuvSuccess) {
+ if (buf == NULL) {
+ buf = xmallocz(MAXPATHL);
+ }
+ xstrlcpy(buf, request.ptr, MAXPATHL + 1);
+ }
+ uv_fs_req_cleanup(&request);
+ return result == kLibuvSuccess ? buf : NULL;
+}
+
#ifdef WIN32
# include <shlobj.h>
@@ -1233,4 +1257,49 @@ shortcut_end:
return rfname;
}
+#define is_path_sep(c) ((c) == L'\\' || (c) == L'/')
+/// Returns true if the path contains a reparse point (junction or symbolic
+/// link). Otherwise false in returned.
+bool os_is_reparse_point_include(const char *path)
+{
+ wchar_t *p, *q, *utf16_path;
+ wchar_t buf[MAX_PATH];
+ DWORD attr;
+ bool result = false;
+
+ const int r = utf8_to_utf16(path, -1, &utf16_path);
+ if (r != 0) {
+ EMSG2("utf8_to_utf16 failed: %d", r);
+ return false;
+ }
+
+ p = utf16_path;
+ if (isalpha(p[0]) && p[1] == L':' && is_path_sep(p[2])) {
+ p += 3;
+ } else if (is_path_sep(p[0]) && is_path_sep(p[1])) {
+ p += 2;
+ }
+
+ while (*p != L'\0') {
+ q = wcspbrk(p, L"\\/");
+ if (q == NULL) {
+ p = q = utf16_path + wcslen(utf16_path);
+ } else {
+ p = q + 1;
+ }
+ if (q - utf16_path >= MAX_PATH) {
+ break;
+ }
+ wcsncpy(buf, utf16_path, (size_t)(q - utf16_path));
+ buf[q - utf16_path] = L'\0';
+ attr = GetFileAttributesW(buf);
+ if (attr != INVALID_FILE_ATTRIBUTES
+ && (attr & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
+ result = true;
+ break;
+ }
+ }
+ xfree(utf16_path);
+ return result;
+}
#endif
diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c
index d1de18d5b3..6294d5e4e2 100644
--- a/src/nvim/os/shell.c
+++ b/src/nvim/os/shell.c
@@ -9,13 +9,17 @@
#include <uv.h>
#include "nvim/ascii.h"
+#include "nvim/fileio.h"
#include "nvim/lib/kvec.h"
#include "nvim/log.h"
#include "nvim/event/loop.h"
#include "nvim/event/libuv_process.h"
#include "nvim/event/rstream.h"
+#include "nvim/ex_cmds.h"
+#include "nvim/misc1.h"
#include "nvim/os/shell.h"
#include "nvim/os/signal.h"
+#include "nvim/path.h"
#include "nvim/types.h"
#include "nvim/main.h"
#include "nvim/vim.h"
@@ -32,6 +36,8 @@
#define NS_1_SECOND 1000000000U // 1 second, in nanoseconds
#define OUT_DATA_THRESHOLD 1024 * 10U // 10KB, "a few screenfuls" of data.
+#define SHELL_SPECIAL (char_u *)"\t \"&'$;<>()\\|"
+
typedef struct {
char *data;
size_t cap, len;
@@ -41,6 +47,498 @@ typedef struct {
# include "os/shell.c.generated.h"
#endif
+static void save_patterns(int num_pat, char_u **pat, int *num_file,
+ char_u ***file)
+{
+ *file = xmalloc((size_t)num_pat * sizeof(char_u *));
+ for (int i = 0; i < num_pat; i++) {
+ char_u *s = vim_strsave(pat[i]);
+ // Be compatible with expand_filename(): halve the number of
+ // backslashes.
+ backslash_halve(s);
+ (*file)[i] = s;
+ }
+ *num_file = num_pat;
+}
+
+static bool have_wildcard(int num, char_u **file)
+{
+ for (int i = 0; i < num; i++) {
+ if (path_has_wildcard(file[i])) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static bool have_dollars(int num, char_u **file)
+{
+ for (int i = 0; i < num; i++) {
+ if (vim_strchr(file[i], '$') != NULL) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/// Performs wildcard pattern matching using the shell.
+///
+/// @param num_pat is the number of input patterns.
+/// @param pat is an array of pointers to input patterns.
+/// @param[out] num_file is pointer to number of matched file names.
+/// Set to the number of pointers in *file.
+/// @param[out] file is pointer to array of pointers to matched file names.
+/// Memory pointed to by the initial value of *file will
+/// not be freed.
+/// Set to NULL if FAIL is returned. Otherwise points to
+/// allocated memory.
+/// @param flags is a combination of EW_* flags used in
+/// expand_wildcards().
+/// If matching fails but EW_NOTFOUND is set in flags or
+/// there are no wildcards, the patterns from pat are
+/// copied into *file.
+///
+/// @returns OK for success or FAIL for error.
+int os_expand_wildcards(int num_pat, char_u **pat, int *num_file,
+ char_u ***file, int flags)
+ FUNC_ATTR_NONNULL_ARG(3)
+ FUNC_ATTR_NONNULL_ARG(4)
+{
+ int i;
+ size_t len;
+ char_u *p;
+ bool dir;
+ char_u *extra_shell_arg = NULL;
+ ShellOpts shellopts = kShellOptExpand | kShellOptSilent;
+ int j;
+ char_u *tempname;
+ char_u *command;
+ FILE *fd;
+ char_u *buffer;
+#define STYLE_ECHO 0 // use "echo", the default
+#define STYLE_GLOB 1 // use "glob", for csh
+#define STYLE_VIMGLOB 2 // use "vimglob", for Posix sh
+#define STYLE_PRINT 3 // use "print -N", for zsh
+#define STYLE_BT 4 // `cmd` expansion, execute the pattern directly
+ int shell_style = STYLE_ECHO;
+ int check_spaces;
+ static bool did_find_nul = false;
+ bool ampersent = false;
+ // vimglob() function to define for Posix shell
+ static char *sh_vimglob_func =
+ "vimglob() { while [ $# -ge 1 ]; do echo \"$1\"; shift; done }; vimglob >";
+
+ bool is_fish_shell =
+#if defined(UNIX)
+ STRNCMP(invocation_path_tail(p_sh, NULL), "fish", 4) == 0;
+#else
+ false;
+#endif
+
+ *num_file = 0; // default: no files found
+ *file = NULL;
+
+ // If there are no wildcards, just copy the names to allocated memory.
+ // Saves a lot of time, because we don't have to start a new shell.
+ if (!have_wildcard(num_pat, pat)) {
+ save_patterns(num_pat, pat, num_file, file);
+ return OK;
+ }
+
+ // Don't allow any shell command in the sandbox.
+ if (sandbox != 0 && check_secure()) {
+ return FAIL;
+ }
+
+ // Don't allow the use of backticks in secure and restricted mode.
+ if (secure || restricted) {
+ for (i = 0; i < num_pat; i++) {
+ if (vim_strchr(pat[i], '`') != NULL
+ && (check_restricted() || check_secure())) {
+ return FAIL;
+ }
+ }
+ }
+
+ // get a name for the temp file
+ if ((tempname = vim_tempname()) == NULL) {
+ EMSG(_(e_notmp));
+ return FAIL;
+ }
+
+ // Let the shell expand the patterns and write the result into the temp
+ // file.
+ // STYLE_BT: NL separated
+ // If expanding `cmd` execute it directly.
+ // STYLE_GLOB: NUL separated
+ // If we use *csh, "glob" will work better than "echo".
+ // STYLE_PRINT: NL or NUL separated
+ // If we use *zsh, "print -N" will work better than "glob".
+ // STYLE_VIMGLOB: NL separated
+ // If we use *sh*, we define "vimglob()".
+ // STYLE_ECHO: space separated.
+ // A shell we don't know, stay safe and use "echo".
+ if (num_pat == 1 && *pat[0] == '`'
+ && (len = STRLEN(pat[0])) > 2
+ && *(pat[0] + len - 1) == '`') {
+ shell_style = STYLE_BT;
+ } else if ((len = STRLEN(p_sh)) >= 3) {
+ if (STRCMP(p_sh + len - 3, "csh") == 0) {
+ shell_style = STYLE_GLOB;
+ } else if (STRCMP(p_sh + len - 3, "zsh") == 0) {
+ shell_style = STYLE_PRINT;
+ }
+ }
+ if (shell_style == STYLE_ECHO
+ && strstr((char *)path_tail(p_sh), "sh") != NULL) {
+ shell_style = STYLE_VIMGLOB;
+ }
+
+ // Compute the length of the command. We need 2 extra bytes: for the
+ // optional '&' and for the NUL.
+ // Worst case: "unset nonomatch; print -N >" plus two is 29
+ len = STRLEN(tempname) + 29;
+ if (shell_style == STYLE_VIMGLOB) {
+ len += STRLEN(sh_vimglob_func);
+ }
+
+ for (i = 0; i < num_pat; i++) {
+ // Count the length of the patterns in the same way as they are put in
+ // "command" below.
+ len++; // add space
+ for (j = 0; pat[i][j] != NUL; j++) {
+ if (vim_strchr(SHELL_SPECIAL, pat[i][j]) != NULL) {
+ len++; // may add a backslash
+ }
+ len++;
+ }
+ }
+
+ if (is_fish_shell) {
+ len += sizeof("egin;"" end") - 1;
+ }
+
+ command = xmalloc(len);
+
+ // Build the shell command:
+ // - Set $nonomatch depending on EW_NOTFOUND (hopefully the shell
+ // recognizes this).
+ // - Add the shell command to print the expanded names.
+ // - Add the temp file name.
+ // - Add the file name patterns.
+ if (shell_style == STYLE_BT) {
+ // change `command; command& ` to (command; command )
+ if (is_fish_shell) {
+ STRCPY(command, "begin; ");
+ } else {
+ STRCPY(command, "(");
+ }
+ STRCAT(command, pat[0] + 1); // exclude first backtick
+ p = command + STRLEN(command) - 1;
+ if (is_fish_shell) {
+ *p-- = ';';
+ STRCAT(command, " end");
+ } else {
+ *p-- = ')'; // remove last backtick
+ }
+ while (p > command && ascii_iswhite(*p)) {
+ p--;
+ }
+ if (*p == '&') { // remove trailing '&'
+ ampersent = true;
+ *p = ' ';
+ }
+ STRCAT(command, ">");
+ } else {
+ if (flags & EW_NOTFOUND) {
+ STRCPY(command, "set nonomatch; ");
+ } else {
+ STRCPY(command, "unset nonomatch; ");
+ }
+ if (shell_style == STYLE_GLOB) {
+ STRCAT(command, "glob >");
+ } else if (shell_style == STYLE_PRINT) {
+ STRCAT(command, "print -N >");
+ } else if (shell_style == STYLE_VIMGLOB) {
+ STRCAT(command, sh_vimglob_func);
+ } else {
+ STRCAT(command, "echo >");
+ }
+ }
+
+ STRCAT(command, tempname);
+
+ if (shell_style != STYLE_BT) {
+ for (i = 0; i < num_pat; i++) {
+ // Put a backslash before special
+ // characters, except inside ``.
+ bool intick = false;
+
+ p = command + STRLEN(command);
+ *p++ = ' ';
+ for (j = 0; pat[i][j] != NUL; j++) {
+ if (pat[i][j] == '`') {
+ intick = !intick;
+ } else if (pat[i][j] == '\\' && pat[i][j + 1] != NUL) {
+ // Remove a backslash, take char literally. But keep
+ // backslash inside backticks, before a special character
+ // and before a backtick.
+ if (intick
+ || vim_strchr(SHELL_SPECIAL, pat[i][j + 1]) != NULL
+ || pat[i][j + 1] == '`') {
+ *p++ = '\\';
+ }
+ j++;
+ } else if (!intick
+ && ((flags & EW_KEEPDOLLAR) == 0 || pat[i][j] != '$')
+ && vim_strchr(SHELL_SPECIAL, pat[i][j]) != NULL) {
+ // Put a backslash before a special character, but not
+ // when inside ``. And not for $var when EW_KEEPDOLLAR is
+ // set.
+ *p++ = '\\';
+ }
+
+ // Copy one character.
+ *p++ = pat[i][j];
+ }
+ *p = NUL;
+ }
+ }
+
+ if (flags & EW_SILENT) {
+ shellopts |= kShellOptHideMess;
+ }
+
+ if (ampersent) {
+ STRCAT(command, "&"); // put the '&' after the redirection
+ }
+
+ // Using zsh -G: If a pattern has no matches, it is just deleted from
+ // the argument list, otherwise zsh gives an error message and doesn't
+ // expand any other pattern.
+ if (shell_style == STYLE_PRINT) {
+ extra_shell_arg = (char_u *)"-G"; // Use zsh NULL_GLOB option
+
+ // If we use -f then shell variables set in .cshrc won't get expanded.
+ // vi can do it, so we will too, but it is only necessary if there is a "$"
+ // in one of the patterns, otherwise we can still use the fast option.
+ } else if (shell_style == STYLE_GLOB && !have_dollars(num_pat, pat)) {
+ extra_shell_arg = (char_u *)"-f"; // Use csh fast option
+ }
+
+ // execute the shell command
+ i = call_shell(command, shellopts, extra_shell_arg);
+
+ // When running in the background, give it some time to create the temp
+ // file, but don't wait for it to finish.
+ if (ampersent) {
+ os_delay(10L, true);
+ }
+
+ xfree(command);
+
+ if (i) { // os_call_shell() failed
+ os_remove((char *)tempname);
+ xfree(tempname);
+ // With interactive completion, the error message is not printed.
+ if (!(flags & EW_SILENT)) {
+ msg_putchar('\n'); // clear bottom line quickly
+ cmdline_row = Rows - 1; // continue on last line
+ MSG(_(e_wildexpand));
+ msg_start(); // don't overwrite this message
+ }
+
+ // If a `cmd` expansion failed, don't list `cmd` as a match, even when
+ // EW_NOTFOUND is given
+ if (shell_style == STYLE_BT) {
+ return FAIL;
+ }
+ goto notfound;
+ }
+
+ // read the names from the file into memory
+ fd = fopen((char *)tempname, READBIN);
+ if (fd == NULL) {
+ // Something went wrong, perhaps a file name with a special char.
+ if (!(flags & EW_SILENT)) {
+ MSG(_(e_wildexpand));
+ msg_start(); // don't overwrite this message
+ }
+ xfree(tempname);
+ goto notfound;
+ }
+ int fseek_res = fseek(fd, 0L, SEEK_END);
+ if (fseek_res < 0) {
+ xfree(tempname);
+ fclose(fd);
+ return FAIL;
+ }
+ int64_t templen = ftell(fd); // get size of temp file
+ if (templen < 0) {
+ xfree(tempname);
+ fclose(fd);
+ return FAIL;
+ }
+#if SIZEOF_LONG_LONG > SIZEOF_SIZE_T
+ assert(templen <= (long long)SIZE_MAX); // NOLINT(runtime/int)
+#endif
+ len = (size_t)templen;
+ fseek(fd, 0L, SEEK_SET);
+ buffer = xmalloc(len + 1);
+ // fread() doesn't terminate buffer with NUL;
+ // appropriate termination (not always NUL) is done below.
+ size_t readlen = fread((char *)buffer, 1, len, fd);
+ fclose(fd);
+ os_remove((char *)tempname);
+ if (readlen != len) {
+ // unexpected read error
+ EMSG2(_(e_notread), tempname);
+ xfree(tempname);
+ xfree(buffer);
+ return FAIL;
+ }
+ xfree(tempname);
+
+ // file names are separated with Space
+ if (shell_style == STYLE_ECHO) {
+ buffer[len] = '\n'; // make sure the buffer ends in NL
+ p = buffer;
+ for (i = 0; *p != '\n'; i++) { // count number of entries
+ while (*p != ' ' && *p != '\n') {
+ p++;
+ }
+ p = skipwhite(p); // skip to next entry
+ }
+ // file names are separated with NL
+ } else if (shell_style == STYLE_BT || shell_style == STYLE_VIMGLOB) {
+ buffer[len] = NUL; // make sure the buffer ends in NUL
+ p = buffer;
+ for (i = 0; *p != NUL; i++) { // count number of entries
+ while (*p != '\n' && *p != NUL) {
+ p++;
+ }
+ if (*p != NUL) {
+ p++;
+ }
+ p = skipwhite(p); // skip leading white space
+ }
+ // file names are separated with NUL
+ } else {
+ // Some versions of zsh use spaces instead of NULs to separate
+ // results. Only do this when there is no NUL before the end of the
+ // buffer, otherwise we would never be able to use file names with
+ // embedded spaces when zsh does use NULs.
+ // When we found a NUL once, we know zsh is OK, set did_find_nul and
+ // don't check for spaces again.
+ check_spaces = false;
+ if (shell_style == STYLE_PRINT && !did_find_nul) {
+ // If there is a NUL, set did_find_nul, else set check_spaces
+ buffer[len] = NUL;
+ if (len && (int)STRLEN(buffer) < (int)len) {
+ did_find_nul = true;
+ } else {
+ check_spaces = true;
+ }
+ }
+
+ // Make sure the buffer ends with a NUL. For STYLE_PRINT there
+ // already is one, for STYLE_GLOB it needs to be added.
+ if (len && buffer[len - 1] == NUL) {
+ len--;
+ } else {
+ buffer[len] = NUL;
+ }
+ for (p = buffer; p < buffer + len; p++) {
+ if (*p == NUL || (*p == ' ' && check_spaces)) { // count entry
+ i++;
+ *p = NUL;
+ }
+ }
+ if (len) {
+ i++; // count last entry
+ }
+ }
+ assert(buffer[len] == NUL || buffer[len] == '\n');
+
+ if (i == 0) {
+ // Can happen when using /bin/sh and typing ":e $NO_SUCH_VAR^I".
+ // /bin/sh will happily expand it to nothing rather than returning an
+ // error; and hey, it's good to check anyway -- webb.
+ xfree(buffer);
+ goto notfound;
+ }
+ *num_file = i;
+ *file = xmalloc(sizeof(char_u *) * (size_t)i);
+
+ // Isolate the individual file names.
+ p = buffer;
+ for (i = 0; i < *num_file; i++) {
+ (*file)[i] = p;
+ // Space or NL separates
+ if (shell_style == STYLE_ECHO || shell_style == STYLE_BT
+ || shell_style == STYLE_VIMGLOB) {
+ while (!(shell_style == STYLE_ECHO && *p == ' ')
+ && *p != '\n' && *p != NUL) {
+ p++;
+ }
+ if (p == buffer + len) { // last entry
+ *p = NUL;
+ } else {
+ *p++ = NUL;
+ p = skipwhite(p); // skip to next entry
+ }
+ } else { // NUL separates
+ while (*p && p < buffer + len) { // skip entry
+ p++;
+ }
+ p++; // skip NUL
+ }
+ }
+
+ // Move the file names to allocated memory.
+ for (j = 0, i = 0; i < *num_file; i++) {
+ // Require the files to exist. Helps when using /bin/sh
+ if (!(flags & EW_NOTFOUND) && !os_path_exists((*file)[i])) {
+ continue;
+ }
+
+ // check if this entry should be included
+ dir = (os_isdir((*file)[i]));
+ if ((dir && !(flags & EW_DIR)) || (!dir && !(flags & EW_FILE))) {
+ continue;
+ }
+
+ // Skip files that are not executable if we check for that.
+ if (!dir && (flags & EW_EXEC)
+ && !os_can_exe((char *)(*file)[i], NULL, !(flags & EW_SHELLCMD))) {
+ continue;
+ }
+
+ p = xmalloc(STRLEN((*file)[i]) + 1 + dir);
+ STRCPY(p, (*file)[i]);
+ if (dir) {
+ add_pathsep((char *)p); // add '/' to a directory name
+ }
+ (*file)[j++] = p;
+ }
+ xfree(buffer);
+ *num_file = j;
+
+ if (*num_file == 0) { // rejected all entries
+ XFREE_CLEAR(*file);
+ goto notfound;
+ }
+
+ return OK;
+
+notfound:
+ if (flags & EW_NOTFOUND) {
+ save_patterns(num_pat, pat, num_file, file);
+ return OK;
+ }
+ return FAIL;
+}
+
/// Builds the argument vector for running the user-configured 'shell' (p_sh)
/// with an optional command prefixed by 'shellcmdflag' (p_shcf). E.g.:
///
diff --git a/src/nvim/os/signal.c b/src/nvim/os/signal.c
index ba6226ef9d..112de9fed8 100644
--- a/src/nvim/os/signal.c
+++ b/src/nvim/os/signal.c
@@ -50,22 +50,13 @@ void signal_init(void)
signal_watcher_init(&main_loop, &shup, NULL);
signal_watcher_init(&main_loop, &squit, NULL);
signal_watcher_init(&main_loop, &sterm, NULL);
-#ifdef SIGPIPE
- signal_watcher_start(&spipe, on_signal, SIGPIPE);
-#endif
- signal_watcher_start(&shup, on_signal, SIGHUP);
-#ifdef SIGQUIT
- signal_watcher_start(&squit, on_signal, SIGQUIT);
-#endif
- signal_watcher_start(&sterm, on_signal, SIGTERM);
#ifdef SIGPWR
signal_watcher_init(&main_loop, &spwr, NULL);
- signal_watcher_start(&spwr, on_signal, SIGPWR);
#endif
#ifdef SIGUSR1
signal_watcher_init(&main_loop, &susr1, NULL);
- signal_watcher_start(&susr1, on_signal, SIGUSR1);
#endif
+ signal_start();
}
void signal_teardown(void)
@@ -83,11 +74,33 @@ void signal_teardown(void)
#endif
}
+void signal_start(void)
+{
+#ifdef SIGPIPE
+ signal_watcher_start(&spipe, on_signal, SIGPIPE);
+#endif
+ signal_watcher_start(&shup, on_signal, SIGHUP);
+#ifdef SIGQUIT
+ signal_watcher_start(&squit, on_signal, SIGQUIT);
+#endif
+ signal_watcher_start(&sterm, on_signal, SIGTERM);
+#ifdef SIGPWR
+ signal_watcher_start(&spwr, on_signal, SIGPWR);
+#endif
+#ifdef SIGUSR1
+ signal_watcher_start(&susr1, on_signal, SIGUSR1);
+#endif
+}
+
void signal_stop(void)
{
+#ifdef SIGPIPE
signal_watcher_stop(&spipe);
+#endif
signal_watcher_stop(&shup);
+#ifdef SIGQUIT
signal_watcher_stop(&squit);
+#endif
signal_watcher_stop(&sterm);
#ifdef SIGPWR
signal_watcher_stop(&spwr);
diff --git a/src/nvim/os/time.c b/src/nvim/os/time.c
index 4dd0614fe2..346e40e02e 100644
--- a/src/nvim/os/time.c
+++ b/src/nvim/os/time.c
@@ -2,9 +2,6 @@
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
#include <assert.h>
-#include <stdint.h>
-#include <stdbool.h>
-#include <time.h>
#include <limits.h>
#include <uv.h>
@@ -13,7 +10,7 @@
#include "nvim/os/time.h"
#include "nvim/os/input.h"
#include "nvim/event/loop.h"
-#include "nvim/vim.h"
+#include "nvim/os/os.h"
#include "nvim/main.h"
static uv_mutex_t delay_mutex;
@@ -112,6 +109,10 @@ void os_microdelay(uint64_t us, bool ignoreinput)
uv_mutex_unlock(&delay_mutex);
}
+// Cache of the current timezone name as retrieved from TZ, or an empty string
+// where unset, up to 64 octets long including trailing null byte.
+static char tz_cache[64];
+
/// Portable version of POSIX localtime_r()
///
/// @return NULL in case of error
@@ -120,6 +121,19 @@ struct tm *os_localtime_r(const time_t *restrict clock,
{
#ifdef UNIX
// POSIX provides localtime_r() as a thread-safe version of localtime().
+ //
+ // Check to see if the environment variable TZ has changed since the last run.
+ // Call tzset(3) to update the global timezone variables if it has.
+ // POSIX standard doesn't require localtime_r() implementations to do that
+ // as it does with localtime(), and we don't want to call tzset() every time.
+ const char *tz = os_getenv("TZ");
+ if (tz == NULL) {
+ tz = "";
+ }
+ if (strncmp(tz_cache, tz, sizeof(tz_cache) - 1) != 0) {
+ tzset();
+ xstrlcpy(tz_cache, tz, sizeof(tz_cache));
+ }
return localtime_r(clock, result); // NOLINT(runtime/threadsafe_fn)
#else
// Windows version of localtime() is thread-safe.
@@ -144,6 +158,39 @@ struct tm *os_localtime(struct tm *result) FUNC_ATTR_NONNULL_ALL
return os_localtime_r(&rawtime, result);
}
+/// Portable version of POSIX ctime_r()
+///
+/// @param clock[in]
+/// @param result[out] Pointer to a 'char' where the result should be placed
+/// @param result_len length of result buffer
+/// @return human-readable string of current local time
+char *os_ctime_r(const time_t *restrict clock, char *restrict result,
+ size_t result_len)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
+{
+ struct tm clock_local;
+ struct tm *clock_local_ptr = os_localtime_r(clock, &clock_local);
+ // MSVC returns NULL for an invalid value of seconds.
+ if (clock_local_ptr == NULL) {
+ snprintf(result, result_len, "%s\n", _("(Invalid)"));
+ } else {
+ strftime(result, result_len, "%a %b %d %H:%M:%S %Y\n", clock_local_ptr);
+ }
+ return result;
+}
+
+/// Gets the current Unix timestamp and adjusts it to local time.
+///
+/// @param result[out] Pointer to a 'char' where the result should be placed
+/// @param result_len length of result buffer
+/// @return human-readable string of current local time
+char *os_ctime(char *result, size_t result_len)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
+{
+ time_t rawtime = time(NULL);
+ return os_ctime_r(&rawtime, result, result_len);
+}
+
/// Obtains the current Unix timestamp.
///
/// @return Seconds since epoch.
diff --git a/src/nvim/os_unix.c b/src/nvim/os_unix.c
index 0f44df2188..be4bd9709b 100644
--- a/src/nvim/os_unix.c
+++ b/src/nvim/os_unix.c
@@ -1,13 +1,6 @@
// 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
-/*
- * os_unix.c -- code for all flavors of Unix (BSD, SYSV, SVR4, POSIX, ...)
- *
- * A lot of this file was originally written by Juergen Weigert and later
- * changed beyond recognition.
- */
-
#include <assert.h>
#include <errno.h>
#include <inttypes.h>
@@ -79,527 +72,3 @@ void mch_free_acl(vim_acl_T aclent)
return;
}
#endif
-
-void mch_exit(int r)
- FUNC_ATTR_NORETURN
-{
- exiting = true;
-
- ui_flush();
- ui_call_stop();
- ml_close_all(true); // remove all memfiles
-
- if (!event_teardown() && r == 0) {
- r = 1; // Exit with error if main_loop did not teardown gracefully.
- }
- if (input_global_fd() >= 0) {
- stream_set_blocking(input_global_fd(), true); // normalize stream (#2598)
- }
-
- ILOG("Nvim exit: %d", r);
-
-#ifdef EXITFREE
- free_all_mem();
-#endif
-
- exit(r);
-}
-
-#define SHELL_SPECIAL (char_u *)"\t \"&'$;<>()\\|"
-
-/// Does wildcard pattern matching using the shell.
-///
-/// @param num_pat is the number of input patterns.
-/// @param pat is an array of pointers to input patterns.
-/// @param[out] num_file is pointer to number of matched file names.
-/// Set to the number of pointers in *file.
-/// @param[out] file is pointer to array of pointers to matched file names.
-/// Memory pointed to by the initial value of *file will
-/// not be freed.
-/// Set to NULL if FAIL is returned. Otherwise points to
-/// allocated memory.
-/// @param flags is a combination of EW_* flags used in
-/// expand_wildcards().
-/// If matching fails but EW_NOTFOUND is set in flags or
-/// there are no wildcards, the patterns from pat are
-/// copied into *file.
-///
-/// @returns OK for success or FAIL for error.
-int mch_expand_wildcards(int num_pat, char_u **pat, int *num_file,
- char_u ***file, int flags) FUNC_ATTR_NONNULL_ARG(3)
- FUNC_ATTR_NONNULL_ARG(4)
-{
- int i;
- size_t len;
- char_u *p;
- bool dir;
- char_u *extra_shell_arg = NULL;
- ShellOpts shellopts = kShellOptExpand | kShellOptSilent;
- int j;
- char_u *tempname;
- char_u *command;
- FILE *fd;
- char_u *buffer;
-#define STYLE_ECHO 0 /* use "echo", the default */
-#define STYLE_GLOB 1 /* use "glob", for csh */
-#define STYLE_VIMGLOB 2 /* use "vimglob", for Posix sh */
-#define STYLE_PRINT 3 /* use "print -N", for zsh */
-#define STYLE_BT 4 /* `cmd` expansion, execute the pattern
- * directly */
- int shell_style = STYLE_ECHO;
- int check_spaces;
- static bool did_find_nul = false;
- bool ampersent = false;
- // vimglob() function to define for Posix shell
- static char *sh_vimglob_func =
- "vimglob() { while [ $# -ge 1 ]; do echo \"$1\"; shift; done }; vimglob >";
-
- bool is_fish_shell =
-#if defined(UNIX)
- STRNCMP(invocation_path_tail(p_sh, NULL), "fish", 4) == 0;
-#else
- false;
-#endif
-
- *num_file = 0; // default: no files found
- *file = NULL;
-
- // If there are no wildcards, just copy the names to allocated memory.
- // Saves a lot of time, because we don't have to start a new shell.
- if (!have_wildcard(num_pat, pat)) {
- save_patterns(num_pat, pat, num_file, file);
- return OK;
- }
-
- // Don't allow any shell command in the sandbox.
- if (sandbox != 0 && check_secure()) {
- return FAIL;
- }
-
- // Don't allow the use of backticks in secure and restricted mode.
- if (secure || restricted) {
- for (i = 0; i < num_pat; i++) {
- if (vim_strchr(pat[i], '`') != NULL
- && (check_restricted() || check_secure())) {
- return FAIL;
- }
- }
- }
-
- // get a name for the temp file
- if ((tempname = vim_tempname()) == NULL) {
- EMSG(_(e_notmp));
- return FAIL;
- }
-
- // Let the shell expand the patterns and write the result into the temp
- // file.
- // STYLE_BT: NL separated
- // If expanding `cmd` execute it directly.
- // STYLE_GLOB: NUL separated
- // If we use *csh, "glob" will work better than "echo".
- // STYLE_PRINT: NL or NUL separated
- // If we use *zsh, "print -N" will work better than "glob".
- // STYLE_VIMGLOB: NL separated
- // If we use *sh*, we define "vimglob()".
- // STYLE_ECHO: space separated.
- // A shell we don't know, stay safe and use "echo".
- if (num_pat == 1 && *pat[0] == '`'
- && (len = STRLEN(pat[0])) > 2
- && *(pat[0] + len - 1) == '`') {
- shell_style = STYLE_BT;
- } else if ((len = STRLEN(p_sh)) >= 3) {
- if (STRCMP(p_sh + len - 3, "csh") == 0) {
- shell_style = STYLE_GLOB;
- } else if (STRCMP(p_sh + len - 3, "zsh") == 0) {
- shell_style = STYLE_PRINT;
- }
- }
- if (shell_style == STYLE_ECHO && strstr((char *)path_tail(p_sh),
- "sh") != NULL)
- shell_style = STYLE_VIMGLOB;
-
- // Compute the length of the command. We need 2 extra bytes: for the
- // optional '&' and for the NUL.
- // Worst case: "unset nonomatch; print -N >" plus two is 29
- len = STRLEN(tempname) + 29;
- if (shell_style == STYLE_VIMGLOB)
- len += STRLEN(sh_vimglob_func);
-
- for (i = 0; i < num_pat; i++) {
- // Count the length of the patterns in the same way as they are put in
- // "command" below.
- len++; // add space
- for (j = 0; pat[i][j] != NUL; j++) {
- if (vim_strchr(SHELL_SPECIAL, pat[i][j]) != NULL) {
- len++; // may add a backslash
- }
- len++;
- }
- }
-
- if (is_fish_shell) {
- len += sizeof("egin;"" end") - 1;
- }
-
- command = xmalloc(len);
-
- // Build the shell command:
- // - Set $nonomatch depending on EW_NOTFOUND (hopefully the shell
- // recognizes this).
- // - Add the shell command to print the expanded names.
- // - Add the temp file name.
- // - Add the file name patterns.
- if (shell_style == STYLE_BT) {
- // change `command; command& ` to (command; command )
- if (is_fish_shell) {
- STRCPY(command, "begin; ");
- } else {
- STRCPY(command, "(");
- }
- STRCAT(command, pat[0] + 1); // exclude first backtick
- p = command + STRLEN(command) - 1;
- if (is_fish_shell) {
- *p-- = ';';
- STRCAT(command, " end");
- } else {
- *p-- = ')'; // remove last backtick
- }
- while (p > command && ascii_iswhite(*p)) {
- p--;
- }
- if (*p == '&') { // remove trailing '&'
- ampersent = true;
- *p = ' ';
- }
- STRCAT(command, ">");
- } else {
- if (flags & EW_NOTFOUND)
- STRCPY(command, "set nonomatch; ");
- else
- STRCPY(command, "unset nonomatch; ");
- if (shell_style == STYLE_GLOB)
- STRCAT(command, "glob >");
- else if (shell_style == STYLE_PRINT)
- STRCAT(command, "print -N >");
- else if (shell_style == STYLE_VIMGLOB)
- STRCAT(command, sh_vimglob_func);
- else
- STRCAT(command, "echo >");
- }
-
- STRCAT(command, tempname);
-
- if (shell_style != STYLE_BT) {
- for (i = 0; i < num_pat; i++) {
- // Put a backslash before special
- // characters, except inside ``.
- bool intick = false;
-
- p = command + STRLEN(command);
- *p++ = ' ';
- for (j = 0; pat[i][j] != NUL; j++) {
- if (pat[i][j] == '`') {
- intick = !intick;
- } else if (pat[i][j] == '\\' && pat[i][j + 1] != NUL) {
- // Remove a backslash, take char literally. But keep
- // backslash inside backticks, before a special character
- // and before a backtick.
- if (intick
- || vim_strchr(SHELL_SPECIAL, pat[i][j + 1]) != NULL
- || pat[i][j + 1] == '`') {
- *p++ = '\\';
- }
- j++;
- } else if (!intick
- && ((flags & EW_KEEPDOLLAR) == 0 || pat[i][j] != '$')
- && vim_strchr(SHELL_SPECIAL, pat[i][j]) != NULL) {
- // Put a backslash before a special character, but not
- // when inside ``. And not for $var when EW_KEEPDOLLAR is
- // set.
- *p++ = '\\';
- }
-
- // Copy one character.
- *p++ = pat[i][j];
- }
- *p = NUL;
- }
- }
-
- if (flags & EW_SILENT) {
- shellopts |= kShellOptHideMess;
- }
-
- if (ampersent) {
- STRCAT(command, "&"); // put the '&' after the redirection
- }
-
- // Using zsh -G: If a pattern has no matches, it is just deleted from
- // the argument list, otherwise zsh gives an error message and doesn't
- // expand any other pattern.
- if (shell_style == STYLE_PRINT) {
- extra_shell_arg = (char_u *)"-G"; // Use zsh NULL_GLOB option
-
- // If we use -f then shell variables set in .cshrc won't get expanded.
- // vi can do it, so we will too, but it is only necessary if there is a "$"
- // in one of the patterns, otherwise we can still use the fast option.
- } else if (shell_style == STYLE_GLOB && !have_dollars(num_pat, pat)) {
- extra_shell_arg = (char_u *)"-f"; // Use csh fast option
- }
-
- // execute the shell command
- i = call_shell(
- command,
- shellopts,
- extra_shell_arg
- );
-
- // When running in the background, give it some time to create the temp
- // file, but don't wait for it to finish.
- if (ampersent) {
- os_delay(10L, true);
- }
-
- xfree(command);
-
- if (i) { // os_call_shell() failed
- os_remove((char *)tempname);
- xfree(tempname);
- // With interactive completion, the error message is not printed.
- if (!(flags & EW_SILENT)) {
- msg_putchar('\n'); // clear bottom line quickly
- cmdline_row = Rows - 1; // continue on last line
- MSG(_(e_wildexpand));
- msg_start(); // don't overwrite this message
- }
-
- // If a `cmd` expansion failed, don't list `cmd` as a match, even when
- // EW_NOTFOUND is given
- if (shell_style == STYLE_BT) {
- return FAIL;
- }
- goto notfound;
- }
-
- // read the names from the file into memory
- fd = fopen((char *)tempname, READBIN);
- if (fd == NULL) {
- // Something went wrong, perhaps a file name with a special char.
- if (!(flags & EW_SILENT)) {
- MSG(_(e_wildexpand));
- msg_start(); // don't overwrite this message
- }
- xfree(tempname);
- goto notfound;
- }
- int fseek_res = fseek(fd, 0L, SEEK_END);
- if (fseek_res < 0) {
- xfree(tempname);
- fclose(fd);
- return FAIL;
- }
- int64_t templen = ftell(fd); // get size of temp file
- if (templen < 0) {
- xfree(tempname);
- fclose(fd);
- return FAIL;
- }
-#if SIZEOF_LONG_LONG > SIZEOF_SIZE_T
- assert(templen <= (long long)SIZE_MAX);
-#endif
- len = (size_t)templen;
- fseek(fd, 0L, SEEK_SET);
- buffer = xmalloc(len + 1);
- // fread() doesn't terminate buffer with NUL;
- // appropriate termination (not always NUL) is done below.
- size_t readlen = fread((char *)buffer, 1, len, fd);
- fclose(fd);
- os_remove((char *)tempname);
- if (readlen != len) {
- // unexpected read error
- EMSG2(_(e_notread), tempname);
- xfree(tempname);
- xfree(buffer);
- return FAIL;
- }
- xfree(tempname);
-
- // file names are separated with Space
- if (shell_style == STYLE_ECHO) {
- buffer[len] = '\n'; // make sure the buffer ends in NL
- p = buffer;
- for (i = 0; *p != '\n'; i++) { // count number of entries
- while (*p != ' ' && *p != '\n') {
- p++;
- }
- p = skipwhite(p); // skip to next entry
- }
- // file names are separated with NL
- } else if (shell_style == STYLE_BT || shell_style == STYLE_VIMGLOB) {
- buffer[len] = NUL; // make sure the buffer ends in NUL
- p = buffer;
- for (i = 0; *p != NUL; i++) { // count number of entries
- while (*p != '\n' && *p != NUL) {
- p++;
- }
- if (*p != NUL) {
- p++;
- }
- p = skipwhite(p); // skip leading white space
- }
- // file names are separated with NUL
- } else {
- // Some versions of zsh use spaces instead of NULs to separate
- // results. Only do this when there is no NUL before the end of the
- // buffer, otherwise we would never be able to use file names with
- // embedded spaces when zsh does use NULs.
- // When we found a NUL once, we know zsh is OK, set did_find_nul and
- // don't check for spaces again.
- check_spaces = false;
- if (shell_style == STYLE_PRINT && !did_find_nul) {
- // If there is a NUL, set did_find_nul, else set check_spaces
- buffer[len] = NUL;
- if (len && (int)STRLEN(buffer) < (int)len)
- did_find_nul = true;
- else
- check_spaces = true;
- }
-
- // Make sure the buffer ends with a NUL. For STYLE_PRINT there
- // already is one, for STYLE_GLOB it needs to be added.
- if (len && buffer[len - 1] == NUL) {
- len--;
- } else {
- buffer[len] = NUL;
- }
- i = 0;
- for (p = buffer; p < buffer + len; p++) {
- if (*p == NUL || (*p == ' ' && check_spaces)) { // count entry
- i++;
- *p = NUL;
- }
- }
- if (len) {
- i++; // count last entry
- }
- }
- assert(buffer[len] == NUL || buffer[len] == '\n');
-
- if (i == 0) {
- // Can happen when using /bin/sh and typing ":e $NO_SUCH_VAR^I".
- // /bin/sh will happily expand it to nothing rather than returning an
- // error; and hey, it's good to check anyway -- webb.
- xfree(buffer);
- goto notfound;
- }
- *num_file = i;
- *file = xmalloc(sizeof(char_u *) * (size_t)i);
-
- // Isolate the individual file names.
- p = buffer;
- for (i = 0; i < *num_file; ++i) {
- (*file)[i] = p;
- // Space or NL separates
- if (shell_style == STYLE_ECHO || shell_style == STYLE_BT
- || shell_style == STYLE_VIMGLOB) {
- while (!(shell_style == STYLE_ECHO && *p == ' ')
- && *p != '\n' && *p != NUL) {
- p++;
- }
- if (p == buffer + len) { // last entry
- *p = NUL;
- } else {
- *p++ = NUL;
- p = skipwhite(p); // skip to next entry
- }
- } else { // NUL separates
- while (*p && p < buffer + len) { // skip entry
- p++;
- }
- p++; // skip NUL
- }
- }
-
- // Move the file names to allocated memory.
- for (j = 0, i = 0; i < *num_file; i++) {
- // Require the files to exist. Helps when using /bin/sh
- if (!(flags & EW_NOTFOUND) && !os_path_exists((*file)[i])) {
- continue;
- }
-
- // check if this entry should be included
- dir = (os_isdir((*file)[i]));
- if ((dir && !(flags & EW_DIR)) || (!dir && !(flags & EW_FILE)))
- continue;
-
- // Skip files that are not executable if we check for that.
- if (!dir && (flags & EW_EXEC)
- && !os_can_exe((char *)(*file)[i], NULL, !(flags & EW_SHELLCMD))) {
- continue;
- }
-
- p = xmalloc(STRLEN((*file)[i]) + 1 + dir);
- STRCPY(p, (*file)[i]);
- if (dir) {
- add_pathsep((char *)p); // add '/' to a directory name
- }
- (*file)[j++] = p;
- }
- xfree(buffer);
- *num_file = j;
-
- if (*num_file == 0) { // rejected all entries
- XFREE_CLEAR(*file);
- goto notfound;
- }
-
- return OK;
-
-notfound:
- if (flags & EW_NOTFOUND) {
- save_patterns(num_pat, pat, num_file, file);
- return OK;
- }
- return FAIL;
-
-}
-
-
-static void save_patterns(int num_pat, char_u **pat, int *num_file,
- char_u ***file)
-{
- int i;
- char_u *s;
-
- *file = xmalloc((size_t)num_pat * sizeof(char_u *));
-
- for (i = 0; i < num_pat; i++) {
- s = vim_strsave(pat[i]);
- // Be compatible with expand_filename(): halve the number of
- // backslashes.
- backslash_halve(s);
- (*file)[i] = s;
- }
- *num_file = num_pat;
-}
-
-static bool have_wildcard(int num, char_u **file)
-{
- int i;
-
- for (i = 0; i < num; i++)
- if (path_has_wildcard(file[i]))
- return true;
- return false;
-}
-
-static bool have_dollars(int num, char_u **file)
-{
- int i;
-
- for (i = 0; i < num; i++)
- if (vim_strchr(file[i], '$') != NULL)
- return true;
- return false;
-}
diff --git a/src/nvim/path.c b/src/nvim/path.c
index a53870acb8..31318f6bea 100644
--- a/src/nvim/path.c
+++ b/src/nvim/path.c
@@ -1120,10 +1120,22 @@ static bool has_env_var(char_u *p)
static bool has_special_wildchar(char_u *p)
{
for (; *p; MB_PTR_ADV(p)) {
- // Allow for escaping
- if (*p == '\\' && p[1] != NUL) {
+ // Disallow line break characters.
+ if (*p == '\r' || *p == '\n') {
+ break;
+ }
+ // Allow for escaping.
+ if (*p == '\\' && p[1] != NUL && p[1] != '\r' && p[1] != '\n') {
p++;
} else if (vim_strchr((char_u *)SPECIAL_WILDCHAR, *p) != NULL) {
+ // A { must be followed by a matching }.
+ if (*p == '{' && vim_strchr(p, '}') == NULL) {
+ continue;
+ }
+ // A quote and backtick must be followed by another one.
+ if ((*p == '`' || *p == '\'') && vim_strchr(p, *p) == NULL) {
+ continue;
+ }
return true;
}
}
@@ -1166,7 +1178,7 @@ int gen_expand_wildcards(int num_pat, char_u **pat, int *num_file,
*/
if (recursive)
#ifdef SPECIAL_WILDCHAR
- return mch_expand_wildcards(num_pat, pat, num_file, file, flags);
+ return os_expand_wildcards(num_pat, pat, num_file, file, flags);
#else
return FAIL;
#endif
@@ -1181,7 +1193,7 @@ int gen_expand_wildcards(int num_pat, char_u **pat, int *num_file,
for (int i = 0; i < num_pat; i++) {
if (has_special_wildchar(pat[i])
&& !(vim_backtick(pat[i]) && pat[i][1] == '=')) {
- return mch_expand_wildcards(num_pat, pat, num_file, file, flags);
+ return os_expand_wildcards(num_pat, pat, num_file, file, flags);
}
}
#endif
@@ -1221,8 +1233,8 @@ int gen_expand_wildcards(int num_pat, char_u **pat, int *num_file,
else if (has_env_var(p) || *p == '~') {
xfree(p);
ga_clear_strings(&ga);
- i = mch_expand_wildcards(num_pat, pat, num_file, file,
- flags | EW_KEEPDOLLAR);
+ i = os_expand_wildcards(num_pat, pat, num_file, file,
+ flags | EW_KEEPDOLLAR);
recursive = false;
return i;
}
diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c
index 4ba2a1032d..e06433892d 100644
--- a/src/nvim/popupmnu.c
+++ b/src/nvim/popupmnu.c
@@ -38,7 +38,8 @@ static int pum_width; // width of displayed pum items
static int pum_base_width; // width of pum items base
static int pum_kind_width; // width of pum items kind column
static int pum_extra_width; // width of extra stuff
-static int pum_scrollbar; // TRUE when scrollbar present
+static int pum_scrollbar; // one when scrollbar present, else zero
+static bool pum_rl; // true when popupmenu is drawn 'rightleft'
static int pum_anchor_grid; // grid where position is defined
static int pum_row; // top row of pum
@@ -62,9 +63,12 @@ static void pum_compute_size(void)
pum_kind_width = 0;
pum_extra_width = 0;
for (int i = 0; i < pum_size; i++) {
- int w = vim_strsize(pum_array[i].pum_text);
- if (pum_base_width < w) {
- pum_base_width = w;
+ int w;
+ if (pum_array[i].pum_text != NULL) {
+ w = vim_strsize(pum_array[i].pum_text);
+ if (pum_base_width < w) {
+ pum_base_width = w;
+ }
}
if (pum_array[i].pum_kind != NULL) {
w = vim_strsize(pum_array[i].pum_kind) + 1;
@@ -110,6 +114,8 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed,
|| (State == CMDLINE && ui_has(kUIWildmenu));
}
+ pum_rl = (curwin->w_p_rl && State != CMDLINE);
+
do {
// Mark the pum as visible already here,
// to avoid that must_redraw is set when 'cursorcolumn' is on.
@@ -127,7 +133,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed,
} else {
// anchor position: the start of the completed word
pum_win_row = curwin->w_wrow;
- if (curwin->w_p_rl) {
+ if (pum_rl) {
cursor_col = curwin->w_width - curwin->w_wcol - 1;
} else {
cursor_col = curwin->w_wcol;
@@ -270,16 +276,14 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed,
def_width = max_width;
}
- if ((((cursor_col < Columns - p_pw)
- || (cursor_col < Columns - max_width))
- && !curwin->w_p_rl)
- || (curwin->w_p_rl
- && ((cursor_col > p_pw) || (cursor_col > max_width)))) {
+ if ((((cursor_col < Columns - p_pw) || (cursor_col < Columns - max_width))
+ && !pum_rl)
+ || (pum_rl && ((cursor_col > p_pw) || (cursor_col > max_width)))) {
// align pum with "cursor_col"
pum_col = cursor_col;
// start with the maximum space available
- if (curwin->w_p_rl) {
+ if (pum_rl) {
pum_width = pum_col - pum_scrollbar + 1;
} else {
assert(Columns - pum_col - pum_scrollbar >= INT_MIN
@@ -297,19 +301,16 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed,
pum_width = (int)p_pw;
}
}
- } else if (((cursor_col > p_pw || cursor_col > max_width)
- && !curwin->w_p_rl)
- || (curwin->w_p_rl
- && (cursor_col < Columns - p_pw
- || cursor_col < Columns - max_width))) {
+ } else if (((cursor_col > p_pw || cursor_col > max_width) && !pum_rl)
+ || (pum_rl && (cursor_col < Columns - p_pw
+ || cursor_col < Columns - max_width))) {
// align pum edge with "cursor_col"
- if (curwin->w_p_rl
- && W_ENDCOL(curwin) < max_width + pum_scrollbar + 1) {
+ if (pum_rl && W_ENDCOL(curwin) < max_width + pum_scrollbar + 1) {
pum_col = cursor_col + max_width + pum_scrollbar + 1;
if (pum_col >= Columns) {
pum_col = Columns - 1;
}
- } else if (!curwin->w_p_rl) {
+ } else if (!pum_rl) {
if (curwin->w_wincol > Columns - max_width - pum_scrollbar
&& max_width <= p_pw) {
// use full width to end of the screen
@@ -320,7 +321,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed,
}
}
- if (curwin->w_p_rl) {
+ if (pum_rl) {
pum_width = pum_col - pum_scrollbar + 1;
} else {
pum_width = Columns - pum_col - pum_scrollbar;
@@ -328,7 +329,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed,
if (pum_width < p_pw) {
pum_width = (int)p_pw;
- if (curwin->w_p_rl) {
+ if (pum_rl) {
if (pum_width > pum_col) {
pum_width = pum_col;
}
@@ -346,7 +347,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed,
}
} else if (Columns < def_width) {
// not enough room, will use what we have
- if (curwin->w_p_rl) {
+ if (pum_rl) {
assert(Columns - 1 >= INT_MIN);
pum_col = (int)(Columns - 1);
} else {
@@ -360,7 +361,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed,
max_width = (int)p_pw;
}
- if (curwin->w_p_rl) {
+ if (pum_rl) {
pum_col = max_width - 1;
} else {
assert(Columns - max_width >= INT_MIN
@@ -399,7 +400,7 @@ void pum_redraw(void)
int grid_width = pum_width;
int col_off = 0;
bool extra_space = false;
- if (curwin->w_p_rl) {
+ if (pum_rl) {
col_off = pum_width;
if (pum_col < curwin->w_wincol + curwin->w_width - 1) {
grid_width += 1;
@@ -460,7 +461,7 @@ void pum_redraw(void)
// prepend a space if there is room
if (extra_space) {
- if (curwin->w_p_rl) {
+ if (pum_rl) {
grid_putchar(&pum_grid, ' ', row, col_off + 1, attr);
} else {
grid_putchar(&pum_grid, ' ', row, col_off - 1, attr);
@@ -507,7 +508,7 @@ void pum_redraw(void)
st = (char_u *)transstr((const char *)s);
*p = saved;
- if (curwin->w_p_rl) {
+ if (pum_rl) {
char_u *rt = reverse_text(st);
char_u *rt_start = rt;
int size = vim_strsize(rt);
@@ -542,7 +543,7 @@ void pum_redraw(void)
}
// Display two spaces for a Tab.
- if (curwin->w_p_rl) {
+ if (pum_rl) {
grid_puts_len(&pum_grid, (char_u *)" ", 2, row, col - 1,
attr);
col -= 2;
@@ -577,7 +578,7 @@ void pum_redraw(void)
break;
}
- if (curwin->w_p_rl) {
+ if (pum_rl) {
grid_fill(&pum_grid, row, row + 1, col_off - pum_base_width - n + 1,
col + 1, ' ', ' ', attr);
col = col_off - pum_base_width - n + 1;
@@ -589,7 +590,7 @@ void pum_redraw(void)
totwidth = pum_base_width + n;
}
- if (curwin->w_p_rl) {
+ if (pum_rl) {
grid_fill(&pum_grid, row, row + 1, col_off - pum_width + 1, col + 1,
' ', ' ', attr);
} else {
@@ -598,7 +599,7 @@ void pum_redraw(void)
}
if (pum_scrollbar > 0) {
- if (curwin->w_p_rl) {
+ if (pum_rl) {
grid_putchar(&pum_grid, ' ', row, col_off - pum_width,
i >= thumb_pos && i < thumb_pos + thumb_heigth
? attr_thumb : attr_scroll);
@@ -910,10 +911,17 @@ void pum_set_event_info(dict_T *dict)
if (!pum_visible()) {
return;
}
- tv_dict_add_nr(dict, S_LEN("height"), pum_height);
- tv_dict_add_nr(dict, S_LEN("width"), pum_width);
- tv_dict_add_nr(dict, S_LEN("row"), pum_row);
- tv_dict_add_nr(dict, S_LEN("col"), pum_col);
+ double w, h, r, c;
+ if (!ui_pum_get_pos(&w, &h, &r, &c)) {
+ w = (double)pum_width;
+ h = (double)pum_height;
+ r = (double)pum_row;
+ c = (double)pum_col;
+ }
+ tv_dict_add_float(dict, S_LEN("height"), h);
+ tv_dict_add_float(dict, S_LEN("width"), w);
+ tv_dict_add_float(dict, S_LEN("row"), r);
+ tv_dict_add_float(dict, S_LEN("col"), c);
tv_dict_add_nr(dict, S_LEN("size"), pum_size);
tv_dict_add_special(dict, S_LEN("scrollbar"),
pum_scrollbar ? kSpecialVarTrue : kSpecialVarFalse);
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index 71c6f06ac0..484168e798 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -3845,7 +3845,7 @@ static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last)
*dirname = NUL;
// Add one line for each error
- if (old_last == NULL) {
+ if (old_last == NULL || old_last->qf_next == NULL) {
qfp = qfl->qf_start;
lnum = 0;
} else {
@@ -4775,10 +4775,10 @@ static void vgr_display_fname(char_u *fname)
static buf_T *vgr_load_dummy_buf(char_u *fname, char_u *dirname_start,
char_u *dirname_now)
{
- char_u *save_ei = NULL;
-
// Don't do Filetype autocommands to avoid loading syntax and
// indent scripts, a great speed improvement.
+ char_u *save_ei = au_event_disable(",Filetype");
+
long save_mls = p_mls;
p_mls = 0;
@@ -5134,6 +5134,7 @@ theend:
// Restore current working directory to "dirname_start" if they differ, taking
// into account whether it is set locally or globally.
static void restore_start_dir(char_u *dirname_start)
+ FUNC_ATTR_NONNULL_ALL
{
char_u *dirname_now = xmalloc(MAXPATHL);
@@ -5251,8 +5252,29 @@ load_dummy_buffer (
// directory to "dirname_start" prior to returning, if autocmds or the
// 'autochdir' option have changed it.
static void wipe_dummy_buffer(buf_T *buf, char_u *dirname_start)
+ FUNC_ATTR_NONNULL_ALL
{
- if (curbuf != buf) { // safety check
+ // If any autocommand opened a window on the dummy buffer, close that
+ // window. If we can't close them all then give up.
+ while (buf->b_nwindows > 0) {
+ bool did_one = false;
+
+ if (firstwin->w_next != NULL) {
+ for (win_T *wp = firstwin; wp != NULL; wp = wp->w_next) {
+ if (wp->w_buffer == buf) {
+ if (win_close(wp, false) == OK) {
+ did_one = true;
+ }
+ break;
+ }
+ }
+ }
+ if (!did_one) {
+ return;
+ }
+ }
+
+ if (curbuf != buf && buf->b_nwindows == 0) { // safety check
cleanup_T cs;
// Reset the error/interrupt/exception state here so that aborting()
diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c
index 90dc8ab90f..34553fcec4 100644
--- a/src/nvim/regexp.c
+++ b/src/nvim/regexp.c
@@ -56,6 +56,7 @@
#include "nvim/regexp.h"
#include "nvim/charset.h"
#include "nvim/eval.h"
+#include "nvim/eval/userfunc.h"
#include "nvim/ex_cmds2.h"
#include "nvim/mark.h"
#include "nvim/memline.h"
@@ -6491,20 +6492,24 @@ typedef struct {
static regsubmatch_T rsm; // can only be used when can_f_submatch is true
-/// Put the submatches in "argv[0]" which is a list passed into call_func() by
-/// vim_regsub_both().
-static int fill_submatch_list(int argc, typval_T *argv, int argcount)
+/// Put the submatches in "argv[argskip]" which is a list passed into
+/// call_func() by vim_regsub_both().
+static int fill_submatch_list(int argc FUNC_ATTR_UNUSED, typval_T *argv,
+ int argskip, int argcount)
+ FUNC_ATTR_NONNULL_ALL
{
- if (argcount == 0) {
- // called function doesn't take an argument
- return 0;
+ typval_T *listarg = argv + argskip;
+
+ if (argcount == argskip) {
+ // called function doesn't take a submatches argument
+ return argskip;
}
// Relies on sl_list to be the first item in staticList10_T.
- tv_list_init_static10((staticList10_T *)argv->vval.v_list);
+ tv_list_init_static10((staticList10_T *)listarg->vval.v_list);
// There are always 10 list items in staticList10_T.
- listitem_T *li = tv_list_first(argv->vval.v_list);
+ listitem_T *li = tv_list_first(listarg->vval.v_list);
for (int i = 0; i < 10; i++) {
char_u *s = rsm.sm_match->startp[i];
if (s == NULL || rsm.sm_match->endp[i] == NULL) {
@@ -6516,7 +6521,7 @@ static int fill_submatch_list(int argc, typval_T *argv, int argcount)
TV_LIST_ITEM_TV(li)->vval.v_string = s;
li = TV_LIST_ITEM_NEXT(argv->vval.v_list, li);
}
- return 1;
+ return argskip + 1;
}
static void clear_submatch_list(staticList10_T *sl)
@@ -6679,10 +6684,15 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest,
// fill_submatch_list() was called.
clear_submatch_list(&matchList);
}
- char buf[NUMBUFLEN];
- eval_result = (char_u *)tv_get_string_buf_chk(&rettv, buf);
- if (eval_result != NULL) {
- eval_result = vim_strsave(eval_result);
+ if (rettv.v_type == VAR_UNKNOWN) {
+ // something failed, no need to report another error
+ eval_result = NULL;
+ } else {
+ char buf[NUMBUFLEN];
+ eval_result = (char_u *)tv_get_string_buf_chk(&rettv, buf);
+ if (eval_result != NULL) {
+ eval_result = vim_strsave(eval_result);
+ }
}
tv_clear(&rettv);
} else {
diff --git a/src/nvim/regexp_defs.h b/src/nvim/regexp_defs.h
index 5e5b19b63f..116bfee91e 100644
--- a/src/nvim/regexp_defs.h
+++ b/src/nvim/regexp_defs.h
@@ -91,7 +91,7 @@ typedef struct {
char_u *regmust;
int regmlen;
char_u reghasz;
- char_u program[1]; /* actually longer.. */
+ char_u program[1]; // actually longer..
} bt_regprog_T;
// Structure representing a NFA state.
@@ -102,7 +102,7 @@ struct nfa_state {
nfa_state_T *out;
nfa_state_T *out1;
int id;
- int lastlist[2]; /* 0: normal, 1: recursive */
+ int lastlist[2]; // 0: normal, 1: recursive
int val;
};
@@ -116,19 +116,19 @@ typedef struct {
unsigned re_engine;
unsigned re_flags; ///< Second argument for vim_regcomp().
- nfa_state_T *start; /* points into state[] */
+ nfa_state_T *start; // points into state[]
- int reganch; /* pattern starts with ^ */
- int regstart; /* char at start of pattern */
- char_u *match_text; /* plain text to match with */
+ int reganch; // pattern starts with ^
+ int regstart; // char at start of pattern
+ char_u *match_text; // plain text to match with
- int has_zend; /* pattern contains \ze */
- int has_backref; /* pattern contains \1 .. \9 */
+ int has_zend; // pattern contains \ze
+ int has_backref; // pattern contains \1 .. \9
int reghasz;
char_u *pattern;
- int nsubexp; /* number of () */
+ int nsubexp; // number of ()
int nstate;
- nfa_state_T state[1]; /* actually longer.. */
+ nfa_state_T state[1]; // actually longer..
} nfa_regprog_T;
/*
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index ae38f657cd..9e958663aa 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -334,10 +334,10 @@ int update_screen(int type)
}
return FAIL;
}
+ updating_screen = 1;
- updating_screen = TRUE;
- ++display_tick; /* let syntax code know we're in a next round of
- * display updating */
+ display_tick++; // let syntax code know we're in a next round of
+ // display updating
// Tricky: vim code can reset msg_scrolled behind our back, so need
// separate bookkeeping for now.
@@ -370,6 +370,17 @@ int update_screen(int type)
grid_clear_line(&default_grid, default_grid.line_offset[i],
Columns, false);
}
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (wp->w_floating) {
+ continue;
+ }
+ if (W_ENDROW(wp) > valid) {
+ wp->w_redr_type = MAX(wp->w_redr_type, NOT_VALID);
+ }
+ if (W_ENDROW(wp) + wp->w_status_height > valid) {
+ wp->w_redr_status = true;
+ }
+ }
}
msg_grid_set_pos(Rows-p_ch, false);
msg_grid_invalid = false;
@@ -406,7 +417,7 @@ int update_screen(int type)
need_wait_return = false;
}
- win_ui_flush_positions();
+ win_ui_flush();
msg_ext_check_clear();
/* reset cmdline_row now (may have been changed temporarily) */
@@ -554,7 +565,7 @@ int update_screen(int type)
wp->w_buffer->b_mod_set = false;
}
- updating_screen = FALSE;
+ updating_screen = 0;
/* Clear or redraw the command line. Done last, because scrolling may
* mess up the command line. */
@@ -623,9 +634,17 @@ bool win_cursorline_standout(const win_T *wp)
|| (wp->w_p_cole > 0 && (VIsual_active || !conceal_cursor_line(wp)));
}
-static DecorationState decorations;
+static DecorationRedrawState decorations;
bool decorations_active = false;
+void decorations_add_luahl_attr(int attr_id,
+ int start_row, int start_col,
+ int end_row, int end_col)
+{
+ kv_push(decorations.active,
+ ((HlRange){ start_row, start_col, end_row, end_col, attr_id, NULL }));
+}
+
/*
* Update a single window.
*
@@ -1217,6 +1236,8 @@ static void win_update(win_T *wp)
srow = 0;
lnum = wp->w_topline; // first line shown in window
+ decorations_active = decorations_redraw_reset(buf, &decorations);
+
if (buf->b_luahl && buf->b_luahl_window != LUA_NOREF) {
Error err = ERROR_INIT;
FIXED_TEMP_ARRAY(args, 4);
@@ -1237,7 +1258,6 @@ static void win_update(win_T *wp)
}
}
- decorations_active = extmark_decorations_reset(buf, &decorations);
for (;; ) {
/* stop updating when reached the end of the window (check for _past_
@@ -1609,6 +1629,7 @@ static void win_update(win_T *wp)
* changes are relevant).
*/
wp->w_valid |= VALID_BOTLINE;
+ wp->w_viewport_invalid = true;
if (wp == curwin && wp->w_botline != old_botline && !recursive) {
recursive = TRUE;
curwin->w_valid &= ~VALID_TOPLINE;
@@ -1628,7 +1649,7 @@ static void win_update(win_T *wp)
/* restore got_int, unless CTRL-C was hit while redrawing */
if (!got_int)
got_int = save_got_int;
-}
+} // NOLINT(readability/fn_size)
/// Returns width of the signcolumn that should be used for the whole window
///
@@ -1720,11 +1741,36 @@ static int advance_color_col(int vcol, int **color_cols)
return **color_cols >= 0;
}
+// Returns the next grid column.
+static int text_to_screenline(win_T *wp, char_u *text, int col, int off)
+ FUNC_ATTR_NONNULL_ALL
+{
+ int idx = wp->w_p_rl ? off : off + col;
+ LineState s = LINE_STATE(text);
+
+ while (*s.p != NUL) {
+ // TODO(bfredl): cargo-culted from the old Vim code:
+ // if(col + cells > wp->w_width - (wp->w_p_rl ? col : 0)) { break; }
+ // This is obvious wrong. If Vim ever fixes this, solve for "cells" again
+ // in the correct condition.
+ const int maxcells = wp->w_grid.Columns - col - (wp->w_p_rl ? col : 0);
+ const int cells = line_putchar(&s, &linebuf_char[idx], maxcells,
+ wp->w_p_rl);
+ if (cells == -1) {
+ break;
+ }
+ col += cells;
+ idx += cells;
+ }
+
+ return col;
+}
+
// Compute the width of the foldcolumn. Based on 'foldcolumn' and how much
// space is available for window "wp", minus "col".
static int compute_foldcolumn(win_T *wp, int col)
{
- int fdc = wp->w_p_fdc;
+ int fdc = win_fdccol_count(wp);
int wmw = wp == curwin && p_wmw == 0 ? 1 : p_wmw;
int wwidth = wp->w_grid.Columns;
@@ -1921,29 +1967,7 @@ static void fold_line(win_T *wp, long fold_count, foldinfo_T *foldinfo, linenr_T
// 5. move the text to linebuf_char[off]. Fill up with "fold".
// Right-left text is put in columns 0 - number-col, normal text is put
// in columns number-col - window-width.
- int idx;
-
- if (wp->w_p_rl) {
- idx = off;
- } else {
- idx = off + col;
- }
-
- LineState s = LINE_STATE(text);
-
- while (*s.p != NUL) {
- // TODO(bfredl): cargo-culted from the old Vim code:
- // if(col + cells > wp->w_width - (wp->w_p_rl ? col : 0)) { break; }
- // This is obvious wrong. If Vim ever fixes this, solve for "cells" again
- // in the correct condition.
- int maxcells = wp->w_grid.Columns - col - (wp->w_p_rl ? col : 0);
- int cells = line_putchar(&s, &linebuf_char[idx], maxcells, wp->w_p_rl);
- if (cells == -1) {
- break;
- }
- col += cells;
- idx += cells;
- }
+ col = text_to_screenline(wp, text, col, off);
/* Fill the rest of the line with the fold filler */
if (wp->w_p_rl)
@@ -2195,10 +2219,10 @@ win_line (
int n_skip = 0; /* nr of chars to skip for 'nowrap' */
- int fromcol = 0, tocol = 0; // start/end of inverting
+ int fromcol = -10; // start of inverting
+ int tocol = MAXCOL; // end of inverting
int fromcol_prev = -2; // start of inverting after cursor
- int noinvcur = false; // don't invert the cursor
- pos_T *top, *bot;
+ bool noinvcur = false; // don't invert the cursor
int lnum_in_visual_area = false;
pos_T pos;
long v;
@@ -2300,6 +2324,8 @@ win_line (
char *luatext = NULL;
+ buf_T *buf = wp->w_buffer;
+
if (!number_only) {
// To speed up the loop below, set extra_check when there is linebreak,
// trailing white space and/or syntax processing to be done.
@@ -2322,8 +2348,31 @@ win_line (
}
if (decorations_active) {
- has_decorations = extmark_decorations_line(wp->w_buffer, lnum-1,
- &decorations);
+ if (buf->b_luahl && buf->b_luahl_line != LUA_NOREF) {
+ Error err = ERROR_INIT;
+ FIXED_TEMP_ARRAY(args, 3);
+ args.items[0] = WINDOW_OBJ(wp->handle);
+ args.items[1] = BUFFER_OBJ(buf->handle);
+ args.items[2] = INTEGER_OBJ(lnum-1);
+ lua_attr_active = true;
+ extra_check = true;
+ Object o = executor_exec_lua_cb(buf->b_luahl_line, "line",
+ args, true, &err);
+ lua_attr_active = false;
+ if (o.type == kObjectTypeString) {
+ // TODO(bfredl): this is a bit of a hack. A final API should use an
+ // "unified" interface where luahl can add both bufhl and virttext
+ luatext = o.data.string.data;
+ do_virttext = true;
+ } else if (ERROR_SET(&err)) {
+ ELOG("error in luahl line: %s", err.msg);
+ luatext = err.msg;
+ do_virttext = true;
+ }
+ }
+
+ has_decorations = decorations_redraw_line(wp->w_buffer, lnum-1,
+ &decorations);
if (has_decorations) {
extra_check = true;
}
@@ -2371,27 +2420,28 @@ win_line (
capcol_lnum = 0;
}
- //
- // handle visual active in this window
- //
- fromcol = -10;
- tocol = MAXCOL;
+ // handle Visual active in this window
if (VIsual_active && wp->w_buffer == curwin->w_buffer) {
- // Visual is after curwin->w_cursor
+ pos_T *top, *bot;
+
if (ltoreq(curwin->w_cursor, VIsual)) {
+ // Visual is after curwin->w_cursor
top = &curwin->w_cursor;
bot = &VIsual;
- } else { // Visual is before curwin->w_cursor
+ } else {
+ // Visual is before curwin->w_cursor
top = &VIsual;
bot = &curwin->w_cursor;
}
lnum_in_visual_area = (lnum >= top->lnum && lnum <= bot->lnum);
- if (VIsual_mode == Ctrl_V) { // block mode
+ if (VIsual_mode == Ctrl_V) {
+ // block mode
if (lnum_in_visual_area) {
fromcol = wp->w_old_cursor_fcol;
tocol = wp->w_old_cursor_lcol;
}
- } else { // non-block mode
+ } else {
+ // non-block mode
if (lnum > top->lnum && lnum <= bot->lnum) {
fromcol = 0;
} else if (lnum == top->lnum) {
@@ -2521,41 +2571,6 @@ win_line (
line = ml_get_buf(wp->w_buffer, lnum, FALSE);
ptr = line;
- buf_T *buf = wp->w_buffer;
- if (buf->b_luahl && buf->b_luahl_line != LUA_NOREF) {
- size_t size = STRLEN(line);
- if (lua_attr_bufsize < size) {
- xfree(lua_attr_buf);
- lua_attr_buf = xcalloc(size, sizeof(*lua_attr_buf));
- lua_attr_bufsize = size;
- } else if (lua_attr_buf) {
- memset(lua_attr_buf, 0, size * sizeof(*lua_attr_buf));
- }
- Error err = ERROR_INIT;
- // TODO(bfredl): build a macro for the "static array" pattern
- // in buf_updates_send_changes?
- FIXED_TEMP_ARRAY(args, 3);
- args.items[0] = WINDOW_OBJ(wp->handle);
- args.items[1] = BUFFER_OBJ(buf->handle);
- args.items[2] = INTEGER_OBJ(lnum-1);
- lua_attr_active = true;
- extra_check = true;
- Object o = executor_exec_lua_cb(buf->b_luahl_line, "line",
- args, true, &err);
- lua_attr_active = false;
- if (o.type == kObjectTypeString) {
- // TODO(bfredl): this is a bit of a hack. A final API should use an
- // "unified" interface where luahl can add both bufhl and virttext
- luatext = o.data.string.data;
- do_virttext = true;
- } else if (ERROR_SET(&err)) {
- ELOG("error in luahl line: %s", err.msg);
- luatext = err.msg;
- do_virttext = true;
- api_clear_error(&err);
- }
- }
-
if (has_spell && !number_only) {
// For checking first word with a capital skip white space.
if (cap_col == 0) {
@@ -2951,11 +2966,11 @@ win_line (
}
}
- if (wp->w_p_brisbr && draw_state == WL_BRI - 1
+ if (wp->w_briopt_sbr && draw_state == WL_BRI - 1
&& n_extra == 0 && *p_sbr != NUL) {
// draw indent after showbreak value
draw_state = WL_BRI;
- } else if (wp->w_p_brisbr && draw_state == WL_SBR && n_extra == 0) {
+ } else if (wp->w_briopt_sbr && draw_state == WL_SBR && n_extra == 0) {
// after the showbreak, draw the breakindent
draw_state = WL_BRI - 1;
}
@@ -2979,7 +2994,7 @@ win_line (
c_final = NUL;
n_extra =
get_breakindent_win(wp, ml_get_buf(wp->w_buffer, lnum, false));
- if (wp->w_skipcol > 0 && wp->w_p_wrap) {
+ if (wp->w_skipcol > 0 && wp->w_p_wrap && wp->w_briopt_sbr) {
need_showbreak = false;
}
// Correct end of highlighted area for 'breakindent',
@@ -3149,8 +3164,8 @@ win_line (
shl->endcol += (*mb_ptr2len)(line + shl->endcol);
}
- /* Loop to check if the match starts at the
- * current position */
+ // Loop to check if the match starts at the
+ // current position
continue;
}
}
@@ -3527,8 +3542,8 @@ win_line (
}
if (has_decorations && v > 0) {
- int extmark_attr = extmark_decorations_col(wp->w_buffer, (colnr_T)v-1,
- &decorations);
+ int extmark_attr = decorations_redraw_col(wp->w_buffer, (colnr_T)v-1,
+ &decorations);
if (extmark_attr != 0) {
if (!attr_pri) {
char_attr = hl_combine_attr(char_attr, extmark_attr);
@@ -3538,15 +3553,6 @@ win_line (
}
}
- // TODO(bfredl): luahl should reuse the "active decorations" buffer
- if (buf->b_luahl && v > 0 && v < (long)lua_attr_bufsize+1) {
- if (!attr_pri) {
- char_attr = hl_combine_attr(char_attr, lua_attr_buf[v-1]);
- } else {
- char_attr = hl_combine_attr(lua_attr_buf[v-1], char_attr);
- }
- }
-
if (wp->w_buffer->terminal) {
char_attr = hl_combine_attr(term_attrs[vcol], char_attr);
}
@@ -3646,8 +3652,9 @@ win_line (
tab_len += n_extra - tab_len;
}
- /* if n_extra > 0, it gives the number of chars to use for
- * a tab, else we need to calculate the width for a tab */
+ // if n_extra > 0, it gives the number of chars
+ // to use for a tab, else we need to calculate the width
+ // for a tab
int len = (tab_len * mb_char2len(wp->w_p_lcs_chars.tab2));
if (n_extra > 0) {
len += n_extra - tab_len;
@@ -3659,10 +3666,16 @@ win_line (
xfree(p_extra_free);
p_extra_free = p;
for (i = 0; i < tab_len; i++) {
- utf_char2bytes(wp->w_p_lcs_chars.tab2, p);
- p += mb_char2len(wp->w_p_lcs_chars.tab2);
- n_extra += mb_char2len(wp->w_p_lcs_chars.tab2)
- - (saved_nextra > 0 ? 1: 0);
+ int lcs = wp->w_p_lcs_chars.tab2;
+
+ // if tab3 is given, need to change the char
+ // for tab
+ if (wp->w_p_lcs_chars.tab3 && i == tab_len - 1) {
+ lcs = wp->w_p_lcs_chars.tab3;
+ }
+ utf_char2bytes(lcs, p);
+ p += mb_char2len(lcs);
+ n_extra += mb_char2len(lcs) - (saved_nextra > 0 ? 1 : 0);
}
p_extra = p_extra_free;
@@ -4031,8 +4044,7 @@ win_line (
kv_push(virt_text, ((VirtTextChunk){ .text = luatext, .hl_id = 0 }));
do_virttext = true;
} else if (has_decorations) {
- VirtText *vp = extmark_decorations_virt_text(wp->w_buffer,
- &decorations);
+ VirtText *vp = decorations_redraw_virt_text(wp->w_buffer, &decorations);
if (vp) {
virt_text = *vp;
do_virttext = true;
@@ -5702,6 +5714,7 @@ void grid_puts_line_flush(bool set_cursor)
static void start_search_hl(void)
{
if (p_hls && !no_hlsearch) {
+ end_search_hl(); // just in case it wasn't called before
last_pat_prog(&search_hl.rm);
// Set the time limit to 'redrawtime'.
search_hl.tm = profile_setlimit(p_rdt);
@@ -5724,12 +5737,11 @@ static void end_search_hl(void)
* Init for calling prepare_search_hl().
*/
static void init_search_hl(win_T *wp)
+ FUNC_ATTR_NONNULL_ALL
{
- matchitem_T *cur;
-
- /* Setup for match and 'hlsearch' highlighting. Disable any previous
- * match */
- cur = wp->w_match_head;
+ // 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)
@@ -5739,7 +5751,7 @@ static void init_search_hl(win_T *wp)
cur->hl.buf = wp->w_buffer;
cur->hl.lnum = 0;
cur->hl.first_lnum = 0;
- /* Set the time limit to 'redrawtime'. */
+ // Set the time limit to 'redrawtime'.
cur->hl.tm = profile_setlimit(p_rdt);
cur = cur->next;
}
@@ -5755,18 +5767,16 @@ static void init_search_hl(win_T *wp)
* Advance to the match in window "wp" line "lnum" or past it.
*/
static void prepare_search_hl(win_T *wp, 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 */
- int shl_flag; /* flag to indicate whether search_hl
- has been processed or not */
- int n;
-
- /*
- * 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.
- */
+ 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) {
@@ -5793,7 +5803,7 @@ static void prepare_search_hl(win_T *wp, linenr_T lnum)
}
bool pos_inprogress = true; // mark that a position match search is
// in progress
- n = 0;
+ int n = 0;
while (shl->first_lnum < lnum && (shl->rm.regprog != NULL
|| (cur != NULL && pos_inprogress))) {
next_search_hl(wp, shl, shl->first_lnum, (colnr_T)n,
@@ -5831,6 +5841,7 @@ next_search_hl (
colnr_T mincol, /* minimal column for a match */
matchitem_T *cur /* to retrieve match positions if any */
)
+ FUNC_ATTR_NONNULL_ARG(2)
{
linenr_T l;
colnr_T matchcol;
@@ -5838,11 +5849,10 @@ next_search_hl (
int save_called_emsg = called_emsg;
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.
- */
+ // 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;
@@ -5856,22 +5866,21 @@ next_search_hl (
*/
called_emsg = FALSE;
for (;; ) {
- /* Stop searching after passing the time limit. */
+ // 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)
+ // 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)) {
+ } 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;
@@ -5888,8 +5897,8 @@ next_search_hl (
shl->lnum = lnum;
if (shl->rm.regprog != NULL) {
- /* Remember whether shl->rm is using a copy of the regprog in
- * cur->match. */
+ // 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
@@ -5918,7 +5927,7 @@ next_search_hl (
nmatched = next_search_hl_pos(shl, lnum, &(cur->pos), matchcol);
}
if (nmatched == 0) {
- shl->lnum = 0; /* no match found */
+ shl->lnum = 0; // no match found
break;
}
if (shl->rm.startpos[0].lnum > 0
@@ -5926,7 +5935,7 @@ next_search_hl (
|| nmatched > 1
|| shl->rm.endpos[0].col > mincol) {
shl->lnum += shl->rm.startpos[0].lnum;
- break; /* useful match found */
+ break; // useful match found
}
// Restore called_emsg for assert_fails().
@@ -5943,6 +5952,7 @@ next_search_hl_pos(
posmatch_T *posmatch, // match positions
colnr_T mincol // minimal column for a match
)
+ FUNC_ATTR_NONNULL_ALL
{
int i;
int found = -1;
@@ -6097,9 +6107,10 @@ void check_for_delay(int check_msg_scroll)
&& emsg_silent == 0) {
ui_flush();
os_delay(1000L, true);
- emsg_on_display = FALSE;
- if (check_msg_scroll)
- msg_scroll = FALSE;
+ emsg_on_display = false;
+ if (check_msg_scroll) {
+ msg_scroll = false;
+ }
}
}
diff --git a/src/nvim/search.c b/src/nvim/search.c
index 3ee9777805..23086c629b 100644
--- a/src/nvim/search.c
+++ b/src/nvim/search.c
@@ -1192,6 +1192,7 @@ int do_search(
len = STRLEN(p) + off_len + 3;
}
+ xfree(msgbuf);
msgbuf = xmalloc(len);
{
memset(msgbuf, ' ', len);
@@ -2227,6 +2228,8 @@ showmatch(
pos_T *lpos, save_cursor;
pos_T mpos;
colnr_T vcol;
+ long *so = curwin->w_p_so >= 0 ? &curwin->w_p_so : &p_so;
+ long *siso = curwin->w_p_siso >= 0 ? &curwin->w_p_siso : &p_siso;
long save_so;
long save_siso;
int save_state;
@@ -2262,23 +2265,24 @@ showmatch(
&& vcol < curwin->w_leftcol + curwin->w_width_inner)) {
mpos = *lpos; // save the pos, update_screen() may change it
save_cursor = curwin->w_cursor;
- save_so = p_so;
- save_siso = p_siso;
- /* Handle "$" in 'cpo': If the ')' is typed on top of the "$",
- * stop displaying the "$". */
- if (dollar_vcol >= 0 && dollar_vcol == curwin->w_virtcol)
+ save_so = *so;
+ save_siso = *siso;
+ // Handle "$" in 'cpo': If the ')' is typed on top of the "$",
+ // stop displaying the "$".
+ if (dollar_vcol >= 0 && dollar_vcol == curwin->w_virtcol) {
dollar_vcol = -1;
- ++curwin->w_virtcol; /* do display ')' just before "$" */
- update_screen(VALID); /* show the new char first */
+ }
+ curwin->w_virtcol++; // do display ')' just before "$"
+ update_screen(VALID); // show the new char first
save_dollar_vcol = dollar_vcol;
save_state = State;
State = SHOWMATCH;
- ui_cursor_shape(); /* may show different cursor shape */
- curwin->w_cursor = mpos; /* move to matching char */
- p_so = 0; /* don't use 'scrolloff' here */
- p_siso = 0; /* don't use 'sidescrolloff' here */
- showruler(FALSE);
+ ui_cursor_shape(); // may show different cursor shape
+ curwin->w_cursor = mpos; // move to matching char
+ *so = 0; // don't use 'scrolloff' here
+ *siso = 0; // don't use 'sidescrolloff' here
+ showruler(false);
setcursor();
ui_flush();
/* Restore dollar_vcol(), because setcursor() may call curs_rows()
@@ -2294,11 +2298,11 @@ showmatch(
os_delay(p_mat * 100L, true);
else if (!char_avail())
os_delay(p_mat * 100L, false);
- curwin->w_cursor = save_cursor; /* restore cursor position */
- p_so = save_so;
- p_siso = save_siso;
+ curwin->w_cursor = save_cursor; // restore cursor position
+ *so = save_so;
+ *siso = save_siso;
State = save_state;
- ui_cursor_shape(); /* may show different cursor shape */
+ ui_cursor_shape(); // may show different cursor shape
}
}
}
diff --git a/src/nvim/shada.c b/src/nvim/shada.c
index 2306da94c6..19a14f340b 100644
--- a/src/nvim/shada.c
+++ b/src/nvim/shada.c
@@ -177,7 +177,7 @@ typedef enum {
/// Possible results when reading ShaDa file
typedef enum {
- kSDReadStatusSuccess, ///< Reading was successfull.
+ kSDReadStatusSuccess, ///< Reading was successful.
kSDReadStatusFinished, ///< Nothing more to read.
kSDReadStatusReadError, ///< Failed to read from file.
kSDReadStatusNotShaDa, ///< Input is most likely not a ShaDa file.
@@ -186,11 +186,11 @@ typedef enum {
/// Possible results of shada_write function.
typedef enum {
- kSDWriteSuccessfull, ///< Writing was successfull.
- kSDWriteReadNotShada, ///< Writing was successfull, but when reading it
+ kSDWriteSuccessfull, ///< Writing was successful.
+ kSDWriteReadNotShada, ///< Writing was successful, but when reading it
///< attempted to read file that did not look like
///< a ShaDa file.
- kSDWriteFailed, ///< Writing was not successfull (e.g. because there
+ kSDWriteFailed, ///< Writing was not successful (e.g. because there
///< was no space left on device).
kSDWriteIgnError, ///< Writing resulted in a error which can be ignored
///< (e.g. when trying to dump a function reference or
@@ -3005,7 +3005,7 @@ shada_write_exit:
/// location is used.
/// @param[in] nomerge If true then old file is ignored.
///
-/// @return OK if writing was successfull, FAIL otherwise.
+/// @return OK if writing was successful, FAIL otherwise.
int shada_write_file(const char *const file, bool nomerge)
{
if (shada_disabled()) {
@@ -3341,7 +3341,7 @@ static ShaDaReadResult fread_len(ShaDaReadDef *const sd_reader,
/// @param[in] sd_reader Structure containing file reader definition.
/// @param[out] result Location where result is saved.
///
-/// @return kSDReadStatusSuccess if reading was successfull,
+/// @return kSDReadStatusSuccess if reading was successful,
/// kSDReadStatusNotShaDa if there were not enough bytes to read or
/// kSDReadStatusReadError if reading failed for whatever reason.
static ShaDaReadResult msgpack_read_uint64(ShaDaReadDef *const sd_reader,
diff --git a/src/nvim/sign.c b/src/nvim/sign.c
index 23dd447744..ab5d04d39b 100644
--- a/src/nvim/sign.c
+++ b/src/nvim/sign.c
@@ -198,7 +198,7 @@ static void insert_sign(
// column for signs.
if (buf->b_signlist == NULL) {
redraw_buf_later(buf, NOT_VALID);
- changed_cline_bef_curs();
+ changed_line_abv_curs();
}
// first sign in signlist
@@ -265,6 +265,81 @@ dict_T * sign_get_info(signlist_T *sign)
return d;
}
+// Sort the signs placed on the same line as "sign" by priority. Invoked after
+// changing the priority of an already placed sign. Assumes the signs in the
+// buffer are sorted by line number and priority.
+static void sign_sort_by_prio_on_line(buf_T *buf, signlist_T *sign)
+ FUNC_ATTR_NONNULL_ALL
+{
+ // If there is only one sign in the buffer or only one sign on the line or
+ // the sign is already sorted by priority, then return.
+ if ((sign->prev == NULL
+ || sign->prev->lnum != sign->lnum
+ || sign->prev->priority > sign->priority)
+ && (sign->next == NULL
+ || sign->next->lnum != sign->lnum
+ || sign->next->priority < sign->priority)) {
+ return;
+ }
+
+ // One or more signs on the same line as 'sign'
+ // Find a sign after which 'sign' should be inserted
+
+ // First search backward for a sign with higher priority on the same line
+ signlist_T *p = sign;
+ while (p->prev != NULL
+ && p->prev->lnum == sign->lnum
+ && p->prev->priority <= sign->priority) {
+ p = p->prev;
+ }
+ if (p == sign) {
+ // Sign not found. Search forward for a sign with priority just before
+ // 'sign'.
+ p = sign->next;
+ while (p->next != NULL
+ && p->next->lnum == sign->lnum
+ && p->next->priority > sign->priority) {
+ p = p->next;
+ }
+ }
+
+ // Remove 'sign' from the list
+ if (buf->b_signlist == sign) {
+ buf->b_signlist = sign->next;
+ }
+ if (sign->prev != NULL) {
+ sign->prev->next = sign->next;
+ }
+ if (sign->next != NULL) {
+ sign->next->prev = sign->prev;
+ }
+ sign->prev = NULL;
+ sign->next = NULL;
+
+ // Re-insert 'sign' at the right place
+ if (p->priority <= sign->priority) {
+ // 'sign' has a higher priority and should be inserted before 'p'
+ sign->prev = p->prev;
+ sign->next = p;
+ p->prev = sign;
+ if (sign->prev != NULL) {
+ sign->prev->next = sign;
+ }
+ if (buf->b_signlist == p) {
+ buf->b_signlist = sign;
+ }
+ } else {
+ // 'sign' has a lower priority and should be inserted after 'p'
+ sign->prev = p;
+ sign->next = p->next;
+ p->next = sign;
+ if (sign->next != NULL) {
+ sign->next->prev = sign;
+ }
+ }
+}
+
+
/// Add the sign into the signlist. Find the right spot to do it though.
void buf_addsign(
buf_T *buf, // buffer to store sign in
@@ -284,6 +359,8 @@ void buf_addsign(
&& sign_in_group(sign, groupname)) {
// Update an existing sign
sign->typenr = typenr;
+ sign->priority = prio;
+ sign_sort_by_prio_on_line(buf, sign);
return;
} else if (lnum < sign->lnum) {
insert_sign_by_lnum_prio(buf, prev, id, groupname, prio, lnum, typenr);
@@ -418,11 +495,11 @@ linenr_T buf_delsign(
}
}
- // When deleted the last sign needs to redraw the windows to remove the
- // sign column.
+ // When deleting the last sign the cursor position may change, because the
+ // sign columns no longer shows. And the 'signcolumn' may be hidden.
if (buf->b_signlist == NULL) {
redraw_buf_later(buf, NOT_VALID);
- changed_cline_bef_curs();
+ changed_line_abv_curs();
}
return lnum;
@@ -495,7 +572,7 @@ void buf_delete_signs(buf_T *buf, char_u *group)
// When deleting the last sign need to redraw the windows to remove the
// sign column. Not when curwin is NULL (this means we're exiting).
if (buf->b_signlist != NULL && curwin != NULL) {
- changed_cline_bef_curs();
+ changed_line_abv_curs();
}
lastp = &buf->b_signlist;
@@ -754,6 +831,14 @@ int sign_define_by_name(
} else {
sp_prev->sn_next = sp;
}
+ } else {
+ // Signs may already exist, a redraw is needed in windows with a
+ // non-empty sign list.
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (wp->w_buffer->b_signlist != NULL) {
+ redraw_buf_later(wp->w_buffer, NOT_VALID);
+ }
+ }
}
// set values for a defined sign.
@@ -1531,10 +1616,44 @@ static enum
EXP_SUBCMD, // expand :sign sub-commands
EXP_DEFINE, // expand :sign define {name} args
EXP_PLACE, // expand :sign place {id} args
+ EXP_LIST, // expand :sign place args
EXP_UNPLACE, // expand :sign unplace"
- EXP_SIGN_NAMES // expand with name of placed signs
+ EXP_SIGN_NAMES, // expand with name of placed signs
+ EXP_SIGN_GROUPS, // expand with name of placed sign groups
} expand_what;
+// Return the n'th sign name (used for command line completion)
+static char_u *get_nth_sign_name(int idx)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ // Complete with name of signs already defined
+ int current_idx = 0;
+ for (sign_T *sp = first_sign; sp != NULL; sp = sp->sn_next) {
+ if (current_idx++ == idx) {
+ return sp->sn_name;
+ }
+ }
+ return NULL;
+}
+
+// Return the n'th sign group name (used for command line completion)
+static char_u *get_nth_sign_group_name(int idx)
+{
+ // Complete with name of sign groups already defined
+ int current_idx = 0;
+ int todo = (int)sg_table.ht_used;
+ for (hashitem_T *hi = sg_table.ht_array; todo > 0; hi++) {
+ if (!HASHITEM_EMPTY(hi)) {
+ todo--;
+ if (current_idx++ == idx) {
+ signgroup_T *const group = HI2SG(hi);
+ return group->sg_name;
+ }
+ }
+ }
+ return NULL;
+}
+
/// Function given to ExpandGeneric() to obtain the sign command
/// expansion.
char_u * get_sign_name(expand_T *xp, int idx)
@@ -1552,20 +1671,18 @@ char_u * get_sign_name(expand_T *xp, int idx)
"buffer=", NULL };
return (char_u *)place_arg[idx];
}
+ case EXP_LIST: {
+ char *list_arg[] = { "group=", "file=", "buffer=", NULL };
+ return (char_u *)list_arg[idx];
+ }
case EXP_UNPLACE: {
char *unplace_arg[] = { "group=", "file=", "buffer=", NULL };
return (char_u *)unplace_arg[idx];
}
- case EXP_SIGN_NAMES: {
- // Complete with name of signs already defined
- int current_idx = 0;
- for (sign_T *sp = first_sign; sp != NULL; sp = sp->sn_next) {
- if (current_idx++ == idx) {
- return sp->sn_name;
- }
- }
- }
- return NULL;
+ case EXP_SIGN_NAMES:
+ return get_nth_sign_name(idx);
+ case EXP_SIGN_GROUPS:
+ return get_nth_sign_group_name(idx);
default:
return NULL;
}
@@ -1574,7 +1691,6 @@ char_u * get_sign_name(expand_T *xp, int idx)
/// Handle command line completion for :sign command.
void set_context_in_sign_cmd(expand_T *xp, char_u *arg)
{
- char_u *p;
char_u *end_subcmd;
char_u *last;
int cmd_idx;
@@ -1598,26 +1714,6 @@ void set_context_in_sign_cmd(expand_T *xp, char_u *arg)
// |
// begin_subcmd_args
begin_subcmd_args = skipwhite(end_subcmd);
- p = skiptowhite(begin_subcmd_args);
- if (*p == NUL) {
- //
- // Expand first argument of subcmd when possible.
- // For ":jump {id}" and ":unplace {id}", we could
- // possibly expand the ids of all signs already placed.
- //
- xp->xp_pattern = begin_subcmd_args;
- switch (cmd_idx) {
- case SIGNCMD_LIST:
- case SIGNCMD_UNDEFINE:
- // :sign list <CTRL-D>
- // :sign undefine <CTRL-D>
- expand_what = EXP_SIGN_NAMES;
- break;
- default:
- xp->xp_context = EXPAND_NOTHING;
- }
- return;
- }
// Expand last argument of subcmd.
//
@@ -1626,6 +1722,7 @@ void set_context_in_sign_cmd(expand_T *xp, char_u *arg)
// p
// Loop until reaching last argument.
+ char_u *p = begin_subcmd_args;
do {
p = skipwhite(p);
last = p;
@@ -1645,7 +1742,20 @@ void set_context_in_sign_cmd(expand_T *xp, char_u *arg)
expand_what = EXP_DEFINE;
break;
case SIGNCMD_PLACE:
- expand_what = EXP_PLACE;
+ // List placed signs
+ if (ascii_isdigit(*begin_subcmd_args)) {
+ // :sign place {id} {args}...
+ expand_what = EXP_PLACE;
+ } else {
+ // :sign place {args}...
+ expand_what = EXP_LIST;
+ }
+ break;
+ case SIGNCMD_LIST:
+ case SIGNCMD_UNDEFINE:
+ // :sign list <CTRL-D>
+ // :sign undefine <CTRL-D>
+ expand_what = EXP_SIGN_NAMES;
break;
case SIGNCMD_JUMP:
case SIGNCMD_UNPLACE:
@@ -1659,19 +1769,33 @@ void set_context_in_sign_cmd(expand_T *xp, char_u *arg)
xp->xp_pattern = p + 1;
switch (cmd_idx) {
case SIGNCMD_DEFINE:
- if (STRNCMP(last, "texthl", p - last) == 0
- || STRNCMP(last, "linehl", p - last) == 0
- || STRNCMP(last, "numhl", p - last) == 0) {
+ if (STRNCMP(last, "texthl", 6) == 0
+ || STRNCMP(last, "linehl", 6) == 0
+ || STRNCMP(last, "numhl", 5) == 0) {
xp->xp_context = EXPAND_HIGHLIGHT;
- } else if (STRNCMP(last, "icon", p - last) == 0) {
+ } else if (STRNCMP(last, "icon", 4) == 0) {
xp->xp_context = EXPAND_FILES;
} else {
xp->xp_context = EXPAND_NOTHING;
}
break;
case SIGNCMD_PLACE:
- if (STRNCMP(last, "name", p - last) == 0) {
+ if (STRNCMP(last, "name", 4) == 0) {
expand_what = EXP_SIGN_NAMES;
+ } else if (STRNCMP(last, "group", 5) == 0) {
+ expand_what = EXP_SIGN_GROUPS;
+ } else if (STRNCMP(last, "file", 4) == 0) {
+ xp->xp_context = EXPAND_BUFFERS;
+ } else {
+ xp->xp_context = EXPAND_NOTHING;
+ }
+ break;
+ case SIGNCMD_UNPLACE:
+ case SIGNCMD_JUMP:
+ if (STRNCMP(last, "group", 5) == 0) {
+ expand_what = EXP_SIGN_GROUPS;
+ } else if (STRNCMP(last, "file", 4) == 0) {
+ xp->xp_context = EXPAND_BUFFERS;
} else {
xp->xp_context = EXPAND_NOTHING;
}
diff --git a/src/nvim/spell.c b/src/nvim/spell.c
index c75a53a777..180073ade1 100644
--- a/src/nvim/spell.c
+++ b/src/nvim/spell.c
@@ -848,7 +848,7 @@ static void find_word(matchinf_T *mip, int mode)
mip->mi_compflags[mip->mi_complen] = ((unsigned)flags >> 24);
mip->mi_compflags[mip->mi_complen + 1] = NUL;
if (word_ends) {
- char_u fword[MAXWLEN];
+ char_u fword[MAXWLEN] = { 0 };
if (slang->sl_compsylmax < MAXWLEN) {
// "fword" is only needed for checking syllables.
@@ -1026,26 +1026,25 @@ match_checkcompoundpattern (
// Returns true if "flags" is a valid sequence of compound flags and "word"
// does not have too many syllables.
-static bool can_compound(slang_T *slang, char_u *word, char_u *flags)
+static bool can_compound(slang_T *slang, const char_u *word,
+ const char_u *flags)
+ FUNC_ATTR_NONNULL_ALL
{
- char_u uflags[MAXWLEN * 2];
- int i;
- char_u *p;
+ char_u uflags[MAXWLEN * 2] = { 0 };
- if (slang->sl_compprog == NULL)
+ if (slang->sl_compprog == NULL) {
return false;
- if (enc_utf8) {
- // Need to convert the single byte flags to utf8 characters.
- p = uflags;
- for (i = 0; flags[i] != NUL; i++) {
- p += utf_char2bytes(flags[i], p);
- }
- *p = NUL;
- p = uflags;
- } else
- p = flags;
- if (!vim_regexec_prog(&slang->sl_compprog, false, p, 0))
+ }
+ // Need to convert the single byte flags to utf8 characters.
+ char_u *p = uflags;
+ for (int i = 0; flags[i] != NUL; i++) {
+ p += utf_char2bytes(flags[i], p);
+ }
+ *p = NUL;
+ p = uflags;
+ if (!vim_regexec_prog(&slang->sl_compprog, false, p, 0)) {
return false;
+ }
// Count the number of syllables. This may be slow, do it last. If there
// are too many syllables AND the number of compound words is above
@@ -2008,6 +2007,10 @@ char_u *did_set_spelllang(win_T *wp)
region = NULL;
len = (int)STRLEN(lang);
+ if (!valid_spellang(lang)) {
+ continue;
+ }
+
if (STRCMP(lang, "cjk") == 0) {
wp->w_s->b_cjk = 1;
continue;
@@ -2824,9 +2827,6 @@ void spell_suggest(int count)
smsg(_("Sorry, only %" PRId64 " suggestions"),
(int64_t)sug.su_ga.ga_len);
} else {
- XFREE_CLEAR(repl_from);
- XFREE_CLEAR(repl_to);
-
// When 'rightleft' is set the list is drawn right-left.
cmdmsg_rl = curwin->w_p_rl;
if (cmdmsg_rl)
@@ -2906,6 +2906,9 @@ void spell_suggest(int count)
if (selected > 0 && selected <= sug.su_ga.ga_len && u_save_cursor() == OK) {
// Save the from and to text for :spellrepall.
+ XFREE_CLEAR(repl_from);
+ XFREE_CLEAR(repl_to);
+
stp = &SUG(sug.su_ga, selected - 1);
if (sug.su_badlen > stp->st_orglen) {
// Replacing less than "su_badlen", append the remainder to
@@ -3603,7 +3606,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so
{
char_u tword[MAXWLEN]; // good word collected so far
trystate_T stack[MAXWLEN];
- char_u preword[MAXWLEN * 3]; // word found with proper case;
+ char_u preword[MAXWLEN * 3] = { 0 }; // word found with proper case;
// concatenation of prefix compound
// words and split word. NUL terminated
// when going deeper but not when coming
@@ -4268,9 +4271,8 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so
// For changing a composing character adjust
// the score from SCORE_SUBST to
// SCORE_SUBCOMP.
- if (enc_utf8
- && utf_iscomposing(utf_ptr2char(tword + sp->ts_twordlen
- - sp->ts_tcharlen))
+ if (utf_iscomposing(utf_ptr2char(tword + sp->ts_twordlen
+ - sp->ts_tcharlen))
&& utf_iscomposing(utf_ptr2char(fword
+ sp->ts_fcharstart))) {
sp->ts_score -= SCORE_SUBST - SCORE_SUBCOMP;
@@ -5759,19 +5761,22 @@ cleanup_suggestions (
int maxscore,
int keep // nr of suggestions to keep
)
+ FUNC_ATTR_NONNULL_ALL
{
suggest_T *stp = &SUG(*gap, 0);
- // Sort the list.
- qsort(gap->ga_data, (size_t)gap->ga_len, sizeof(suggest_T), sug_compare);
+ if (gap->ga_len > 0) {
+ // Sort the list.
+ qsort(gap->ga_data, (size_t)gap->ga_len, sizeof(suggest_T), sug_compare);
- // Truncate the list to the number of suggestions that will be displayed.
- if (gap->ga_len > keep) {
- for (int i = keep; i < gap->ga_len; ++i) {
- xfree(stp[i].st_word);
+ // Truncate the list to the number of suggestions that will be displayed.
+ if (gap->ga_len > keep) {
+ for (int i = keep; i < gap->ga_len; i++) {
+ xfree(stp[i].st_word);
+ }
+ gap->ga_len = keep;
+ return stp[keep - 1].st_score;
}
- gap->ga_len = keep;
- return stp[keep - 1].st_score;
}
return maxscore;
}
@@ -5855,7 +5860,7 @@ static void spell_soundfold_sofo(slang_T *slang, char_u *inword, char_u *res)
// 255, sl_sal the rest.
for (s = inword; *s != NUL; ) {
c = mb_cptr2char_adv((const char_u **)&s);
- if (enc_utf8 ? utf_class(c) == 0 : ascii_iswhite(c)) {
+ if (utf_class(c) == 0) {
c = ' ';
} else if (c < 256) {
c = slang->sl_sal_first[c];
@@ -5932,9 +5937,10 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res)
const char_u *t = s;
c = mb_cptr2char_adv((const char_u **)&s);
if (slang->sl_rem_accents) {
- if (enc_utf8 ? utf_class(c) == 0 : ascii_iswhite(c)) {
- if (did_white)
+ if (utf_class(c) == 0) {
+ if (did_white) {
continue;
+ }
c = ' ';
did_white = true;
} else {
diff --git a/src/nvim/spell_defs.h b/src/nvim/spell_defs.h
index e83b21b219..034c580b3e 100644
--- a/src/nvim/spell_defs.h
+++ b/src/nvim/spell_defs.h
@@ -261,20 +261,15 @@ typedef struct trystate_S {
// Use our own character-case definitions, because the current locale may
// differ from what the .spl file uses.
// These must not be called with negative number!
-#include <wchar.h> // for towupper() and towlower()
// Multi-byte implementation. For Unicode we can call utf_*(), but don't do
// that for ASCII, because we don't want to use 'casemap' here. Otherwise use
// the "w" library function for characters above 255.
-#define SPELL_TOFOLD(c) (enc_utf8 && (c) >= 128 ? utf_fold(c) \
- : (c) < \
- 256 ? (int)spelltab.st_fold[c] : (int)towlower(c))
+#define SPELL_TOFOLD(c) ((c) >= 128 ? utf_fold(c) : (int)spelltab.st_fold[c])
-#define SPELL_TOUPPER(c) (enc_utf8 && (c) >= 128 ? mb_toupper(c) \
- : (c) < \
- 256 ? (int)spelltab.st_upper[c] : (int)towupper(c))
+#define SPELL_TOUPPER(c) ((c) >= 128 ? mb_toupper(c) \
+ : (int)spelltab.st_upper[c])
-#define SPELL_ISUPPER(c) (enc_utf8 && (c) >= 128 ? mb_isupper(c) \
- : (c) < 256 ? spelltab.st_isu[c] : iswupper(c))
+#define SPELL_ISUPPER(c) ((c) >= 128 ? mb_isupper(c) : spelltab.st_isu[c])
// First language that is loaded, start of the linked list of loaded
// languages.
diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c
index 14abfda9ff..f8c10d0258 100644
--- a/src/nvim/spellfile.c
+++ b/src/nvim/spellfile.c
@@ -304,9 +304,6 @@
static char *e_spell_trunc = N_("E758: Truncated spell file");
static char *e_afftrailing = N_("Trailing text in %s line %d: %s");
static char *e_affname = N_("Affix name too long in %s line %d: %s");
-static char *e_affform = N_("E761: Format error in affix file FOL, LOW or UPP");
-static char *e_affrange = N_(
- "E762: Character in FOL, LOW or UPP is out of range");
static char *msg_compressing = N_("Compressing word tree...");
#define MAXLINELEN 500 // Maximum length in bytes of a line in a .aff
@@ -1386,8 +1383,7 @@ static int read_compound(FILE *fd, slang_T *slang, int len)
// Inserting backslashes may double the length, "^\(\)$<Nul>" is 7 bytes.
// Conversion to utf-8 may double the size.
c = todo * 2 + 7;
- if (enc_utf8)
- c += todo * 2;
+ c += todo * 2;
pat = xmalloc(c);
// We also need a list of all flags that can appear at the start and one
@@ -2624,19 +2620,6 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname)
spin->si_clear_chartab = false;
}
- // Don't write a word table for an ASCII file, so that we don't check
- // for conflicts with a word table that matches 'encoding'.
- // Don't write one for utf-8 either, we use utf_*() and
- // mb_get_class(), the list of chars in the file will be incomplete.
- if (!spin->si_ascii
- && !enc_utf8
- ) {
- if (fol == NULL || low == NULL || upp == NULL)
- smsg(_("Missing FOL/LOW/UPP line in %s"), fname);
- else
- (void)set_spell_chartab(fol, low, upp);
- }
-
xfree(fol);
xfree(low);
xfree(upp);
@@ -5533,65 +5516,6 @@ static void init_spellfile(void)
}
}
-// Set the spell character tables from strings in the affix file.
-static int set_spell_chartab(char_u *fol, char_u *low, char_u *upp)
-{
- // We build the new tables here first, so that we can compare with the
- // previous one.
- spelltab_T new_st;
- char_u *pf = fol, *pl = low, *pu = upp;
- int f, l, u;
-
- clear_spell_chartab(&new_st);
-
- while (*pf != NUL) {
- if (*pl == NUL || *pu == NUL) {
- EMSG(_(e_affform));
- return FAIL;
- }
- f = mb_ptr2char_adv((const char_u **)&pf);
- l = mb_ptr2char_adv((const char_u **)&pl);
- u = mb_ptr2char_adv((const char_u **)&pu);
- // Every character that appears is a word character.
- if (f < 256)
- new_st.st_isw[f] = true;
- if (l < 256)
- new_st.st_isw[l] = true;
- if (u < 256)
- new_st.st_isw[u] = true;
-
- // if "LOW" and "FOL" are not the same the "LOW" char needs
- // case-folding
- if (l < 256 && l != f) {
- if (f >= 256) {
- EMSG(_(e_affrange));
- return FAIL;
- }
- new_st.st_fold[l] = f;
- }
-
- // if "UPP" and "FOL" are not the same the "UPP" char needs
- // case-folding, it's upper case and the "UPP" is the upper case of
- // "FOL" .
- if (u < 256 && u != f) {
- if (f >= 256) {
- EMSG(_(e_affrange));
- return FAIL;
- }
- new_st.st_fold[u] = f;
- new_st.st_isu[u] = true;
- new_st.st_upper[f] = u;
- }
- }
-
- if (*pl != NUL || *pu != NUL) {
- EMSG(_(e_affform));
- return FAIL;
- }
-
- return set_spell_finish(&new_st);
-}
-
// Set the spell character tables from strings in the .spl file.
static void
set_spell_charflags (
diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c
index bcf133afda..ef4dfb3caa 100644
--- a/src/nvim/syntax.c
+++ b/src/nvim/syntax.c
@@ -3981,7 +3981,7 @@ static void add_keyword(char_u *const name,
STRLEN(kp->keyword), hash);
// even though it looks like only the kp->keyword member is
- // being used here, vim uses some pointer trickery to get the orignal
+ // being used here, vim uses some pointer trickery to get the original
// struct again later by using knowledge of the offset of the keyword
// field in the struct. See the definition of the HI2KE macro.
if (HASHITEM_EMPTY(hi)) {
@@ -6400,7 +6400,7 @@ static int color_numbers_88[28] = { 0, 4, 2, 6,
75, 11, 78, 15, -1 };
// for xterm with 256 colors...
static int color_numbers_256[28] = { 0, 4, 2, 6,
- 1, 5, 130, 130,
+ 1, 5, 130, 3,
248, 248, 7, 7,
242, 242,
12, 81, 10, 121,
diff --git a/src/nvim/tag.c b/src/nvim/tag.c
index a412ed0276..81d1ef4c9f 100644
--- a/src/nvim/tag.c
+++ b/src/nvim/tag.c
@@ -1182,7 +1182,7 @@ static int find_tagfunc_tags(
if (result == FAIL) {
return FAIL;
}
- if (rettv.v_type == VAR_SPECIAL && rettv.vval.v_number == VV_NULL) {
+ if (rettv.v_type == VAR_SPECIAL && rettv.vval.v_special == kSpecialVarNull) {
tv_clear(&rettv);
return NOTDONE;
}
@@ -2540,7 +2540,9 @@ parse_match(
}
p += 2; // skip ";\""
if (*p++ == TAB) {
- while (ASCII_ISALPHA(*p)) {
+ // Accept ASCII alphabetic kind characters and any multi-byte
+ // character.
+ while (ASCII_ISALPHA(*p) || utfc_ptr2len(p) > 1) {
if (STRNCMP(p, "kind:", 5) == 0) {
tagp->tagkind = p + 5;
} else if (STRNCMP(p, "user_data:", 10) == 0) {
@@ -2559,19 +2561,22 @@ parse_match(
}
if (pt == NULL)
break;
- p = pt + 1;
+ p = pt;
+ MB_PTR_ADV(p);
}
}
}
if (tagp->tagkind != NULL) {
for (p = tagp->tagkind;
- *p && *p != '\t' && *p != '\r' && *p != '\n'; ++p)
- ;
+ *p && *p != '\t' && *p != '\r' && *p != '\n';
+ MB_PTR_ADV(p)) {
+ }
tagp->tagkind_end = p;
}
if (tagp->user_data != NULL) {
for (p = tagp->user_data;
- *p && *p != '\t' && *p != '\r' && *p != '\n'; p++) {
+ *p && *p != '\t' && *p != '\r' && *p != '\n';
+ MB_PTR_ADV(p)) {
}
tagp->user_data_end = p;
}
@@ -3158,9 +3163,11 @@ int get_tags(list_T *list, char_u *pat, char_u *buf_fname)
is_static = test_for_static(&tp);
- /* Skip pseudo-tag lines. */
- if (STRNCMP(tp.tagname, "!_TAG_", 6) == 0)
+ // Skip pseudo-tag lines.
+ if (STRNCMP(tp.tagname, "!_TAG_", 6) == 0) {
+ xfree(matches[i]);
continue;
+ }
dict = tv_dict_alloc();
tv_list_append_dict(list, dict);
@@ -3179,7 +3186,8 @@ int get_tags(list_T *list, char_u *pat, char_u *buf_fname)
if (tp.command_end != NULL) {
for (char_u *p = tp.command_end + 3;
- *p != NUL && *p != '\n' && *p != '\r'; p++) {
+ *p != NUL && *p != '\n' && *p != '\r';
+ MB_PTR_ADV(p)) {
if (p == tp.tagkind
|| (p + 5 == tp.tagkind && STRNCMP(p, "kind:", 5) == 0)) {
// skip "kind:<kind>" and "<kind>"
@@ -3378,11 +3386,15 @@ static void tagstack_set_curidx(win_T *wp, int curidx)
}
// Set the tag stack entries of the specified window.
-// 'action' is set to either 'a' for append or 'r' for replace.
-int set_tagstack(win_T *wp, dict_T *d, int action)
+// 'action' is set to one of:
+// 'a' for append
+// 'r' for replace
+// 't' for truncate
+int set_tagstack(win_T *wp, const dict_T *d, int action)
+ FUNC_ATTR_NONNULL_ARG(1)
{
dictitem_T *di;
- list_T *l;
+ list_T *l = NULL;
// not allowed to alter the tag stack entries from inside tagfunc
if (tfu_in_use) {
@@ -3395,16 +3407,30 @@ int set_tagstack(win_T *wp, dict_T *d, int action)
return FAIL;
}
l = di->di_tv.vval.v_list;
+ }
- if (action == 'r') {
+ if ((di = tv_dict_find(d, "curidx", -1)) != NULL) {
+ tagstack_set_curidx(wp, (int)tv_get_number(&di->di_tv) - 1);
+ }
+ if (action == 't') { // truncate the stack
+ taggy_T *const tagstack = wp->w_tagstack;
+ const int tagstackidx = wp->w_tagstackidx;
+ int tagstacklen = wp->w_tagstacklen;
+ // delete all the tag stack entries above the current entry
+ while (tagstackidx < tagstacklen) {
+ tagstack_clear_entry(&tagstack[--tagstacklen]);
+ }
+ wp->w_tagstacklen = tagstacklen;
+ }
+
+ if (l != NULL) {
+ if (action == 'r') { // replace the stack
tagstack_clear(wp);
}
tagstack_push_items(wp, l);
- }
-
- if ((di = tv_dict_find(d, "curidx", -1)) != NULL) {
- tagstack_set_curidx(wp, (int)tv_get_number(&di->di_tv) - 1);
+ // set the current index after the last entry
+ wp->w_tagstackidx = wp->w_tagstacklen;
}
return OK;
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
index c5e756905a..a096b77ac6 100644
--- a/src/nvim/terminal.c
+++ b/src/nvim/terminal.c
@@ -343,12 +343,16 @@ void terminal_enter(void)
RedrawingDisabled = false;
// Disable these options in terminal-mode. They are nonsense because cursor is
- // placed at end of buffer to "follow" output.
+ // placed at end of buffer to "follow" output. #11072
win_T *save_curwin = curwin;
int save_w_p_cul = curwin->w_p_cul;
int save_w_p_cuc = curwin->w_p_cuc;
+ long save_w_p_so = curwin->w_p_so;
+ long save_w_p_siso = curwin->w_p_siso;
curwin->w_p_cul = false;
curwin->w_p_cuc = false;
+ curwin->w_p_so = 0;
+ curwin->w_p_siso = 0;
adjust_topline(s->term, buf, 0); // scroll to end
// erase the unfocused cursor
@@ -370,6 +374,8 @@ void terminal_enter(void)
if (save_curwin == curwin) { // save_curwin may be invalid (window closed)!
curwin->w_p_cul = save_w_p_cul;
curwin->w_p_cuc = save_w_p_cuc;
+ curwin->w_p_so = save_w_p_so;
+ curwin->w_p_siso = save_w_p_siso;
}
// draw the unfocused cursor
@@ -650,6 +656,11 @@ Buffer terminal_buf(const Terminal *term)
return term->buf_handle;
}
+bool terminal_running(const Terminal *term)
+{
+ return !term->closed;
+}
+
// }}}
// libvterm callbacks {{{
@@ -992,8 +1003,9 @@ static void mouse_action(Terminal *term, int button, int row, int col,
static bool send_mouse_event(Terminal *term, int c)
{
int row = mouse_row, col = mouse_col, grid = mouse_grid;
+ int offset;
win_T *mouse_win = mouse_find_win(&grid, &row, &col);
- if (mouse_win == NULL) {
+ if (mouse_win == NULL || (offset = win_col_off(mouse_win)) > col) {
goto end;
}
@@ -1015,7 +1027,7 @@ static bool send_mouse_event(Terminal *term, int c)
default: return false;
}
- mouse_action(term, button, row, col, drag, 0);
+ mouse_action(term, button, row, col - offset, drag, 0);
size_t len = vterm_output_read(term->vt, term->textbuf,
sizeof(term->textbuf));
terminal_send(term, term->textbuf, (size_t)len);
diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile
index c36458930f..e52fd888bd 100644
--- a/src/nvim/testdir/Makefile
+++ b/src/nvim/testdir/Makefile
@@ -86,7 +86,7 @@ nongui: nolog $(FIXFF) $(SCRIPTS) newtests report
@echo 'set $$_exitcode = -1\nrun\nif $$_exitcode != -1\n quit\nend' > .gdbinit
report:
- $(RUN_VIMTEST) $(NO_INITS) -u NONE -S summarize.vim messages
+ $(NVIM_PRG) -u NONE $(NO_INITS) -S summarize.vim messages
@echo
@echo 'Test results:'
@cat test_result.log
diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim
index fd49e48c2d..adf7463936 100644
--- a/src/nvim/testdir/runtest.vim
+++ b/src/nvim/testdir/runtest.vim
@@ -98,11 +98,6 @@ func GetAllocId(name)
return lnum - top - 1
endfunc
-func CanRunVimInTerminal()
- " Nvim: always false, we use Lua screen-tests instead.
- return 0
-endfunc
-
func RunTheTest(test)
echo 'Executing ' . a:test
diff --git a/src/nvim/testdir/screendump.vim b/src/nvim/testdir/screendump.vim
index e69de29bb2..8afff1da91 100644
--- a/src/nvim/testdir/screendump.vim
+++ b/src/nvim/testdir/screendump.vim
@@ -0,0 +1,2 @@
+source shared.vim
+source term_util.vim
diff --git a/src/nvim/testdir/shared.vim b/src/nvim/testdir/shared.vim
index 3875ffc056..b041fdedb1 100644
--- a/src/nvim/testdir/shared.vim
+++ b/src/nvim/testdir/shared.vim
@@ -1,10 +1,12 @@
" Functions shared by several tests.
" Only load this script once.
-if exists('*WaitFor')
+if exists('*PythonProg')
finish
endif
+source view_util.vim
+
" {Nvim}
" Filepath captured from output may be truncated, like this:
" /home/va...estdir/Xtest-tmpdir/nvimxbXN4i/10
@@ -328,17 +330,6 @@ func RunVimPiped(before, after, arguments, pipecmd)
return 1
endfunc
-" Get line "lnum" as displayed on the screen.
-" Trailing white space is trimmed.
-func! Screenline(lnum)
- let chars = []
- for c in range(1, winwidth(0))
- call add(chars, nr2char(screenchar(a:lnum, c)))
- endfor
- let line = join(chars, '')
- return matchstr(line, '^.\{-}\ze\s*$')
-endfunc
-
func CanRunGui()
return has('gui') && ($DISPLAY != "" || has('gui_running'))
endfunc
diff --git a/src/nvim/testdir/term_util.vim b/src/nvim/testdir/term_util.vim
new file mode 100644
index 0000000000..3a838a3a1f
--- /dev/null
+++ b/src/nvim/testdir/term_util.vim
@@ -0,0 +1,11 @@
+" Functions about terminal shared by several tests
+
+" Only load this script once.
+if exists('*CanRunVimInTerminal')
+ finish
+endif
+
+func CanRunVimInTerminal()
+ " Nvim: always false, we use Lua screen-tests instead.
+ return 0
+endfunc
diff --git a/src/nvim/testdir/test49.vim b/src/nvim/testdir/test49.vim
index fc79f57d2e..c86fdf25ab 100644
--- a/src/nvim/testdir/test49.vim
+++ b/src/nvim/testdir/test49.vim
@@ -1,6 +1,6 @@
" Vim script language tests
" Author: Servatius Brandt <Servatius.Brandt@fujitsu-siemens.com>
-" Last Change: 2019 May 24
+" Last Change: 2019 Oct 08
"-------------------------------------------------------------------------------
" Test environment {{{1
@@ -456,7 +456,7 @@ function! ExtraVim(...)
" messing up the user's viminfo file.
let redirect = a:0 ?
\ " -c 'au VimLeave * redir END' -c 'redir\\! >" . a:1 . "'" : ""
- exec "!echo '" . debug_quits . "q' | $NVIM_PRG -u NONE -N -es" . redirect .
+ exec "!echo '" . debug_quits . "q' | " .. v:progpath .. " -u NONE -N -es" . redirect .
\ " -c 'debuggreedy|set viminfo+=nviminfo'" .
\ " -c 'let ExtraVimBegin = " . extra_begin . "'" .
\ " -c 'let ExtraVimResult = \"" . resultfile . "\"'" . breakpoints .
diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim
index e3547aea5b..954e5d875f 100644
--- a/src/nvim/testdir/test_autocmd.vim
+++ b/src/nvim/testdir/test_autocmd.vim
@@ -1072,6 +1072,40 @@ func Test_Cmd_Autocmds()
enew!
endfunc
+func s:ReadFile()
+ setl noswapfile nomodified
+ let filename = resolve(expand("<afile>:p"))
+ execute 'read' fnameescape(filename)
+ 1d_
+ exe 'file' fnameescape(filename)
+ setl buftype=acwrite
+endfunc
+
+func s:WriteFile()
+ let filename = resolve(expand("<afile>:p"))
+ setl buftype=
+ noautocmd execute 'write' fnameescape(filename)
+ setl buftype=acwrite
+ setl nomodified
+endfunc
+
+func Test_BufReadCmd()
+ autocmd BufReadCmd *.test call s:ReadFile()
+ autocmd BufWriteCmd *.test call s:WriteFile()
+
+ call writefile(['one', 'two', 'three'], 'Xcmd.test')
+ edit Xcmd.test
+ call assert_match('Xcmd.test" line 1 of 3', execute('file'))
+ normal! Gofour
+ write
+ call assert_equal(['one', 'two', 'three', 'four'], readfile('Xcmd.test'))
+
+ bwipe!
+ call delete('Xcmd.test')
+ au! BufReadCmd
+ au! BufWriteCmd
+endfunc
+
func SetChangeMarks(start, end)
exe a:start. 'mark ['
exe a:end. 'mark ]'
@@ -1786,3 +1820,46 @@ func Test_FileChangedShell_reload()
bwipe!
call delete('Xchanged')
endfunc
+
+" Test for FileReadCmd autocmd
+func Test_autocmd_FileReadCmd()
+ func ReadFileCmd()
+ call append(line('$'), "v:cmdarg = " .. v:cmdarg)
+ endfunc
+ augroup FileReadCmdTest
+ au!
+ au FileReadCmd Xtest call ReadFileCmd()
+ augroup END
+
+ new
+ read ++bin Xtest
+ read ++nobin Xtest
+ read ++edit Xtest
+ read ++bad=keep Xtest
+ read ++bad=drop Xtest
+ read ++bad=- Xtest
+ read ++ff=unix Xtest
+ read ++ff=dos Xtest
+ read ++ff=mac Xtest
+ read ++enc=utf-8 Xtest
+
+ call assert_equal(['',
+ \ 'v:cmdarg = ++bin',
+ \ 'v:cmdarg = ++nobin',
+ \ 'v:cmdarg = ++edit',
+ \ 'v:cmdarg = ++bad=keep',
+ \ 'v:cmdarg = ++bad=drop',
+ \ 'v:cmdarg = ++bad=-',
+ \ 'v:cmdarg = ++ff=unix',
+ \ 'v:cmdarg = ++ff=dos',
+ \ 'v:cmdarg = ++ff=mac',
+ \ 'v:cmdarg = ++enc=utf-8'], getline(1, '$'))
+
+ close!
+ augroup FileReadCmdTest
+ au!
+ augroup END
+ delfunc ReadFileCmd
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_breakindent.vim b/src/nvim/testdir/test_breakindent.vim
index 6d88f1dc5a..5675bf74dd 100644
--- a/src/nvim/testdir/test_breakindent.vim
+++ b/src/nvim/testdir/test_breakindent.vim
@@ -361,5 +361,15 @@ func Test_breakindent19_sbr_nextpage()
\ "> aaaaaaaaaaaaaaaaaa",
\ ]
call s:compare_lines(expect, lines)
+
+ setl breakindent briopt=min:18 sbr=>
+ norm! 5gj
+ let lines = s:screen_lines(1, 20)
+ let expect = [
+ \ ">aaaaaaaaaaaaaaaaaaa",
+ \ ">aaaaaaaaaaaaaaaaaaa",
+ \ ">aaaaaaaaaaaaaaaaaaa",
+ \ ]
+ call s:compare_lines(expect, lines)
call s:close_windows('set breakindent& briopt& sbr&')
endfunc
diff --git a/src/nvim/testdir/test_bufline.vim b/src/nvim/testdir/test_bufline.vim
index a924ce0002..076f03fdd8 100644
--- a/src/nvim/testdir/test_bufline.vim
+++ b/src/nvim/testdir/test_bufline.vim
@@ -1,7 +1,7 @@
" Tests for setbufline(), getbufline(), appendbufline(), deletebufline()
source shared.vim
-" source screendump.vim
+source screendump.vim
func Test_setbufline_getbufline()
new
diff --git a/src/nvim/testdir/test_bufwintabinfo.vim b/src/nvim/testdir/test_bufwintabinfo.vim
index 176d49d28e..cb7ab44798 100644
--- a/src/nvim/testdir/test_bufwintabinfo.vim
+++ b/src/nvim/testdir/test_bufwintabinfo.vim
@@ -149,3 +149,10 @@ func Test_getbufinfo_lines()
edit Xfoo
bw!
endfunc
+
+function Test_getbufinfo_lastused()
+ new Xfoo
+ let info = getbufinfo('Xfoo')[0]
+ call assert_equal(has_key(info, 'lastused'), 1)
+ call assert_equal(type(info.lastused), type(0))
+endfunc
diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim
index 9c3c33a943..2c7d64f078 100644
--- a/src/nvim/testdir/test_cmdline.vim
+++ b/src/nvim/testdir/test_cmdline.vim
@@ -755,3 +755,62 @@ func Test_cmdwin_feedkeys()
" This should not generate E488
call feedkeys("q:\<CR>", 'x')
endfunc
+
+func Test_buffers_lastused()
+ " check that buffers are sorted by time when wildmode has lastused
+ edit bufc " oldest
+
+ sleep 1200m
+ enew
+ edit bufa " middle
+
+ sleep 1200m
+ enew
+ edit bufb " newest
+
+ enew
+
+ call assert_equal(['bufc', 'bufa', 'bufb'],
+ \ getcompletion('', 'buffer'))
+
+ let save_wildmode = &wildmode
+ set wildmode=full:lastused
+
+ let cap = "\<c-r>=execute('let X=getcmdline()')\<cr>"
+ call feedkeys(":b \<tab>" .. cap .. "\<esc>", 'xt')
+ call assert_equal('b bufb', X)
+ call feedkeys(":b \<tab>\<tab>" .. cap .. "\<esc>", 'xt')
+ call assert_equal('b bufa', X)
+ call feedkeys(":b \<tab>\<tab>\<tab>" .. cap .. "\<esc>", 'xt')
+ call assert_equal('b bufc', X)
+ enew
+
+ sleep 1200m
+ edit other
+ call feedkeys(":b \<tab>" .. cap .. "\<esc>", 'xt')
+ call assert_equal('b bufb', X)
+ call feedkeys(":b \<tab>\<tab>" .. cap .. "\<esc>", 'xt')
+ call assert_equal('b bufa', X)
+ call feedkeys(":b \<tab>\<tab>\<tab>" .. cap .. "\<esc>", 'xt')
+ call assert_equal('b bufc', X)
+ enew
+
+ let &wildmode = save_wildmode
+
+ bwipeout bufa
+ bwipeout bufb
+ bwipeout bufc
+endfunc
+
+" test that ";" works to find a match at the start of the first line
+func Test_zero_line_search()
+ new
+ call setline(1, ["1, pattern", "2, ", "3, pattern"])
+ call cursor(1,1)
+ 0;/pattern/d
+ call assert_equal(["2, ", "3, pattern"], getline(1,'$'))
+ q!
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab " vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_compiler.vim b/src/nvim/testdir/test_compiler.vim
index 40d3cdbdae..6bb602717f 100644
--- a/src/nvim/testdir/test_compiler.vim
+++ b/src/nvim/testdir/test_compiler.vim
@@ -38,10 +38,11 @@ func Test_compiler()
endfunc
func Test_compiler_without_arg()
- let a=split(execute('compiler'))
- call assert_match('^.*runtime/compiler/ant.vim$', a[0])
- call assert_match('^.*runtime/compiler/bcc.vim$', a[1])
- call assert_match('^.*runtime/compiler/xmlwf.vim$', a[-1])
+ let runtime = substitute($VIMRUNTIME, '\\', '/', 'g')
+ let a = split(execute('compiler'))
+ call assert_match(runtime .. '/compiler/ant.vim$', a[0])
+ call assert_match(runtime .. '/compiler/bcc.vim$', a[1])
+ call assert_match(runtime .. '/compiler/xmlwf.vim$', a[-1])
endfunc
func Test_compiler_completion()
diff --git a/src/nvim/testdir/test_debugger.vim b/src/nvim/testdir/test_debugger.vim
index 130bcf8910..811717208e 100644
--- a/src/nvim/testdir/test_debugger.vim
+++ b/src/nvim/testdir/test_debugger.vim
@@ -1,7 +1,7 @@
" Tests for the Vim script debug commands
source shared.vim
-" source screendump.vim
+source screendump.vim
" Run a Vim debugger command
" If the expected output argument is supplied, then check for it.
diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim
index 21e0271bda..42e18ed027 100644
--- a/src/nvim/testdir/test_diffmode.vim
+++ b/src/nvim/testdir/test_diffmode.vim
@@ -1,4 +1,6 @@
" Tests for diff mode
+source shared.vim
+source screendump.vim
func Test_diff_fold_sync()
enew!
@@ -67,7 +69,7 @@ func Common_vert_split()
set foldmethod=marker foldcolumn=4
call assert_equal(0, &diff)
call assert_equal('marker', &foldmethod)
- call assert_equal(4, &foldcolumn)
+ call assert_equal('4', &foldcolumn)
call assert_equal(0, &scrollbind)
call assert_equal(0, &cursorbind)
call assert_equal(1, &wrap)
@@ -76,7 +78,7 @@ func Common_vert_split()
vert diffsplit Xtest2
call assert_equal(1, &diff)
call assert_equal('diff', &foldmethod)
- call assert_equal(2, &foldcolumn)
+ call assert_equal('2', &foldcolumn)
call assert_equal(1, &scrollbind)
call assert_equal(1, &cursorbind)
call assert_equal(0, &wrap)
@@ -142,7 +144,7 @@ func Common_vert_split()
1wincmd w
call assert_equal(0, &diff)
call assert_equal('marker', &foldmethod)
- call assert_equal(4, &foldcolumn)
+ call assert_equal('4', &foldcolumn)
call assert_equal(0, &scrollbind)
call assert_equal(0, &cursorbind)
call assert_equal(1, &wrap)
@@ -150,7 +152,7 @@ func Common_vert_split()
wincmd w
call assert_equal(0, &diff)
call assert_equal('marker', &foldmethod)
- call assert_equal(4, &foldcolumn)
+ call assert_equal('4', &foldcolumn)
call assert_equal(0, &scrollbind)
call assert_equal(0, &cursorbind)
call assert_equal(1, &wrap)
@@ -158,7 +160,7 @@ func Common_vert_split()
wincmd w
call assert_equal(0, &diff)
call assert_equal('marker', &foldmethod)
- call assert_equal(4, &foldcolumn)
+ call assert_equal('4', &foldcolumn)
call assert_equal(0, &scrollbind)
call assert_equal(0, &cursorbind)
call assert_equal(1, &wrap)
diff --git a/src/nvim/testdir/test_digraph.vim b/src/nvim/testdir/test_digraph.vim
index 5da05e85b5..1792dcc00b 100644
--- a/src/nvim/testdir/test_digraph.vim
+++ b/src/nvim/testdir/test_digraph.vim
@@ -433,6 +433,18 @@ func Test_digraphs_output()
call assert_equal('Z% Ж 1046', matchstr(out, '\C\<Z%\D*1046\>'))
call assert_equal('u- ū 363', matchstr(out, '\C\<u-\D*363\>'))
call assert_equal('SH ^A 1', matchstr(out, '\C\<SH\D*1\>'))
+ call assert_notmatch('Latin supplement', out)
+
+ let out_bang_without_custom = execute(':digraph!')
+ digraph lt 60
+ let out_bang_with_custom = execute(':digraph!')
+ call assert_notmatch('lt', out_bang_without_custom)
+ call assert_match("^\n"
+ \ .. "NU ^@ 10 .*\n"
+ \ .. "Latin supplement\n"
+ \ .. "!I ¡ 161 .*\n"
+ \ .. ".*\n"
+ \ .. 'Custom\n.*\<lt < 60\>', out_bang_with_custom)
bw!
endfunc
diff --git a/src/nvim/testdir/test_edit.vim b/src/nvim/testdir/test_edit.vim
index 98fa9a3c47..12d5d9790e 100644
--- a/src/nvim/testdir/test_edit.vim
+++ b/src/nvim/testdir/test_edit.vim
@@ -1439,7 +1439,7 @@ func Test_edit_alt()
call delete('XAltFile')
endfunc
-func Test_leave_insert_autocmd()
+func Test_edit_InsertLeave()
new
au InsertLeave * let g:did_au = 1
let g:did_au = 0
@@ -1469,6 +1469,21 @@ func Test_leave_insert_autocmd()
iunmap x
endfunc
+func Test_edit_InsertLeave_undo()
+ new XtestUndo
+ set undofile
+ au InsertLeave * wall
+ exe "normal ofoo\<Esc>"
+ call assert_equal(2, line('$'))
+ normal u
+ call assert_equal(1, line('$'))
+
+ bwipe!
+ au! InsertLeave
+ call delete('XtestUndo')
+ set undofile&
+endfunc
+
" Test for inserting characters using CTRL-V followed by a number.
func Test_edit_special_chars()
new
diff --git a/src/nvim/testdir/test_escaped_glob.vim b/src/nvim/testdir/test_escaped_glob.vim
index aad3a1e835..2bfd82c296 100644
--- a/src/nvim/testdir/test_escaped_glob.vim
+++ b/src/nvim/testdir/test_escaped_glob.vim
@@ -17,7 +17,7 @@ function Test_glob()
" Setting 'shell' to an invalid name causes a memory leak.
sandbox call assert_equal("", glob('Xxx\{'))
sandbox call assert_equal("", glob('Xxx\$'))
- w! Xxx{
+ w! Xxx\{
" } to fix highlighting
w! Xxx\$
sandbox call assert_equal("Xxx{", glob('Xxx\{'))
diff --git a/src/nvim/testdir/test_excmd.vim b/src/nvim/testdir/test_excmd.vim
index f5ce979208..4a027c3864 100644
--- a/src/nvim/testdir/test_excmd.vim
+++ b/src/nvim/testdir/test_excmd.vim
@@ -8,3 +8,35 @@ func Test_ex_delete()
.dl
call assert_equal(['a', 'c'], getline(1, 2))
endfunc
+
+func Test_buffers_lastused()
+ edit bufc " oldest
+
+ sleep 1200m
+ edit bufa " middle
+
+ sleep 1200m
+ edit bufb " newest
+
+ enew
+
+ let ls = split(execute('buffers t', 'silent!'), '\n')
+ let bufs = []
+ for line in ls
+ let bufs += [split(line, '"\s*')[1:2]]
+ endfor
+
+ let names = []
+ for buf in bufs
+ if buf[0] !=# '[No Name]'
+ let names += [buf[0]]
+ endif
+ endfor
+
+ call assert_equal(['bufb', 'bufa', 'bufc'], names)
+ call assert_match('[0-2] seconds ago', bufs[1][1])
+
+ bwipeout bufa
+ bwipeout bufb
+ bwipeout bufc
+endfunc
diff --git a/src/nvim/testdir/test_expr.vim b/src/nvim/testdir/test_expr.vim
index dd546dbf71..264d8b000f 100644
--- a/src/nvim/testdir/test_expr.vim
+++ b/src/nvim/testdir/test_expr.vim
@@ -475,6 +475,8 @@ func Test_funcref()
let OneByRef = funcref('One')
call assert_equal(2, OneByRef())
call assert_fails('echo funcref("{")', 'E475:')
+ let OneByRef = funcref("One", repeat(["foo"], 20))
+ call assert_fails('let OneByRef = funcref("One", repeat(["foo"], 21))', 'E118:')
endfunc
func Test_empty_concatenate()
diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim
index 2334cc95a7..ace56a375f 100644
--- a/src/nvim/testdir/test_filetype.vim
+++ b/src/nvim/testdir/test_filetype.vim
@@ -79,6 +79,7 @@ let s:filename_checks = {
\ 'bib': ['file.bib'],
\ 'bindzone': ['named.root'],
\ 'blank': ['file.bl'],
+ \ 'bsdl': ['file.bsd', 'file.bsdl'],
\ 'bst': ['file.bst'],
\ 'bzr': ['bzr_log.any'],
\ 'c': ['enlightenment/file.cfg', 'file.qc', 'file.c'],
@@ -139,7 +140,7 @@ let s:filename_checks = {
\ 'dockerfile': ['Dockerfile', 'file.Dockerfile'],
\ 'dosbatch': ['file.bat', 'file.sys'],
\ 'dosini': ['.editorconfig', '/etc/yum.conf', 'file.ini'],
- \ 'dot': ['file.dot'],
+ \ 'dot': ['file.dot', 'file.gv'],
\ 'dracula': ['file.drac', 'file.drc', 'filelvs', 'filelpe'],
\ 'dsl': ['file.dsl'],
\ 'dtd': ['file.dtd'],
@@ -234,6 +235,7 @@ let s:filename_checks = {
\ 'kconfig': ['Kconfig', 'Kconfig.debug'],
\ 'kivy': ['file.kv'],
\ 'kix': ['file.kix'],
+ \ 'kotlin': ['file.kt', 'file.ktm', 'file.kts'],
\ 'kscript': ['file.ks'],
\ 'kwt': ['file.k'],
\ 'lace': ['file.ace', 'file.ACE'],
@@ -319,6 +321,7 @@ let s:filename_checks = {
\ 'openroad': ['file.or'],
\ 'ora': ['file.ora'],
\ 'pamconf': ['/etc/pam.conf'],
+ \ 'pamenv': ['/etc/security/pam_env.conf', '/home/user/.pam_environment'],
\ 'papp': ['file.papp', 'file.pxml', 'file.pxsl'],
\ 'pascal': ['file.pas', 'file.dpr'],
\ 'passwd': ['any/etc/passwd', 'any/etc/passwd-', 'any/etc/passwd.edit', 'any/etc/shadow', 'any/etc/shadow-', 'any/etc/shadow.edit', 'any/var/backups/passwd.bak', 'any/var/backups/shadow.bak'],
@@ -435,7 +438,7 @@ let s:filename_checks = {
\ 'swiftgyb': ['file.swift.gyb'],
\ 'sil': ['file.sil'],
\ 'sysctl': ['/etc/sysctl.conf', '/etc/sysctl.d/file.conf'],
- \ 'systemd': ['any/systemd/file.automount', 'any/systemd/file.mount', 'any/systemd/file.path', 'any/systemd/file.service', 'any/systemd/file.socket', 'any/systemd/file.swap', 'any/systemd/file.target', 'any/systemd/file.timer', '/etc/systemd/system/some.d/file.conf', '/etc/systemd/system/some.d/.#file'],
+ \ 'systemd': ['any/systemd/file.automount', 'any/systemd/file.mount', 'any/systemd/file.path', 'any/systemd/file.service', 'any/systemd/file.socket', 'any/systemd/file.swap', 'any/systemd/file.target', 'any/systemd/file.timer', '/etc/systemd/system/some.d/file.conf', '/etc/systemd/system/some.d/.#file', '/etc/systemd/system/.#otherfile', '/home/user/.config/systemd/user/some.d/mine.conf', '/home/user/.config/systemd/user/some.d/.#file', '/home/user/.config/systemd/user/.#otherfile'],
\ 'systemverilog': ['file.sv', 'file.svh'],
\ 'tags': ['tags'],
\ 'tak': ['file.tak'],
@@ -479,7 +482,7 @@ let s:filename_checks = {
\ 'verilog': ['file.v'],
\ 'verilogams': ['file.va', 'file.vams'],
\ 'vgrindefs': ['vgrindefs'],
- \ 'vhdl': ['file.hdl', 'file.vhd', 'file.vhdl', 'file.vbe', 'file.vst', 'file.vhdl_123'],
+ \ 'vhdl': ['file.hdl', 'file.vhd', 'file.vhdl', 'file.vbe', 'file.vst', 'file.vhdl_123', 'file.vho'],
\ 'vim': ['file.vim', 'file.vba', '.exrc', '_exrc'],
\ 'viminfo': ['.viminfo', '_viminfo'],
\ 'vmasm': ['file.mar'],
@@ -599,6 +602,7 @@ let s:script_checks = {
\ 'haskell': [['#!/path/haskell']],
\ 'cpp': [['// Standard iostream objects -*- C++ -*-'],
\ ['// -*- C++ -*-']],
+ \ 'yaml': [['%YAML 1.2']],
\ }
func Test_script_detection()
diff --git a/src/nvim/testdir/test_fold.vim b/src/nvim/testdir/test_fold.vim
index 56ed543d4b..692f6e4780 100644
--- a/src/nvim/testdir/test_fold.vim
+++ b/src/nvim/testdir/test_fold.vim
@@ -1,6 +1,7 @@
" Test for folding
source view_util.vim
+source screendump.vim
func PrepIndent(arg)
return [a:arg] + repeat(["\t".a:arg], 5)
diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim
index 7822507f86..51689db9c4 100644
--- a/src/nvim/testdir/test_functions.vim
+++ b/src/nvim/testdir/test_functions.vim
@@ -186,6 +186,32 @@ func Test_strftime()
call assert_fails('call strftime([])', 'E730:')
call assert_fails('call strftime("%Y", [])', 'E745:')
+
+ " Check that the time changes after we change the timezone
+ " Save previous timezone value, if any
+ if exists('$TZ')
+ let tz = $TZ
+ endif
+
+ " Force EST and then UTC, save the current hour (24-hour clock) for each
+ let $TZ = 'EST' | let est = strftime('%H')
+ let $TZ = 'UTC' | let utc = strftime('%H')
+
+ " Those hours should be two bytes long, and should not be the same; if they
+ " are, a tzset(3) call may have failed somewhere
+ call assert_equal(strlen(est), 2)
+ call assert_equal(strlen(utc), 2)
+ " TODO: this fails on MS-Windows
+ if has('unix')
+ call assert_notequal(est, utc)
+ endif
+
+ " If we cached a timezone value, put it back, otherwise clear it
+ if exists('tz')
+ let $TZ = tz
+ else
+ unlet $TZ
+ endif
endfunc
func Test_resolve()
@@ -640,6 +666,16 @@ func Test_getbufvar()
call assert_equal('iso-8859-2', getbufvar(bufnr('%'), '&fenc'))
close
+ " Get the b: dict.
+ let b:testvar = 'one'
+ new
+ let b:testvar = 'two'
+ let thebuf = bufnr()
+ wincmd w
+ call assert_equal('two', getbufvar(thebuf, 'testvar'))
+ call assert_equal('two', getbufvar(thebuf, '').testvar)
+ bwipe!
+
set fileformats&
endfunc
@@ -1271,3 +1307,33 @@ func Test_bufadd_bufload()
bwipe otherName
call assert_equal(0, bufexists('someName'))
endfunc
+
+func Test_readdir()
+ call mkdir('Xdir')
+ call writefile([], 'Xdir/foo.txt')
+ call writefile([], 'Xdir/bar.txt')
+ call mkdir('Xdir/dir')
+
+ " All results
+ let files = readdir('Xdir')
+ call assert_equal(['bar.txt', 'dir', 'foo.txt'], sort(files))
+
+ " Only results containing "f"
+ let files = readdir('Xdir', { x -> stridx(x, 'f') !=- 1 })
+ call assert_equal(['foo.txt'], sort(files))
+
+ " Only .txt files
+ let files = readdir('Xdir', { x -> x =~ '.txt$' })
+ call assert_equal(['bar.txt', 'foo.txt'], sort(files))
+
+ " Only .txt files with string
+ let files = readdir('Xdir', 'v:val =~ ".txt$"')
+ call assert_equal(['bar.txt', 'foo.txt'], sort(files))
+
+ " Limit to 1 result.
+ let l = []
+ let files = readdir('Xdir', {x -> len(add(l, x)) == 2 ? -1 : 1})
+ call assert_equal(1, len(files))
+
+ call delete('Xdir', 'rf')
+endfunc
diff --git a/src/nvim/testdir/test_hardcopy.vim b/src/nvim/testdir/test_hardcopy.vim
index ced13b107c..6125f9b993 100644
--- a/src/nvim/testdir/test_hardcopy.vim
+++ b/src/nvim/testdir/test_hardcopy.vim
@@ -1,39 +1,137 @@
" Test :hardcopy
-func Test_printoptions_parsing()
- " Only test that this doesn't throw an error.
- set printoptions=left:5in,right:10pt,top:8mm,bottom:2pc
- set printoptions=left:2in,top:30pt,right:16mm,bottom:3pc
- set printoptions=header:3,syntax:y,number:7,wrap:n
- set printoptions=duplex:short,collate:n,jobsplit:y,portrait:n
- set printoptions=paper:10x14
- set printoptions=paper:A3
- set printoptions=paper:A4
- set printoptions=paper:A5
- set printoptions=paper:B4
- set printoptions=paper:B5
- set printoptions=paper:executive
- set printoptions=paper:folio
- set printoptions=paper:ledger
- set printoptions=paper:legal
- set printoptions=paper:letter
- set printoptions=paper:quarto
- set printoptions=paper:statement
- set printoptions=paper:tabloid
- set printoptions=formfeed:y
- set printoptions=
- set printoptions&
+func Test_printoptions()
+ edit test_hardcopy.vim
+ syn on
+
+ for opt in ['left:5in,right:10pt,top:8mm,bottom:2pc',
+ \ 'left:2in,top:30pt,right:16mm,bottom:3pc',
+ \ 'header:3,syntax:y,number:y,wrap:n',
+ \ 'header:3,syntax:n,number:y,wrap:y',
+ \ 'duplex:short,collate:n,jobsplit:y,portrait:n',
+ \ 'duplex:long,collate:y,jobsplit:n,portrait:y',
+ \ 'paper:10x14',
+ \ 'paper:A3',
+ \ 'paper:A4',
+ \ 'paper:A5',
+ \ 'paper:B4',
+ \ 'paper:B5',
+ \ 'paper:executive',
+ \ 'paper:folio',
+ \ 'paper:ledger',
+ \ 'paper:legal',
+ \ 'paper:letter',
+ \ 'paper:quarto',
+ \ 'paper:statement',
+ \ 'paper:tabloid',
+ \ 'formfeed:y',
+ \ '']
+ exe 'set printoptions=' .. opt
+ if has('postscript')
+ hardcopy > Xhardcopy_printoptions
+ let lines = readfile('Xhardcopy_printoptions')
+ call assert_true(len(lines) > 20, opt)
+ call assert_true(lines[0] =~ 'PS-Adobe', opt)
+ call delete('Xhardcopy_printoptions')
+ endif
+ endfor
call assert_fails('set printoptions=paper', 'E550:')
call assert_fails('set printoptions=shredder:on', 'E551:')
call assert_fails('set printoptions=left:no', 'E552:')
+ set printoptions&
+ bwipe
endfunc
-func Test_printmbfont_parsing()
- " Only test that this doesn't throw an error.
- set printmbfont=r:WadaMin-Regular,b:WadaMin-Bold,i:WadaMin-Italic,o:WadaMin-Bold-Italic,c:yes,a:no
- set printmbfont=
+func Test_printmbfont()
+ " Print a small help page which contains tabs to cover code that expands tabs to spaces.
+ help help
+ syn on
+
+ for opt in [':WadaMin-Regular,b:WadaMin-Bold,i:WadaMin-Italic,o:WadaMin-Bold-Italic,c:yes,a:no',
+ \ '']
+ exe 'set printmbfont=' .. opt
+ if has('postscript')
+ hardcopy > Xhardcopy_printmbfont
+ let lines = readfile('Xhardcopy_printmbfont')
+ call assert_true(len(lines) > 20, opt)
+ call assert_true(lines[0] =~ 'PS-Adobe', opt)
+ call delete('Xhardcopy_printmbfont')
+ endif
+ endfor
set printmbfont&
+ bwipe
+endfunc
+
+func Test_printexpr()
+ if !has('unix')
+ return
+ endif
+
+ " Not a very useful printexpr value, but enough to test
+ " hardcopy with 'printexpr'.
+ function PrintFile(fname)
+ call writefile(['Test printexpr: ' .. v:cmdarg],
+ \ 'Xhardcopy_printexpr')
+ call delete(a:fname)
+ return 0
+ endfunc
+ set printexpr=PrintFile(v:fname_in)
+
+ help help
+ hardcopy dummy args
+ call assert_equal(['Test printexpr: dummy args'],
+ \ readfile('Xhardcopy_printexpr'))
+ call delete('Xhardcopy_printexpr')
+
+ " Function return 1 to test print failure.
+ function PrintFails(fname)
+ call delete(a:fname)
+ return 1
+ endfunc
+ set printexpr=PrintFails(v:fname_in)
+ call assert_fails('hardcopy', 'E365:')
+
+ set printexpr&
+ bwipe
+endfunc
+
+func Test_errors()
+ " FIXME: Windows fails differently than Unix.
+ if has('unix')
+ edit test_hardcopy.vim
+ call assert_fails('hardcopy >', 'E324:')
+ bwipe
+ endif
+endfunc
+
+func Test_dark_background()
+ edit test_hardcopy.vim
+ syn on
+
+ for bg in ['dark', 'light']
+ exe 'set background=' .. bg
+
+ if has('postscript')
+ hardcopy > Xhardcopy_dark_background
+ let lines = readfile('Xhardcopy_dark_background')
+ call assert_true(len(lines) > 20)
+ call assert_true(lines[0] =~ 'PS-Adobe')
+ call delete('Xhardcopy_dark_background')
+ endif
+ endfor
+
+ set background&
+ bwipe
+endfun
+
+func Test_empty_buffer()
+ " FIXME: Unclear why this fails on Windows.
+ if has('unix')
+ new
+ call assert_equal("\nNo text to be printed", execute('hardcopy'))
+ bwipe
+ endif
endfunc
func Test_printheader_parsing()
@@ -46,22 +144,6 @@ func Test_printheader_parsing()
set printheader&
endfunc
-" Test that :hardcopy produces a non-empty file.
-" We don't check much of the contents.
-func Test_with_syntax()
- if has('postscript')
- edit test_hardcopy.vim
- set printoptions=syntax:y
- syn on
- hardcopy > Xhardcopy
- let lines = readfile('Xhardcopy')
- call assert_true(len(lines) > 20)
- call assert_true(lines[0] =~ 'PS-Adobe')
- call delete('Xhardcopy')
- set printoptions&
- endif
-endfunc
-
func Test_fname_with_spaces()
if !has('postscript')
return
@@ -86,4 +168,3 @@ func Test_illegal_byte()
bwipe!
call delete('Xpstest')
endfunc
-
diff --git a/src/nvim/testdir/test_highlight.vim b/src/nvim/testdir/test_highlight.vim
index a320e8edc8..6aa187b17e 100644
--- a/src/nvim/testdir/test_highlight.vim
+++ b/src/nvim/testdir/test_highlight.vim
@@ -1,6 +1,7 @@
" Tests for ":highlight" and highlighting.
source view_util.vim
+source screendump.vim
func Test_highlight()
" basic test if ":highlight" doesn't crash
diff --git a/src/nvim/testdir/test_ins_complete.vim b/src/nvim/testdir/test_ins_complete.vim
index e6d427db05..1c275d5bd1 100644
--- a/src/nvim/testdir/test_ins_complete.vim
+++ b/src/nvim/testdir/test_ins_complete.vim
@@ -120,7 +120,7 @@ function! s:CompleteDone_CompleteFuncDict( findstart, base )
\ 'menu': 'extra text',
\ 'info': 'words are cool',
\ 'kind': 'W',
- \ 'user_data': 'test'
+ \ 'user_data': ['one', 'two']
\ }
\ ]
\ }
@@ -130,18 +130,20 @@ func s:CompleteDone_CheckCompletedItemNone()
let s:called_completedone = 1
endfunc
-function! s:CompleteDone_CheckCompletedItemDict()
+func s:CompleteDone_CheckCompletedItemDict(pre)
call assert_equal( 'aword', v:completed_item[ 'word' ] )
call assert_equal( 'wrd', v:completed_item[ 'abbr' ] )
call assert_equal( 'extra text', v:completed_item[ 'menu' ] )
call assert_equal( 'words are cool', v:completed_item[ 'info' ] )
call assert_equal( 'W', v:completed_item[ 'kind' ] )
- call assert_equal( 'test', v:completed_item[ 'user_data' ] )
+ call assert_equal( ['one', 'two'], v:completed_item[ 'user_data' ] )
- call assert_equal('function', complete_info().mode)
+ if a:pre
+ call assert_equal('function', complete_info().mode)
+ endif
let s:called_completedone = 1
-endfunction
+endfunc
func Test_CompleteDoneNone()
throw 'skipped: Nvim does not support v:none'
@@ -161,13 +163,14 @@ func Test_CompleteDoneNone()
endfunc
func Test_CompleteDoneDict()
- au CompleteDone * :call <SID>CompleteDone_CheckCompletedItemDict()
+ au CompleteDonePre * :call <SID>CompleteDone_CheckCompletedItemDict(1)
+ au CompleteDone * :call <SID>CompleteDone_CheckCompletedItemDict(0)
set completefunc=<SID>CompleteDone_CompleteFuncDict
execute "normal a\<C-X>\<C-U>\<C-Y>"
set completefunc&
- call assert_equal('test', v:completed_item[ 'user_data' ])
+ call assert_equal(['one', 'two'], v:completed_item[ 'user_data' ])
call assert_true(s:called_completedone)
let s:called_completedone = 0
diff --git a/src/nvim/testdir/test_listchars.vim b/src/nvim/testdir/test_listchars.vim
index 57cfaa298e..dcc588120c 100644
--- a/src/nvim/testdir/test_listchars.vim
+++ b/src/nvim/testdir/test_listchars.vim
@@ -58,6 +58,26 @@ func Test_listchars()
call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
endfor
+ " tab with 3rd character and linebreak set
+ set listchars-=tab:<=>
+ set listchars+=tab:<·>
+ set linebreak
+ let expected = [
+ \ '<······>aa<····>$',
+ \ '..bb<··>--$',
+ \ '...cccc>-$',
+ \ 'dd........ee--<>$',
+ \ '-$'
+ \ ]
+ redraw!
+ for i in range(1, 5)
+ call cursor(i, 1)
+ call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
+ endfor
+ set nolinebreak
+ set listchars-=tab:<·>
+ set listchars+=tab:<=>
+
set listchars-=trail:-
let expected = [
\ '<======>aa<====>$',
diff --git a/src/nvim/testdir/test_maparg.vim b/src/nvim/testdir/test_maparg.vim
index 0b941d51ec..5f73bd80ad 100644
--- a/src/nvim/testdir/test_maparg.vim
+++ b/src/nvim/testdir/test_maparg.vim
@@ -41,6 +41,11 @@ function Test_maparg()
map abc y<S-char-114>y
call assert_equal("yRy", maparg('abc'))
+ omap { w
+ let d = maparg('{', 'o', 0, 1)
+ call assert_equal(['{', 'w', 'o'], [d.lhs, d.rhs, d.mode])
+ ounmap {
+
map abc <Nop>
call assert_equal("<Nop>", maparg('abc'))
unmap abc
@@ -62,3 +67,5 @@ function Test_range_map()
execute "normal a\uf040\<Esc>"
call assert_equal("abcd", getline(1))
endfunction
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_mapping.vim b/src/nvim/testdir/test_mapping.vim
index f14f292a92..82562339f6 100644
--- a/src/nvim/testdir/test_mapping.vim
+++ b/src/nvim/testdir/test_mapping.vim
@@ -390,3 +390,77 @@ func Test_motionforce_omap()
delfunc Select
delfunc GetCommand
endfunc
+
+" Test for mapping errors
+func Test_map_error()
+ call assert_fails('unmap', 'E474:')
+ call assert_fails("exe 'map ' .. repeat('a', 51) .. ' :ls'", 'E474:')
+ call assert_fails('unmap abc', 'E31:')
+ call assert_fails('unabbr abc', 'E24:')
+ call assert_equal('', maparg(''))
+ call assert_fails('echo maparg("abc", [])', 'E730:')
+
+ " unique map
+ map ,w /[#&!]<CR>
+ call assert_fails("map <unique> ,w /[#&!]<CR>", 'E227:')
+ " unique buffer-local map
+ call assert_fails("map <buffer> <unique> ,w /[.,;]<CR>", 'E225:')
+ unmap ,w
+
+ " unique abbreviation
+ abbr SP special
+ call assert_fails("abbr <unique> SP special", 'E226:')
+ " unique buffer-local map
+ call assert_fails("abbr <buffer> <unique> SP special", 'E224:')
+ unabbr SP
+
+ call assert_fails('mapclear abc', 'E474:')
+ call assert_fails('abclear abc', 'E474:')
+endfunc
+
+" Test for <special> key mapping
+func Test_map_special()
+ throw 'skipped: Nvim does not support cpoptions flag "<"'
+ new
+ let old_cpo = &cpo
+ set cpo+=<
+ imap <F12> Blue
+ call feedkeys("i\<F12>", "x")
+ call assert_equal("<F12>", getline(1))
+ call feedkeys("ddi<F12>", "x")
+ call assert_equal("Blue", getline(1))
+ iunmap <F12>
+ imap <special> <F12> Green
+ call feedkeys("ddi\<F12>", "x")
+ call assert_equal("Green", getline(1))
+ call feedkeys("ddi<F12>", "x")
+ call assert_equal("<F12>", getline(1))
+ iunmap <special> <F12>
+ let &cpo = old_cpo
+ %bwipe!
+endfunc
+
+" Test for hasmapto()
+func Test_hasmapto()
+ call assert_equal(0, hasmapto('/^\k\+ ('))
+ call assert_equal(0, hasmapto('/^\k\+ (', 'n'))
+ nmap ,f /^\k\+ (<CR>
+ call assert_equal(1, hasmapto('/^\k\+ ('))
+ call assert_equal(1, hasmapto('/^\k\+ (', 'n'))
+ call assert_equal(0, hasmapto('/^\k\+ (', 'v'))
+
+ call assert_equal(0, hasmapto('/^\k\+ (', 'n', 1))
+endfunc
+
+" Test for command-line completion of maps
+func Test_mapcomplete()
+ call assert_equal(['<buffer>', '<expr>', '<nowait>', '<script>',
+ \ '<silent>', '<special>', '<unique>'],
+ \ getcompletion('', 'mapping'))
+ call assert_equal([], getcompletion(',d', 'mapping'))
+
+ call feedkeys(":abbr! \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_match("abbr! \x01", @:)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_messages.vim b/src/nvim/testdir/test_messages.vim
index 265dee66ce..7fbf04311d 100644
--- a/src/nvim/testdir/test_messages.vim
+++ b/src/nvim/testdir/test_messages.vim
@@ -1,4 +1,4 @@
-" Tests for :messages
+" Tests for :messages, :echomsg, :echoerr
function Test_messages()
let oldmore = &more
@@ -6,6 +6,9 @@ function Test_messages()
set nomore
" Avoid the "message maintainer" line.
let $LANG = ''
+ let $LC_ALL = ''
+ let $LC_MESSAGES = ''
+ let $LC_COLLATE = ''
let arr = map(range(10), '"hello" . v:val')
for s in arr
@@ -65,6 +68,35 @@ func Test_message_completion()
call assert_equal('"message clear', @:)
endfunc
+func Test_echomsg()
+ call assert_equal("\nhello", execute(':echomsg "hello"'))
+ call assert_equal("\n", execute(':echomsg ""'))
+ call assert_equal("\n12345", execute(':echomsg 12345'))
+ call assert_equal("\n[]", execute(':echomsg []'))
+ call assert_equal("\n[1, 2, 3]", execute(':echomsg [1, 2, 3]'))
+ call assert_equal("\n{}", execute(':echomsg {}'))
+ call assert_equal("\n{'a': 1, 'b': 2}", execute(':echomsg {"a": 1, "b": 2}'))
+ if has('float')
+ call assert_equal("\n1.23", execute(':echomsg 1.23'))
+ endif
+ call assert_match("function('<lambda>\\d*')", execute(':echomsg {-> 1234}'))
+endfunc
+
+func Test_echoerr()
+ throw 'skipped: Nvim does not support test_ignore_error()'
+ call test_ignore_error('IgNoRe')
+ call assert_equal("\nIgNoRe hello", execute(':echoerr "IgNoRe hello"'))
+ call assert_equal("\n12345 IgNoRe", execute(':echoerr 12345 "IgNoRe"'))
+ call assert_equal("\n[1, 2, 'IgNoRe']", execute(':echoerr [1, 2, "IgNoRe"]'))
+ call assert_equal("\n{'IgNoRe': 2, 'a': 1}", execute(':echoerr {"a": 1, "IgNoRe": 2}'))
+ if has('float')
+ call assert_equal("\n1.23 IgNoRe", execute(':echoerr 1.23 "IgNoRe"'))
+ endif
+ call test_ignore_error('<lambda>')
+ call assert_match("function('<lambda>\\d*')", execute(':echoerr {-> 1234}'))
+ call test_ignore_error('RESET')
+endfunc
+
func Test_echospace()
set noruler noshowcmd laststatus=1
call assert_equal(&columns - 1, v:echospace)
diff --git a/src/nvim/testdir/test_mksession.vim b/src/nvim/testdir/test_mksession.vim
index b7169444d1..9c9e04be07 100644
--- a/src/nvim/testdir/test_mksession.vim
+++ b/src/nvim/testdir/test_mksession.vim
@@ -2,10 +2,13 @@
scriptencoding latin1
-if !has('multi_byte') || !has('mksession')
+if !has('mksession')
finish
endif
+source shared.vim
+source term_util.vim
+
func Test_mksession()
tabnew
let wrap_save = &wrap
@@ -122,6 +125,34 @@ func Test_mksession_large_winheight()
call delete('Xtest_mks_winheight.out')
endfunc
+func Test_mksession_rtp()
+ if has('win32')
+ " TODO: fix problem with backslashes
+ return
+ endif
+ new
+ set sessionoptions+=options
+ let _rtp=&rtp
+ " Make a real long (invalid) runtimepath value,
+ " that should exceed PATH_MAX (hopefully)
+ let newrtp=&rtp.',~'.repeat('/foobar', 1000)
+ let newrtp.=",".expand("$HOME")."/.vim"
+ let &rtp=newrtp
+
+ " determine expected value
+ let expected=split(&rtp, ',')
+ let expected = map(expected, '"set runtimepath+=".v:val')
+ let expected = ['set runtimepath='] + expected
+ let expected = map(expected, {v,w -> substitute(w, $HOME, "~", "g")})
+
+ mksession! Xtest_mks.out
+ let &rtp=_rtp
+ let li = filter(readfile('Xtest_mks.out'), 'v:val =~# "runtimepath"')
+ call assert_equal(expected, li)
+
+ call delete('Xtest_mks.out')
+endfunc
+
" Verify that arglist is stored correctly to the session file.
func Test_mksession_arglist()
argdel *
diff --git a/src/nvim/testdir/test_number.vim b/src/nvim/testdir/test_number.vim
index 59debcea0d..3c9afc41d5 100644
--- a/src/nvim/testdir/test_number.vim
+++ b/src/nvim/testdir/test_number.vim
@@ -252,3 +252,14 @@ func Test_numberwidth_adjusted()
call s:compare_lines(expect, lines)
call s:close_windows()
endfunc
+
+" This was causing a memcheck error
+func Test_relativenumber_uninitialised()
+ new
+ set rnu
+ call setline(1, ["a", "b"])
+ redraw
+ call feedkeys("j", 'xt')
+ redraw
+ bwipe!
+endfunc
diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim
index 6fcc372591..41f1710faf 100644
--- a/src/nvim/testdir/test_options.vim
+++ b/src/nvim/testdir/test_options.vim
@@ -277,6 +277,21 @@ func Test_set_errors()
call assert_fails('set t_foo=', 'E846:')
endfunc
+" Must be executed before other tests that set 'term'.
+func Test_000_term_option_verbose()
+ if has('nvim') || has('gui_running')
+ return
+ endif
+ let verb_cm = execute('verbose set t_cm')
+ call assert_notmatch('Last set from', verb_cm)
+
+ let term_save = &term
+ set term=ansi
+ let verb_cm = execute('verbose set t_cm')
+ call assert_match('Last set from.*test_options.vim', verb_cm)
+ let &term = term_save
+endfunc
+
func Test_set_ttytype()
" Nvim does not support 'ttytype'.
if !has('nvim') && !has('gui_running') && has('unix')
@@ -496,3 +511,46 @@ func Test_shortmess_F2()
bwipe
bwipe
endfunc
+
+func Test_local_scrolloff()
+ set so=5
+ set siso=7
+ split
+ call assert_equal(5, &so)
+ setlocal so=3
+ call assert_equal(3, &so)
+ wincmd w
+ call assert_equal(5, &so)
+ wincmd w
+ setlocal so<
+ call assert_equal(5, &so)
+ setlocal so=0
+ call assert_equal(0, &so)
+ setlocal so=-1
+ call assert_equal(5, &so)
+
+ call assert_equal(7, &siso)
+ setlocal siso=3
+ call assert_equal(3, &siso)
+ wincmd w
+ call assert_equal(7, &siso)
+ wincmd w
+ setlocal siso<
+ call assert_equal(7, &siso)
+ setlocal siso=0
+ call assert_equal(0, &siso)
+ setlocal siso=-1
+ call assert_equal(7, &siso)
+
+ close
+ set so&
+ set siso&
+endfunc
+
+func Test_visualbell()
+ set belloff=
+ set visualbell
+ call assert_beeps('normal 0h')
+ set novisualbell
+ set belloff=all
+endfunc
diff --git a/src/nvim/testdir/test_plus_arg_edit.vim b/src/nvim/testdir/test_plus_arg_edit.vim
index f6d31e7626..e91a6e467a 100644
--- a/src/nvim/testdir/test_plus_arg_edit.vim
+++ b/src/nvim/testdir/test_plus_arg_edit.vim
@@ -8,3 +8,31 @@ function Test_edit()
call delete('Xfile1')
call delete('Xfile2')
endfunction
+
+func Test_edit_bad()
+ if !has('multi_byte')
+ finish
+ endif
+
+ " Test loading a utf8 file with bad utf8 sequences.
+ call writefile(["[\xff][\xc0][\xe2\x89\xf0][\xc2\xc2]"], "Xfile")
+ new
+
+ " Without ++bad=..., the default behavior is like ++bad=?
+ e! ++enc=utf8 Xfile
+ call assert_equal('[?][?][???][??]', getline(1))
+
+ e! ++enc=utf8 ++bad=_ Xfile
+ call assert_equal('[_][_][___][__]', getline(1))
+
+ e! ++enc=utf8 ++bad=drop Xfile
+ call assert_equal('[][][][]', getline(1))
+
+ e! ++enc=utf8 ++bad=keep Xfile
+ call assert_equal("[\xff][\xc0][\xe2\x89\xf0][\xc2\xc2]", getline(1))
+
+ call assert_fails('e! ++enc=utf8 ++bad=foo Xfile', 'E474:')
+
+ bw!
+ call delete('Xfile')
+endfunc
diff --git a/src/nvim/testdir/test_popup.vim b/src/nvim/testdir/test_popup.vim
index e5696f4cbb..bb0ed6e00c 100644
--- a/src/nvim/testdir/test_popup.vim
+++ b/src/nvim/testdir/test_popup.vim
@@ -979,9 +979,9 @@ func Test_CompleteChanged()
call cursor(4, 1)
call feedkeys("Sf\<C-N>", 'tx')
- call assert_equal({'completed_item': {}, 'width': 15,
- \ 'height': 2, 'size': 2,
- \ 'col': 0, 'row': 4, 'scrollbar': v:false}, g:event)
+ call assert_equal({'completed_item': {}, 'width': 15.0,
+ \ 'height': 2.0, 'size': 2,
+ \ 'col': 0.0, 'row': 4.0, 'scrollbar': v:false}, g:event)
call feedkeys("a\<C-N>\<C-N>\<C-E>", 'tx')
call assert_equal('foo', g:word)
call feedkeys("a\<C-N>\<C-N>\<C-N>\<C-E>", 'tx')
@@ -1009,10 +1009,10 @@ func Test_pum_getpos()
setlocal completefunc=UserDefinedComplete
let d = {
- \ 'height': 5,
- \ 'width': 15,
- \ 'row': 1,
- \ 'col': 0,
+ \ 'height': 5.0,
+ \ 'width': 15.0,
+ \ 'row': 1.0,
+ \ 'col': 0.0,
\ 'size': 5,
\ 'scrollbar': v:false,
\ }
diff --git a/src/nvim/testdir/test_profile.vim b/src/nvim/testdir/test_profile.vim
index f3eb88abf0..4b0097617e 100644
--- a/src/nvim/testdir/test_profile.vim
+++ b/src/nvim/testdir/test_profile.vim
@@ -16,6 +16,7 @@ func Test_profile_func()
while l:count > 0
let l:count = l:count - 1
endwhile
+ sleep 1m
endfunc
func! Foo3()
endfunc
@@ -51,7 +52,7 @@ func Test_profile_func()
" - Unlike Foo3(), Foo2() should not be deleted since there is a check
" for v:profiling.
" - Bar() is not reported since it does not match "profile func Foo*".
- call assert_equal(30, len(lines))
+ call assert_equal(31, len(lines))
call assert_equal('FUNCTION Foo1()', lines[0])
call assert_match('Defined:.*Xprofile_func.vim:3', lines[1])
@@ -71,17 +72,18 @@ func Test_profile_func()
call assert_match('^\s*101\s\+.*\swhile l:count > 0$', lines[16])
call assert_match('^\s*100\s\+.*\s let l:count = l:count - 1$', lines[17])
call assert_match('^\s*101\s\+.*\sendwhile$', lines[18])
- call assert_equal('', lines[19])
- call assert_equal('FUNCTIONS SORTED ON TOTAL TIME', lines[20])
- call assert_equal('count total (s) self (s) function', lines[21])
- call assert_match('^\s*1\s\+\d\+\.\d\+\s\+Foo2()$', lines[22])
- call assert_match('^\s*2\s\+\d\+\.\d\+\s\+Foo1()$', lines[23])
- call assert_equal('', lines[24])
- call assert_equal('FUNCTIONS SORTED ON SELF TIME', lines[25])
- call assert_equal('count total (s) self (s) function', lines[26])
- call assert_match('^\s*1\s\+\d\+\.\d\+\s\+Foo2()$', lines[27])
- call assert_match('^\s*2\s\+\d\+\.\d\+\s\+Foo1()$', lines[28])
- call assert_equal('', lines[29])
+ call assert_match('^\s*1\s\+.\+sleep 1m$', lines[19])
+ call assert_equal('', lines[20])
+ call assert_equal('FUNCTIONS SORTED ON TOTAL TIME', lines[21])
+ call assert_equal('count total (s) self (s) function', lines[22])
+ call assert_match('^\s*1\s\+\d\+\.\d\+\s\+Foo2()$', lines[23])
+ call assert_match('^\s*2\s\+\d\+\.\d\+\s\+Foo1()$', lines[24])
+ call assert_equal('', lines[25])
+ call assert_equal('FUNCTIONS SORTED ON SELF TIME', lines[26])
+ call assert_equal('count total (s) self (s) function', lines[27])
+ call assert_match('^\s*1\s\+\d\+\.\d\+\s\+Foo2()$', lines[28])
+ call assert_match('^\s*2\s\+\d\+\.\d\+\s\+Foo1()$', lines[29])
+ call assert_equal('', lines[30])
call delete('Xprofile_func.vim')
call delete('Xprofile_func.log')
diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim
index d7b387c2c9..35555ca9d3 100644
--- a/src/nvim/testdir/test_quickfix.vim
+++ b/src/nvim/testdir/test_quickfix.vim
@@ -1510,6 +1510,13 @@ func Test_setqflist_invalid_nr()
call setqflist([], ' ', {'nr' : $XXX_DOES_NOT_EXIST})
endfunc
+func Test_setqflist_user_sets_buftype()
+ call setqflist([{'text': 'foo'}, {'text': 'bar'}])
+ set buftype=quickfix
+ call setqflist([], 'a')
+ enew
+endfunc
+
func Test_quickfix_set_list_with_act()
call XquickfixSetListWithAct('c')
call XquickfixSetListWithAct('l')
@@ -3311,6 +3318,14 @@ func Test_lvimgrep_crash()
enew | only
endfunc
+func Test_lvimgrep_crash2()
+ au BufNewFile x sfind
+ call assert_fails('lvimgrep x x', 'E480:')
+ call assert_fails('lvimgrep x x x', 'E480:')
+
+ au! BufNewFile
+endfunc
+
" Test for the position of the quickfix and location list window
func Test_qfwin_pos()
" Open two windows
diff --git a/src/nvim/testdir/test_quotestar.vim b/src/nvim/testdir/test_quotestar.vim
index ce5a9ee827..77a5153a81 100644
--- a/src/nvim/testdir/test_quotestar.vim
+++ b/src/nvim/testdir/test_quotestar.vim
@@ -108,7 +108,8 @@ func Do_test_quotestar_for_x11()
call remote_send(name, ":gui -f\<CR>")
endif
" Wait for the server in the GUI to be up and answering requests.
- call WaitForAssert({-> assert_match("1", remote_expr(name, "has('gui_running')", "", 1))})
+ " On some systems and with valgrind this can be very slow.
+ call WaitForAssert({-> assert_match("1", remote_expr(name, "has('gui_running')", "", 1))}, 10000)
call remote_send(name, ":let @* = 'maybe'\<CR>")
call WaitForAssert({-> assert_equal("maybe", remote_expr(name, "@*", "", 2))})
diff --git a/src/nvim/testdir/test_regexp_utf8.vim b/src/nvim/testdir/test_regexp_utf8.vim
index e06c7d6368..ecd0e8d56b 100644
--- a/src/nvim/testdir/test_regexp_utf8.vim
+++ b/src/nvim/testdir/test_regexp_utf8.vim
@@ -192,3 +192,144 @@ func Test_optmatch_toolong()
set re=0
endfunc
+" Test for regexp patterns with multi-byte support, using utf-8.
+func Test_multibyte_chars()
+ " tl is a List of Lists with:
+ " 2: test auto/old/new 0: test auto/old 1: test auto/new
+ " regexp pattern
+ " text to test the pattern on
+ " expected match (optional)
+ " expected submatch 1 (optional)
+ " expected submatch 2 (optional)
+ " etc.
+ " When there is no match use only the first two items.
+ let tl = []
+
+ " Multi-byte character tests. These will fail unless vim is compiled
+ " with Multibyte (FEAT_MBYTE) or BIG/HUGE features.
+ call add(tl, [2, '[[:alpha:][=a=]]\+', '879 aiaãâaiuvna ', 'aiaãâaiuvna'])
+ call add(tl, [2, '[[=a=]]\+', 'ddaãâbcd', 'aãâ']) " equivalence classes
+ call add(tl, [2, '[^ม ]\+', 'มม oijasoifjos ifjoisj f osij j มมมมม abcd', 'oijasoifjos'])
+ call add(tl, [2, ' [^ ]\+', 'start มabcdม ', ' มabcdม'])
+ call add(tl, [2, '[ม[:alpha:][=a=]]\+', '879 aiaãมâมaiuvna ', 'aiaãมâมaiuvna'])
+
+ " this is not a normal "i" but 0xec
+ call add(tl, [2, '\p\+', 'ìa', 'ìa'])
+ call add(tl, [2, '\p*', 'aあ', 'aあ'])
+
+ " Test recognition of some character classes
+ call add(tl, [2, '\i\+', '&*¨xx ', 'xx'])
+ call add(tl, [2, '\f\+', '&*Ÿfname ', 'fname'])
+
+ " Test composing character matching
+ call add(tl, [2, '.ม', 'xม่x yมy', 'yม'])
+ call add(tl, [2, '.ม่', 'xม่x yมy', 'xม่'])
+ call add(tl, [2, "\u05b9", " x\u05b9 ", "x\u05b9"])
+ call add(tl, [2, ".\u05b9", " x\u05b9 ", "x\u05b9"])
+ call add(tl, [2, "\u05b9\u05bb", " x\u05b9\u05bb ", "x\u05b9\u05bb"])
+ call add(tl, [2, ".\u05b9\u05bb", " x\u05b9\u05bb ", "x\u05b9\u05bb"])
+ call add(tl, [2, "\u05bb\u05b9", " x\u05b9\u05bb ", "x\u05b9\u05bb"])
+ call add(tl, [2, ".\u05bb\u05b9", " x\u05b9\u05bb ", "x\u05b9\u05bb"])
+ call add(tl, [2, "\u05b9", " y\u05bb x\u05b9 ", "x\u05b9"])
+ call add(tl, [2, ".\u05b9", " y\u05bb x\u05b9 ", "x\u05b9"])
+ call add(tl, [2, "\u05b9", " y\u05bb\u05b9 x\u05b9 ", "y\u05bb\u05b9"])
+ call add(tl, [2, ".\u05b9", " y\u05bb\u05b9 x\u05b9 ", "y\u05bb\u05b9"])
+ call add(tl, [1, "\u05b9\u05bb", " y\u05b9 x\u05b9\u05bb ", "x\u05b9\u05bb"])
+ call add(tl, [2, ".\u05b9\u05bb", " y\u05bb x\u05b9\u05bb ", "x\u05b9\u05bb"])
+ call add(tl, [2, "a", "ca\u0300t"])
+ call add(tl, [2, "ca", "ca\u0300t"])
+ call add(tl, [2, "a\u0300", "ca\u0300t", "a\u0300"])
+ call add(tl, [2, 'a\%C', "ca\u0300t", "a\u0300"])
+ call add(tl, [2, 'ca\%C', "ca\u0300t", "ca\u0300"])
+ call add(tl, [2, 'ca\%Ct', "ca\u0300t", "ca\u0300t"])
+
+ " Test \Z
+ call add(tl, [2, 'ú\Z', 'x'])
+ call add(tl, [2, 'יהוה\Z', 'יהוה', 'יהוה'])
+ call add(tl, [2, 'יְהוָה\Z', 'יהוה', 'יהוה'])
+ call add(tl, [2, 'יהוה\Z', 'יְהוָה', 'יְהוָה'])
+ call add(tl, [2, 'יְהוָה\Z', 'יְהוָה', 'יְהוָה'])
+ call add(tl, [2, 'יְ\Z', 'וְיַ', 'יַ'])
+ call add(tl, [2, "ק\u200d\u05b9x\\Z", "xק\u200d\u05b9xy", "ק\u200d\u05b9x"])
+ call add(tl, [2, "ק\u200d\u05b9x\\Z", "xק\u200dxy", "ק\u200dx"])
+ call add(tl, [2, "ק\u200dx\\Z", "xק\u200d\u05b9xy", "ק\u200d\u05b9x"])
+ call add(tl, [2, "ק\u200dx\\Z", "xק\u200dxy", "ק\u200dx"])
+ call add(tl, [2, "\u05b9\\Z", "xyz"])
+ call add(tl, [2, "\\Z\u05b9", "xyz"])
+ call add(tl, [2, "\u05b9\\Z", "xy\u05b9z", "y\u05b9"])
+ call add(tl, [2, "\\Z\u05b9", "xy\u05b9z", "y\u05b9"])
+ call add(tl, [1, "\u05b9\\+\\Z", "xy\u05b9z\u05b9 ", "y\u05b9z\u05b9"])
+ call add(tl, [1, "\\Z\u05b9\\+", "xy\u05b9z\u05b9 ", "y\u05b9z\u05b9"])
+
+ " Combining different tests and features
+ call add(tl, [2, '[^[=a=]]\+', 'ddaãâbcd', 'dd'])
+
+ " Run the tests
+ for t in tl
+ let re = t[0]
+ let pat = t[1]
+ let text = t[2]
+ let matchidx = 3
+ for engine in [0, 1, 2]
+ if engine == 2 && re == 0 || engine == 1 && re == 1
+ continue
+ endif
+ let &regexpengine = engine
+ try
+ let l = matchlist(text, pat)
+ catch
+ call assert_report('Error ' . engine . ': pat: \"' . pat .
+ \ '\", text: \"' . text .
+ \ '\", caused an exception: \"' . v:exception . '\"')
+ endtry
+ " check the match itself
+ if len(l) == 0 && len(t) > matchidx
+ call assert_report('Error ' . engine . ': pat: \"' . pat .
+ \ '\", text: \"' . text .
+ \ '\", did not match, expected: \"' . t[matchidx] . '\"')
+ elseif len(l) > 0 && len(t) == matchidx
+ call assert_report('Error ' . engine . ': pat: \"' . pat .
+ \ '\", text: \"' . text . '\", match: \"' . l[0] .
+ \ '\", expected no match')
+ elseif len(t) > matchidx && l[0] != t[matchidx]
+ call assert_report('Error ' . engine . ': pat: \"' . pat .
+ \ '\", text: \"' . text . '\", match: \"' . l[0] .
+ \ '\", expected: \"' . t[matchidx] . '\"')
+ else
+ " Test passed
+ endif
+ if len(l) > 0
+ " check all the nine submatches
+ for i in range(1, 9)
+ if len(t) <= matchidx + i
+ let e = ''
+ else
+ let e = t[matchidx + i]
+ endif
+ if l[i] != e
+ call assert_report('Error ' . engine . ': pat: \"' . pat .
+ \ '\", text: \"' . text . '\", submatch ' . i .
+ \ ': \"' . l[i] . '\", expected: \"' . e . '\"')
+ endif
+ endfor
+ unlet i
+ endif
+ endfor
+ endfor
+ set regexpengine&
+endfunc
+
+" check that 'ambiwidth' does not change the meaning of \p
+func Test_ambiwidth()
+ set regexpengine=1 ambiwidth=single
+ call assert_equal(0, match("\u00EC", '\p'))
+ set regexpengine=1 ambiwidth=double
+ call assert_equal(0, match("\u00EC", '\p'))
+ set regexpengine=2 ambiwidth=single
+ call assert_equal(0, match("\u00EC", '\p'))
+ set regexpengine=2 ambiwidth=double
+ call assert_equal(0, match("\u00EC", '\p'))
+ set regexpengine& ambiwidth&
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_restricted.vim b/src/nvim/testdir/test_restricted.vim
new file mode 100644
index 0000000000..a29f7c33d3
--- /dev/null
+++ b/src/nvim/testdir/test_restricted.vim
@@ -0,0 +1,103 @@
+" Test for "rvim" or "vim -Z"
+
+source shared.vim
+
+"if has('win32') && has('gui')
+" " Win32 GUI shows a dialog instead of displaying the error in the last line.
+" finish
+"endif
+
+func Test_restricted()
+ call Run_restricted_test('!ls', 'E145:')
+endfunc
+
+func Run_restricted_test(ex_cmd, error)
+ let cmd = GetVimCommand('Xrestricted')
+ if cmd == ''
+ return
+ endif
+
+ " Use a VimEnter autocommand to avoid that the error message is displayed in
+ " a dialog with an OK button.
+ call writefile([
+ \ "func Init()",
+ \ " silent! " . a:ex_cmd,
+ \ " call writefile([v:errmsg], 'Xrestrout')",
+ \ " qa!",
+ \ "endfunc",
+ \ "au VimEnter * call Init()",
+ \ ], 'Xrestricted')
+ call system(cmd . ' -Z')
+ call assert_match(a:error, join(readfile('Xrestrout')))
+
+ call delete('Xrestricted')
+ call delete('Xrestrout')
+endfunc
+
+func Test_restricted_lua()
+ if !has('lua')
+ throw 'Skipped: Lua is not supported'
+ endif
+ call Run_restricted_test('lua print("Hello, Vim!")', 'E981:')
+ call Run_restricted_test('luado return "hello"', 'E981:')
+ call Run_restricted_test('luafile somefile', 'E981:')
+ call Run_restricted_test('call luaeval("expression")', 'E145:')
+endfunc
+
+func Test_restricted_mzscheme()
+ if !has('mzscheme')
+ throw 'Skipped: MzScheme is not supported'
+ endif
+ call Run_restricted_test('mzscheme statement', 'E981:')
+ call Run_restricted_test('mzfile somefile', 'E981:')
+ call Run_restricted_test('call mzeval("expression")', 'E145:')
+endfunc
+
+func Test_restricted_perl()
+ if !has('perl')
+ throw 'Skipped: Perl is not supported'
+ endif
+ " TODO: how to make Safe mode fail?
+ " call Run_restricted_test('perl system("ls")', 'E981:')
+ " call Run_restricted_test('perldo system("hello")', 'E981:')
+ " call Run_restricted_test('perlfile somefile', 'E981:')
+ " call Run_restricted_test('call perleval("system(\"ls\")")', 'E145:')
+endfunc
+
+func Test_restricted_python()
+ if !has('python')
+ throw 'Skipped: Python is not supported'
+ endif
+ call Run_restricted_test('python print "hello"', 'E981:')
+ call Run_restricted_test('pydo return "hello"', 'E981:')
+ call Run_restricted_test('pyfile somefile', 'E981:')
+ call Run_restricted_test('call pyeval("expression")', 'E145:')
+endfunc
+
+func Test_restricted_python3()
+ if !has('python3')
+ throw 'Skipped: Python3 is not supported'
+ endif
+ call Run_restricted_test('py3 print "hello"', 'E981:')
+ call Run_restricted_test('py3do return "hello"', 'E981:')
+ call Run_restricted_test('py3file somefile', 'E981:')
+ call Run_restricted_test('call py3eval("expression")', 'E145:')
+endfunc
+
+func Test_restricted_ruby()
+ if !has('ruby')
+ throw 'Skipped: Ruby is not supported'
+ endif
+ call Run_restricted_test('ruby print "Hello"', 'E981:')
+ call Run_restricted_test('rubydo print "Hello"', 'E981:')
+ call Run_restricted_test('rubyfile somefile', 'E981:')
+endfunc
+
+func Test_restricted_tcl()
+ if !has('tcl')
+ throw 'Skipped: Tcl is not supported'
+ endif
+ call Run_restricted_test('tcl puts "Hello"', 'E981:')
+ call Run_restricted_test('tcldo puts "Hello"', 'E981:')
+ call Run_restricted_test('tclfile somefile', 'E981:')
+endfunc
diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim
index 5d99027ca5..8036dea29f 100644
--- a/src/nvim/testdir/test_search.vim
+++ b/src/nvim/testdir/test_search.vim
@@ -698,3 +698,9 @@ func Test_search_display_pattern()
set norl
endif
endfunc
+
+func Test_search_special()
+ " this was causing illegal memory access and an endless loop
+ set t_PE=
+ exe "norm /\x80PS"
+endfunc
diff --git a/src/nvim/testdir/test_signs.vim b/src/nvim/testdir/test_signs.vim
index ef4b227215..8b1927e4f0 100644
--- a/src/nvim/testdir/test_signs.vim
+++ b/src/nvim/testdir/test_signs.vim
@@ -4,6 +4,8 @@ if !has('signs')
finish
endif
+source screendump.vim
+
func Test_sign()
new
call setline(1, ['a', 'b', 'c', 'd'])
@@ -210,13 +212,16 @@ func Test_sign_completion()
call assert_equal('"sign define Sign linehl=SpellBad SpellCap ' .
\ 'SpellLocal SpellRare', @:)
- call writefile(['foo'], 'XsignOne')
- call writefile(['bar'], 'XsignTwo')
+ call feedkeys(":sign define Sign texthl=Spell\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign define Sign texthl=SpellBad SpellCap ' .
+ \ 'SpellLocal SpellRare', @:)
+
+ call writefile(repeat(["Sun is shining"], 30), "XsignOne")
+ call writefile(repeat(["Sky is blue"], 30), "XsignTwo")
call feedkeys(":sign define Sign icon=Xsig\<C-A>\<C-B>\"\<CR>", 'tx')
call assert_equal('"sign define Sign icon=XsignOne XsignTwo', @:)
- call delete('XsignOne')
- call delete('XsignTwo')
+ " Test for completion of arguments to ':sign undefine'
call feedkeys(":sign undefine \<C-A>\<C-B>\"\<CR>", 'tx')
call assert_equal('"sign undefine Sign1 Sign2', @:)
@@ -227,17 +232,70 @@ func Test_sign_completion()
call feedkeys(":sign place 1 name=\<C-A>\<C-B>\"\<CR>", 'tx')
call assert_equal('"sign place 1 name=Sign1 Sign2', @:)
+ edit XsignOne
+ sign place 1 name=Sign1 line=5
+ sign place 1 name=Sign1 group=g1 line=10
+ edit XsignTwo
+ sign place 1 name=Sign2 group=g2 line=15
+
+ " Test for completion of group= and file= arguments to ':sign place'
+ call feedkeys(":sign place 1 name=Sign1 file=Xsign\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign place 1 name=Sign1 file=XsignOne XsignTwo', @:)
+ call feedkeys(":sign place 1 name=Sign1 group=\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign place 1 name=Sign1 group=g1 g2', @:)
+
+ " Test for completion of arguments to 'sign place' without sign identifier
+ call feedkeys(":sign place \<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign place buffer= file= group=', @:)
+ call feedkeys(":sign place file=Xsign\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign place file=XsignOne XsignTwo', @:)
+ call feedkeys(":sign place group=\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign place group=g1 g2', @:)
+ call feedkeys(":sign place group=g1 file=\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign place group=g1 file=XsignOne XsignTwo', @:)
+
+ " Test for completion of arguments to ':sign unplace'
call feedkeys(":sign unplace 1 \<C-A>\<C-B>\"\<CR>", 'tx')
call assert_equal('"sign unplace 1 buffer= file= group=', @:)
-
+ call feedkeys(":sign unplace 1 file=Xsign\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign unplace 1 file=XsignOne XsignTwo', @:)
+ call feedkeys(":sign unplace 1 group=\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign unplace 1 group=g1 g2', @:)
+ call feedkeys(":sign unplace 1 group=g2 file=Xsign\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign unplace 1 group=g2 file=XsignOne XsignTwo', @:)
+
+ " Test for completion of arguments to ':sign list'
call feedkeys(":sign list \<C-A>\<C-B>\"\<CR>", 'tx')
call assert_equal('"sign list Sign1 Sign2', @:)
+ " Test for completion of arguments to ':sign jump'
call feedkeys(":sign jump 1 \<C-A>\<C-B>\"\<CR>", 'tx')
call assert_equal('"sign jump 1 buffer= file= group=', @:)
+ call feedkeys(":sign jump 1 file=Xsign\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign jump 1 file=XsignOne XsignTwo', @:)
+ call feedkeys(":sign jump 1 group=\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign jump 1 group=g1 g2', @:)
+
+ " Error cases
+ call feedkeys(":sign here\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign here', @:)
+ call feedkeys(":sign define Sign here=\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"sign define Sign here=\<C-A>", @:)
+ call feedkeys(":sign place 1 here=\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"sign place 1 here=\<C-A>", @:)
+ call feedkeys(":sign jump 1 here=\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"sign jump 1 here=\<C-A>", @:)
+ call feedkeys(":sign here there\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"sign here there\<C-A>", @:)
+ call feedkeys(":sign here there=\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"sign here there=\<C-A>", @:)
+ sign unplace * group=*
sign undefine Sign1
sign undefine Sign2
+ enew
+ call delete('XsignOne')
+ call delete('XsignTwo')
endfunc
func Test_sign_invalid_commands()
@@ -1127,6 +1185,319 @@ func Test_sign_priority()
\ 'priority' : 10}],
\ s[0].signs)
+ call sign_unplace('*')
+
+ " Three signs on different lines with changing priorities
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 11, 'priority' : 50})
+ call sign_place(2, '', 'sign2', 'Xsign',
+ \ {'lnum' : 12, 'priority' : 60})
+ call sign_place(3, '', 'sign3', 'Xsign',
+ \ {'lnum' : 13, 'priority' : 70})
+ call sign_place(2, '', 'sign2', 'Xsign',
+ \ {'lnum' : 12, 'priority' : 40})
+ call sign_place(3, '', 'sign3', 'Xsign',
+ \ {'lnum' : 13, 'priority' : 30})
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 11, 'priority' : 50})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 11, 'group' : '',
+ \ 'priority' : 50},
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 12, 'group' : '',
+ \ 'priority' : 40},
+ \ {'id' : 3, 'name' : 'sign3', 'lnum' : 13, 'group' : '',
+ \ 'priority' : 30}],
+ \ s[0].signs)
+
+ call sign_unplace('*')
+
+ " Two signs on the same line with changing priorities
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 20})
+ call sign_place(2, '', 'sign2', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 30})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 30},
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20}],
+ \ s[0].signs)
+ " Change the priority of the last sign to highest
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 40})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 40},
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 30}],
+ \ s[0].signs)
+ " Change the priority of the first sign to lowest
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 25})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 30},
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 25}],
+ \ s[0].signs)
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 45})
+ call sign_place(2, '', 'sign2', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 55})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 55},
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 45}],
+ \ s[0].signs)
+
+ call sign_unplace('*')
+
+ " Three signs on the same line with changing priorities
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 40})
+ call sign_place(2, '', 'sign2', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 30})
+ call sign_place(3, '', 'sign3', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 20})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 40},
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 30},
+ \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20}],
+ \ s[0].signs)
+
+ " Change the priority of the middle sign to the highest
+ call sign_place(2, '', 'sign2', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 50})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 50},
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 40},
+ \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20}],
+ \ s[0].signs)
+
+ " Change the priority of the middle sign to the lowest
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 15})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 50},
+ \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20},
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 15}],
+ \ s[0].signs)
+
+ " Change the priority of the last sign to the highest
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 55})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 55},
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 50},
+ \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20}],
+ \ s[0].signs)
+
+ " Change the priority of the first sign to the lowest
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 15})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 50},
+ \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20},
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 15}],
+ \ s[0].signs)
+
+ call sign_unplace('*')
+
+ " Three signs on the same line with changing priorities along with other
+ " signs
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 2, 'priority' : 10})
+ call sign_place(2, '', 'sign1', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 30})
+ call sign_place(3, '', 'sign2', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 20})
+ call sign_place(4, '', 'sign3', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 25})
+ call sign_place(5, '', 'sign2', 'Xsign',
+ \ {'lnum' : 6, 'priority' : 80})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 2, 'group' : '',
+ \ 'priority' : 10},
+ \ {'id' : 2, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 30},
+ \ {'id' : 4, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 25},
+ \ {'id' : 3, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20},
+ \ {'id' : 5, 'name' : 'sign2', 'lnum' : 6, 'group' : '',
+ \ 'priority' : 80}],
+ \ s[0].signs)
+
+ " Change the priority of the first sign to lowest
+ call sign_place(2, '', 'sign1', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 15})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 2, 'group' : '',
+ \ 'priority' : 10},
+ \ {'id' : 4, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 25},
+ \ {'id' : 3, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20},
+ \ {'id' : 2, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 15},
+ \ {'id' : 5, 'name' : 'sign2', 'lnum' : 6, 'group' : '',
+ \ 'priority' : 80}],
+ \ s[0].signs)
+
+ " Change the priority of the last sign to highest
+ call sign_place(2, '', 'sign1', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 30})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 2, 'group' : '',
+ \ 'priority' : 10},
+ \ {'id' : 2, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 30},
+ \ {'id' : 4, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 25},
+ \ {'id' : 3, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20},
+ \ {'id' : 5, 'name' : 'sign2', 'lnum' : 6, 'group' : '',
+ \ 'priority' : 80}],
+ \ s[0].signs)
+
+ " Change the priority of the middle sign to lowest
+ call sign_place(4, '', 'sign3', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 15})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 2, 'group' : '',
+ \ 'priority' : 10},
+ \ {'id' : 2, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 30},
+ \ {'id' : 3, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20},
+ \ {'id' : 4, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 15},
+ \ {'id' : 5, 'name' : 'sign2', 'lnum' : 6, 'group' : '',
+ \ 'priority' : 80}],
+ \ s[0].signs)
+
+ " Change the priority of the middle sign to highest
+ call sign_place(3, '', 'sign2', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 35})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 2, 'group' : '',
+ \ 'priority' : 10},
+ \ {'id' : 3, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 35},
+ \ {'id' : 2, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 30},
+ \ {'id' : 4, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 15},
+ \ {'id' : 5, 'name' : 'sign2', 'lnum' : 6, 'group' : '',
+ \ 'priority' : 80}],
+ \ s[0].signs)
+
+ call sign_unplace('*')
+
+ " Multiple signs with the same priority on the same line
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 20})
+ call sign_place(2, '', 'sign2', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 20})
+ call sign_place(3, '', 'sign3', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 20})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20},
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20},
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20}],
+ \ s[0].signs)
+ " Place the last sign again with the same priority
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 20})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20},
+ \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20},
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20}],
+ \ s[0].signs)
+ " Place the first sign again with the same priority
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 20})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20},
+ \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20},
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20}],
+ \ s[0].signs)
+ " Place the middle sign again with the same priority
+ call sign_place(3, '', 'sign3', 'Xsign',
+ \ {'lnum' : 4, 'priority' : 20})
+ let s = sign_getplaced('Xsign', {'group' : '*'})
+ call assert_equal([
+ \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20},
+ \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20},
+ \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
+ \ 'priority' : 20}],
+ \ s[0].signs)
+
+ call sign_unplace('*')
+
+ " Place multiple signs with same id on a line with different priority
+ call sign_place(1, '', 'sign1', 'Xsign',
+ \ {'lnum' : 5, 'priority' : 20})
+ call sign_place(1, '', 'sign2', 'Xsign',
+ \ {'lnum' : 5, 'priority' : 10})
+ let s = sign_getplaced('Xsign', {'lnum' : 5})
+ call assert_equal([
+ \ {'id' : 1, 'name' : 'sign2', 'lnum' : 5, 'group' : '',
+ \ 'priority' : 10}],
+ \ s[0].signs)
+ call sign_place(1, '', 'sign2', 'Xsign',
+ \ {'lnum' : 5, 'priority' : 5})
+ let s = sign_getplaced('Xsign', {'lnum' : 5})
+ call assert_equal([
+ \ {'id' : 1, 'name' : 'sign2', 'lnum' : 5, 'group' : '',
+ \ 'priority' : 5}],
+ \ s[0].signs)
+
" Error case
call assert_fails("call sign_place(1, 'g1', 'sign1', 'Xsign',
\ [])", 'E715:')
@@ -1339,3 +1710,35 @@ func Test_sign_jump_func()
sign undefine sign1
enew! | only!
endfunc
+
+" Test for correct cursor position after the sign column appears or disappears.
+func Test_sign_cursor_position()
+ if !CanRunVimInTerminal()
+ throw 'Skipped: cannot make screendumps'
+ endif
+
+ let lines =<< trim END
+ call setline(1, [repeat('x', 75), 'mmmm', 'yyyy'])
+ call cursor(2,1)
+ sign define s1 texthl=Search text==>
+ redraw
+ sign place 10 line=2 name=s1
+ END
+ call writefile(lines, 'XtestSigncolumn')
+ let buf = RunVimInTerminal('-S XtestSigncolumn', {'rows': 6})
+ call VerifyScreenDump(buf, 'Test_sign_cursor_1', {})
+
+ " Change the sign text
+ call term_sendkeys(buf, ":sign define s1 text=-)\<CR>")
+ call VerifyScreenDump(buf, 'Test_sign_cursor_2', {})
+
+ " update cursor position calculation
+ call term_sendkeys(buf, "lh")
+ call term_sendkeys(buf, ":sign unplace 10\<CR>")
+ call VerifyScreenDump(buf, 'Test_sign_cursor_3', {})
+
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('XtestSigncolumn')
+endfunc
diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim
index e2016d7927..e5eaa01e92 100644
--- a/src/nvim/testdir/test_spell.vim
+++ b/src/nvim/testdir/test_spell.vim
@@ -130,20 +130,21 @@ endfunc
func Test_spellinfo()
throw 'skipped: Nvim does not support enc=latin1'
new
+ let runtime = substitute($VIMRUNTIME, '\\', '/', 'g')
set enc=latin1 spell spelllang=en
- call assert_match("^\nfile: .*/runtime/spell/en.latin1.spl\n$", execute('spellinfo'))
+ call assert_match("^\nfile: " .. runtime .. "/spell/en.latin1.spl\n$", execute('spellinfo'))
set enc=cp1250 spell spelllang=en
- call assert_match("^\nfile: .*/runtime/spell/en.ascii.spl\n$", execute('spellinfo'))
+ call assert_match("^\nfile: " .. runtime .. "/spell/en.ascii.spl\n$", execute('spellinfo'))
set enc=utf-8 spell spelllang=en
- call assert_match("^\nfile: .*/runtime/spell/en.utf-8.spl\n$", execute('spellinfo'))
+ call assert_match("^\nfile: " .. runtime .. "/spell/en.utf-8.spl\n$", execute('spellinfo'))
set enc=latin1 spell spelllang=en_us,en_nz
call assert_match("^\n" .
- \ "file: .*/runtime/spell/en.latin1.spl\n" .
- \ "file: .*/runtime/spell/en.latin1.spl\n$", execute('spellinfo'))
+ \ "file: " .. runtime .. "/spell/en.latin1.spl\n" .
+ \ "file: " .. runtime .. "/spell/en.latin1.spl\n$", execute('spellinfo'))
set spell spelllang=
call assert_fails('spellinfo', 'E756:')
@@ -151,6 +152,12 @@ func Test_spellinfo()
set nospell spelllang=en
call assert_fails('spellinfo', 'E756:')
+ call assert_fails('set spelllang=foo/bar', 'E474:')
+ call assert_fails('set spelllang=foo\ bar', 'E474:')
+ call assert_fails("set spelllang=foo\\\nbar", 'E474:')
+ call assert_fails("set spelllang=foo\\\rbar", 'E474:')
+ call assert_fails("set spelllang=foo+bar", 'E474:')
+
set enc& spell& spelllang&
bwipe
endfunc
@@ -386,6 +393,11 @@ func Test_zz_sal_and_addition()
call assert_equal("elekwint", SecondSpellWord())
endfunc
+func Test_spellfile_value()
+ set spellfile=Xdir/Xtest.latin1.add
+ set spellfile=Xdir/Xtest.utf-8.add,Xtest_other.add
+endfunc
+
func Test_region_error()
messages clear
call writefile(["/regions=usgbnz", "elequint/0"], "Xtest.latin1.add")
diff --git a/src/nvim/testdir/test_startup.vim b/src/nvim/testdir/test_startup.vim
index 15b55d35c1..f03c493275 100644
--- a/src/nvim/testdir/test_startup.vim
+++ b/src/nvim/testdir/test_startup.vim
@@ -271,7 +271,7 @@ func Test_V_arg()
call assert_equal(" verbose=0\n", out)
let out = system(GetVimCommand() . ' --clean -es -X -V2 -c "set verbose?" -cq')
- " call assert_match("sourcing \"$VIMRUNTIME[\\/]defaults\.vim\"\r\nSearching for \"filetype\.vim\".*\n", out)
+ " call assert_match("sourcing \"$VIMRUNTIME[\\/]defaults\.vim\"\r\nline \\d\\+: sourcing \"[^\"]*runtime[\\/]filetype\.vim\".*\n", out)
call assert_match(" verbose=2\n", out)
let out = system(GetVimCommand() . ' --clean -es -X -V15 -c "set verbose?" -cq')
diff --git a/src/nvim/testdir/test_startup_utf8.vim b/src/nvim/testdir/test_startup_utf8.vim
index b24b0eb5cf..1b3d2184a0 100644
--- a/src/nvim/testdir/test_startup_utf8.vim
+++ b/src/nvim/testdir/test_startup_utf8.vim
@@ -1,7 +1,7 @@
" Tests for startup using utf-8.
source shared.vim
-" source screendump.vim
+source screendump.vim
func Test_read_stdin_utf8()
let linesin = ['テスト', '€ÀÈÌÒÙ']
diff --git a/src/nvim/testdir/test_statusline.vim b/src/nvim/testdir/test_statusline.vim
index 48ec777ffd..66b6e6c05c 100644
--- a/src/nvim/testdir/test_statusline.vim
+++ b/src/nvim/testdir/test_statusline.vim
@@ -369,3 +369,24 @@ func Test_statusline_visual()
bwipe! x1
bwipe! x2
endfunc
+
+func Test_statusline_removed_group()
+ if !CanRunVimInTerminal()
+ throw 'Skipped: cannot make screendumps'
+ endif
+
+ let lines =<< trim END
+ scriptencoding utf-8
+ set laststatus=2
+ let &statusline = '%#StatColorHi2#%(✓%#StatColorHi2#%) Q≡'
+ END
+ call writefile(lines, 'XTest_statusline')
+
+ let buf = RunVimInTerminal('-S XTest_statusline', {'rows': 10, 'cols': 50})
+ call term_wait(buf, 100)
+ call VerifyScreenDump(buf, 'Test_statusline_1', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('XTest_statusline')
+endfunc
diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim
index e94bd22cea..ff07d8eceb 100644
--- a/src/nvim/testdir/test_substitute.vim
+++ b/src/nvim/testdir/test_substitute.vim
@@ -613,6 +613,25 @@ func Test_sub_replace_10()
call assert_equal('1aaa', substitute('123', '1\zs\|[23]', 'a', 'g'))
endfunc
+func SubReplacer(text, submatches)
+ return a:text .. a:submatches[0] .. a:text
+endfunc
+func SubReplacer20(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16, t17, t18, t19, submatches)
+ return a:t3 .. a:submatches[0] .. a:t11
+endfunc
+
+func Test_substitute_partial()
+ call assert_equal('1foo2foo3', substitute('123', '2', function('SubReplacer', ['foo']), 'g'))
+
+ " 19 arguments plus one is just OK
+ let Replacer = function('SubReplacer20', repeat(['foo'], 19))
+ call assert_equal('1foo2foo3', substitute('123', '2', Replacer, 'g'))
+
+ " 20 arguments plus one is too many
+ let Replacer = function('SubReplacer20', repeat(['foo'], 20))
+ call assert_fails("call substitute('123', '2', Replacer, 'g')", 'E118')
+endfunc
+
func Test_sub_cmd_9()
new
let input = ['1 aaa', '2 aaa', '3 aaa']
diff --git a/src/nvim/testdir/test_suspend.vim b/src/nvim/testdir/test_suspend.vim
index ef5a96bd72..4b3bd5eadf 100644
--- a/src/nvim/testdir/test_suspend.vim
+++ b/src/nvim/testdir/test_suspend.vim
@@ -1,6 +1,7 @@
" Test :suspend
source shared.vim
+source term_util.vim
func CheckSuspended(buf, fileExists)
call WaitForAssert({-> assert_match('[$#] $', term_getline(a:buf, '.'))})
@@ -55,7 +56,7 @@ func Test_suspend()
call term_wait(buf)
" Wait until Vim actually exited and shell shows a prompt
call WaitForAssert({-> assert_match('[$#] $', term_getline(buf, '.'))})
- call Stop_shell_in_terminal(buf)
+ call StopShellInTerminal(buf)
exe buf . 'bwipe!'
call delete('Xfoo')
diff --git a/src/nvim/testdir/test_tabpage.vim b/src/nvim/testdir/test_tabpage.vim
index ce9f7adfef..55dff3d476 100644
--- a/src/nvim/testdir/test_tabpage.vim
+++ b/src/nvim/testdir/test_tabpage.vim
@@ -1,6 +1,6 @@
" Tests for tabpage
-" source screendump.vim
+source screendump.vim
function Test_tabpage()
bw!
diff --git a/src/nvim/testdir/test_tagjump.vim b/src/nvim/testdir/test_tagjump.vim
index fe98ef1ae2..5fd71d8bfc 100644
--- a/src/nvim/testdir/test_tagjump.vim
+++ b/src/nvim/testdir/test_tagjump.vim
@@ -340,6 +340,28 @@ func Test_getsettagstack()
\ {'items' : [{'tagname' : 'abc', 'from' : [1, 10, 1, 0]}]}, 'a')
call assert_equal('abc', gettagstack().items[19].tagname)
+ " truncate the tag stack
+ call settagstack(1,
+ \ {'curidx' : 9,
+ \ 'items' : [{'tagname' : 'abc', 'from' : [1, 10, 1, 0]}]}, 't')
+ let t = gettagstack()
+ call assert_equal(9, t.length)
+ call assert_equal(10, t.curidx)
+
+ " truncate the tag stack without pushing any new items
+ call settagstack(1, {'curidx' : 5}, 't')
+ let t = gettagstack()
+ call assert_equal(4, t.length)
+ call assert_equal(5, t.curidx)
+
+ " truncate an empty tag stack and push new items
+ call settagstack(1, {'items' : []})
+ call settagstack(1,
+ \ {'items' : [{'tagname' : 'abc', 'from' : [1, 10, 1, 0]}]}, 't')
+ let t = gettagstack()
+ call assert_equal(1, t.length)
+ call assert_equal(2, t.curidx)
+
" Tag with multiple matches
call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
\ "two\tXfile1\t1",
diff --git a/src/nvim/testdir/test_taglist.vim b/src/nvim/testdir/test_taglist.vim
index cb54ace695..d4ff42fd68 100644
--- a/src/nvim/testdir/test_taglist.vim
+++ b/src/nvim/testdir/test_taglist.vim
@@ -7,6 +7,7 @@ func Test_taglist()
\ "BFoo\tXbar\t1",
\ "BBar\tXbar\t2",
\ "Kindly\tXbar\t3;\"\tv\tfile:",
+ \ "Lambda\tXbar\t3;\"\tλ\tfile:",
\ "Command\tXbar\tcall cursor(3, 4)|;\"\td",
\ ], 'Xtags')
set tags=Xtags
@@ -17,12 +18,16 @@ func Test_taglist()
call assert_equal(['FFoo', 'BFoo'], map(taglist("Foo", "Xfoo"), {i, v -> v.name}))
call assert_equal(['BFoo', 'FFoo'], map(taglist("Foo", "Xbar"), {i, v -> v.name}))
- let kind = taglist("Kindly")
- call assert_equal(1, len(kind))
- call assert_equal('v', kind[0]['kind'])
- call assert_equal('3', kind[0]['cmd'])
- call assert_equal(1, kind[0]['static'])
- call assert_equal('Xbar', kind[0]['filename'])
+ let kindly = taglist("Kindly")
+ call assert_equal(1, len(kindly))
+ call assert_equal('v', kindly[0]['kind'])
+ call assert_equal('3', kindly[0]['cmd'])
+ call assert_equal(1, kindly[0]['static'])
+ call assert_equal('Xbar', kindly[0]['filename'])
+
+ let lambda = taglist("Lambda")
+ call assert_equal(1, len(lambda))
+ call assert_equal('λ', lambda[0]['kind'])
let cmd = taglist("Command")
call assert_equal(1, len(cmd))
diff --git a/src/nvim/testdir/test_textformat.vim b/src/nvim/testdir/test_textformat.vim
index 13fb50b985..75673adf0a 100644
--- a/src/nvim/testdir/test_textformat.vim
+++ b/src/nvim/testdir/test_textformat.vim
@@ -489,3 +489,426 @@ func Test_format_list_auto()
bwipe!
set fo& ai& bs&
endfunc
+
+" Test for formatting multi-byte text with 'fo=t'
+func Test_tw_2_fo_t()
+ new
+ let t =<< trim END
+ {
+ XYZ
+ abc XYZ
+ }
+ END
+ call setline(1, t)
+ call cursor(2, 1)
+
+ set tw=2 fo=t
+ let t =<< trim END
+ XYZ
+ abc XYZ
+ END
+ exe "normal gqgqjgqgq"
+ exe "normal o\n" . join(t, "\n")
+
+ let expected =<< trim END
+ {
+ XYZ
+ abc
+ XYZ
+
+ XYZ
+ abc
+ XYZ
+ }
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ set tw& fo&
+ bwipe!
+endfunc
+
+" Test for formatting multi-byte text with 'fo=tm' and 'tw=1'
+func Test_tw_1_fo_tm()
+ new
+ let t =<< trim END
+ {
+ X
+ Xa
+ X a
+ XY
+ X Y
+ }
+ END
+ call setline(1, t)
+ call cursor(2, 1)
+
+ set tw=1 fo=tm
+ let t =<< trim END
+ X
+ Xa
+ X a
+ XY
+ X Y
+ END
+ exe "normal gqgqjgqgqjgqgqjgqgqjgqgq"
+ exe "normal o\n" . join(t, "\n")
+
+ let expected =<< trim END
+ {
+ X
+ X
+ a
+ X
+ a
+ X
+ Y
+ X
+ Y
+
+ X
+ X
+ a
+ X
+ a
+ X
+ Y
+ X
+ Y
+ }
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ set tw& fo&
+ bwipe!
+endfunc
+
+" Test for formatting multi-byte text with 'fo=tm' and 'tw=2'
+func Test_tw_2_fo_tm()
+ new
+ let t =<< trim END
+ {
+ X
+ Xa
+ X a
+ XY
+ X Y
+ aX
+ abX
+ abcX
+ abX c
+ abXY
+ }
+ END
+ call setline(1, t)
+ call cursor(2, 1)
+
+ set tw=2 fo=tm
+ let t =<< trim END
+ X
+ Xa
+ X a
+ XY
+ X Y
+ aX
+ abX
+ abcX
+ abX c
+ abXY
+ END
+ exe "normal gqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgq"
+ exe "normal o\n" . join(t, "\n")
+
+ let expected =<< trim END
+ {
+ X
+ X
+ a
+ X
+ a
+ X
+ Y
+ X
+ Y
+ a
+ X
+ ab
+ X
+ abc
+ X
+ ab
+ X
+ c
+ ab
+ X
+ Y
+
+ X
+ X
+ a
+ X
+ a
+ X
+ Y
+ X
+ Y
+ a
+ X
+ ab
+ X
+ abc
+ X
+ ab
+ X
+ c
+ ab
+ X
+ Y
+ }
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ set tw& fo&
+ bwipe!
+endfunc
+
+" Test for formatting multi-byte text with 'fo=tm', 'tw=2' and 'autoindent'.
+func Test_tw_2_fo_tm_ai()
+ new
+ let t =<< trim END
+ {
+ X
+ Xa
+ }
+ END
+ call setline(1, t)
+ call cursor(2, 1)
+
+ set ai tw=2 fo=tm
+ let t =<< trim END
+ X
+ Xa
+ END
+ exe "normal gqgqjgqgq"
+ exe "normal o\n" . join(t, "\n")
+
+ let expected =<< trim END
+ {
+ X
+ X
+ a
+
+ X
+ X
+ a
+ }
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ set tw& fo& ai&
+ bwipe!
+endfunc
+
+" Test for formatting multi-byte text with 'fo=tm', 'tw=2' and 'noai'.
+func Test_tw_2_fo_tm_noai()
+ new
+ let t =<< trim END
+ {
+ X
+ Xa
+ }
+ END
+ call setline(1, t)
+ call cursor(2, 1)
+
+ set noai tw=2 fo=tm
+ exe "normal gqgqjgqgqo\n X\n Xa"
+
+ let expected =<< trim END
+ {
+ X
+ X
+ a
+
+ X
+ X
+ a
+ }
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ set tw& fo& ai&
+ bwipe!
+endfunc
+
+func Test_tw_2_fo_cqm_com()
+ new
+ let t =<< trim END
+ {
+ X
+ Xa
+ XaY
+ XY
+ XYZ
+ X Y
+ X YZ
+ XX
+ XXa
+ XXY
+ }
+ END
+ call setline(1, t)
+ call cursor(2, 1)
+
+ set tw=2 fo=cqm comments=n:X
+ exe "normal gqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgq"
+ let t =<< trim END
+ X
+ Xa
+ XaY
+ XY
+ XYZ
+ X Y
+ X YZ
+ XX
+ XXa
+ XXY
+ END
+ exe "normal o\n" . join(t, "\n")
+
+ let expected =<< trim END
+ {
+ X
+ Xa
+ Xa
+ XY
+ XY
+ XY
+ XZ
+ X Y
+ X Y
+ X Z
+ XX
+ XXa
+ XXY
+
+ X
+ Xa
+ Xa
+ XY
+ XY
+ XY
+ XZ
+ X Y
+ X Y
+ X Z
+ XX
+ XXa
+ XXY
+ }
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ set tw& fo& comments&
+ bwipe!
+endfunc
+
+func Test_tw_2_fo_tm_replace()
+ new
+ let t =<< trim END
+ {
+
+ }
+ END
+ call setline(1, t)
+ call cursor(2, 1)
+
+ set tw=2 fo=tm
+ exe "normal RXa"
+
+ let expected =<< trim END
+ {
+ X
+ a
+ }
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ set tw& fo&
+ bwipe!
+endfunc
+
+" Test for 'matchpairs' with multibyte chars
+func Test_mps()
+ new
+ let t =<< trim END
+ {
+ ‘ two three ’ four
+ }
+ END
+ call setline(1, t)
+ call cursor(2, 1)
+
+ exe "set mps+=\u2018:\u2019"
+ normal d%
+
+ let expected =<< trim END
+ {
+ four
+ }
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ set mps&
+ bwipe!
+endfunc
+
+" Test for ra on multi-byte characters
+func Test_ra_multibyte()
+ new
+ let t =<< trim END
+ ra test
+ abba
+ aab
+ END
+ call setline(1, t)
+ call cursor(1, 1)
+
+ normal jVjra
+
+ let expected =<< trim END
+ ra test
+ aaaa
+ aaa
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+endfunc
+
+" Test for 'whichwrap' with multi-byte character
+func Test_whichwrap_multi_byte()
+ new
+ let t =<< trim END
+ á
+ x
+ END
+ call setline(1, t)
+ call cursor(2, 1)
+
+ set whichwrap+=h
+ normal dh
+ set whichwrap&
+
+ let expected =<< trim END
+ áx
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+endfunc
+
+func Test_substitute()
+ call assert_equal('a1a2a3a', substitute('123', '\zs', 'a', 'g'))
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim
index 3043103270..cffd80ff4f 100644
--- a/src/nvim/testdir/test_timers.vim
+++ b/src/nvim/testdir/test_timers.vim
@@ -5,7 +5,7 @@ if !has('timers')
endif
source shared.vim
-source screendump.vim
+source term_util.vim
source load.vim
func MyHandler(timer)
@@ -339,4 +339,8 @@ func Test_nocatch_garbage_collect()
delfunc FeedChar
endfunc
+func Test_timer_invalid_callback()
+ call assert_fails('call timer_start(0, "0")', 'E921')
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_user_func.vim b/src/nvim/testdir/test_user_func.vim
index e7a3701386..67701ee3ca 100644
--- a/src/nvim/testdir/test_user_func.vim
+++ b/src/nvim/testdir/test_user_func.vim
@@ -94,3 +94,7 @@ func Test_user_func()
unlet g:retval g:counter
enew!
endfunc
+
+func Test_failed_call_in_try()
+ try | call UnknownFunc() | catch | endtry
+endfunc
diff --git a/src/nvim/testdir/test_utf8.vim b/src/nvim/testdir/test_utf8.vim
index b1f33f56dd..1b4ce4c4af 100644
--- a/src/nvim/testdir/test_utf8.vim
+++ b/src/nvim/testdir/test_utf8.vim
@@ -60,3 +60,46 @@ func Test_getvcol()
call assert_equal(2, virtcol("'["))
call assert_equal(2, virtcol("']"))
endfunc
+
+func Test_list2str_str2list_utf8()
+ " One Unicode codepoint
+ let s = "\u3042\u3044"
+ let l = [0x3042, 0x3044]
+ call assert_equal(l, str2list(s, 1))
+ call assert_equal(s, list2str(l, 1))
+ if &enc ==# 'utf-8'
+ call assert_equal(str2list(s), str2list(s, 1))
+ call assert_equal(list2str(l), list2str(l, 1))
+ endif
+
+ " With composing characters
+ let s = "\u304b\u3099\u3044"
+ let l = [0x304b, 0x3099, 0x3044]
+ call assert_equal(l, str2list(s, 1))
+ call assert_equal(s, list2str(l, 1))
+ if &enc ==# 'utf-8'
+ call assert_equal(str2list(s), str2list(s, 1))
+ call assert_equal(list2str(l), list2str(l, 1))
+ endif
+
+ " Null list is the same as an empty list
+ call assert_equal('', list2str([]))
+ " call assert_equal('', list2str(test_null_list()))
+endfunc
+
+func Test_list2str_str2list_latin1()
+ " When 'encoding' is not multi-byte can still get utf-8 string.
+ " But we need to create the utf-8 string while 'encoding' is utf-8.
+ let s = "\u3042\u3044"
+ let l = [0x3042, 0x3044]
+
+ let save_encoding = &encoding
+ " set encoding=latin1
+
+ let lres = str2list(s, 1)
+ let sres = list2str(l, 1)
+
+ let &encoding = save_encoding
+ call assert_equal(l, lres)
+ call assert_equal(s, sres)
+endfunc
diff --git a/src/nvim/testdir/test_writefile.vim b/src/nvim/testdir/test_writefile.vim
index 6066d61af4..56031662a3 100644
--- a/src/nvim/testdir/test_writefile.vim
+++ b/src/nvim/testdir/test_writefile.vim
@@ -1,4 +1,4 @@
-" Tests for the writefile() function.
+" Tests for the writefile() function and some :write commands.
func Test_writefile()
let f = tempname()
@@ -16,6 +16,11 @@ func Test_writefile()
call delete(f)
endfunc
+func Test_writefile_ignore_regexp_error()
+ write Xt[z-a]est.txt
+ call delete('Xt[z-a]est.txt')
+endfunc
+
func Test_writefile_fails_gently()
call assert_fails('call writefile(["test"], "Xfile", [])', 'E730:')
call assert_false(filereadable("Xfile"))
diff --git a/src/nvim/testdir/view_util.vim b/src/nvim/testdir/view_util.vim
index 520f65c1e7..1def201a05 100644
--- a/src/nvim/testdir/view_util.vim
+++ b/src/nvim/testdir/view_util.vim
@@ -1,10 +1,21 @@
" Functions about view shared by several tests
" Only load this script once.
-if exists('*ScreenLines')
+if exists('*Screenline')
finish
endif
+" Get line "lnum" as displayed on the screen.
+" Trailing white space is trimmed.
+func Screenline(lnum)
+ let chars = []
+ for c in range(1, winwidth(0))
+ call add(chars, nr2char(screenchar(a:lnum, c)))
+ endfor
+ let line = join(chars, '')
+ return matchstr(line, '^.\{-}\ze\s*$')
+endfunc
+
" ScreenLines(lnum, width) or
" ScreenLines([start, end], width)
function! ScreenLines(lnum, width) abort
diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c
index 951cb50c3c..bbee7e4712 100644
--- a/src/nvim/tui/input.c
+++ b/src/nvim/tui/input.c
@@ -31,6 +31,10 @@ void tinput_init(TermInput *input, Loop *loop)
input->paste = 0;
input->in_fd = STDIN_FILENO;
input->waiting_for_bg_response = 0;
+ // The main thread is waiting for the UI thread to call CONTINUE, so it can
+ // safely access global variables.
+ input->ttimeout = (bool)p_ttimeout;
+ input->ttimeoutlen = p_ttm;
input->key_buffer = rbuffer_new(KEY_BUFFER_SIZE);
uv_mutex_init(&input->key_buffer_mutex);
uv_cond_init(&input->key_buffer_cond);
@@ -285,21 +289,6 @@ static TermKeyResult tk_getkey(TermKey *tk, TermKeyKey *key, bool force)
static void tinput_timer_cb(TimeWatcher *watcher, void *data);
-static int get_key_code_timeout(void)
-{
- Integer ms = -1;
- // Check 'ttimeout' to determine if we should send ESC after 'ttimeoutlen'.
- Error err = ERROR_INIT;
- if (nvim_get_option(cstr_as_string("ttimeout"), &err).data.boolean) {
- Object rv = nvim_get_option(cstr_as_string("ttimeoutlen"), &err);
- if (!ERROR_SET(&err)) {
- ms = rv.data.integer;
- }
- }
- api_clear_error(&err);
- return (int)ms;
-}
-
static void tk_getkeys(TermInput *input, bool force)
{
TermKeyKey key;
@@ -324,12 +313,11 @@ static void tk_getkeys(TermInput *input, bool force)
// yet contain all the bytes required. `key` structure indicates what
// termkey_getkey_force() would return.
- int ms = get_key_code_timeout();
-
- if (ms > 0) {
+ if (input->ttimeout && input->ttimeoutlen >= 0) {
// Stop the current timer if already running
time_watcher_stop(&input->timer_handle);
- time_watcher_start(&input->timer_handle, tinput_timer_cb, (uint32_t)ms, 0);
+ time_watcher_start(&input->timer_handle, tinput_timer_cb,
+ (uint64_t)input->ttimeoutlen, 0);
} else {
tk_getkeys(input, true);
}
diff --git a/src/nvim/tui/input.h b/src/nvim/tui/input.h
index 77bd6fa132..b30546c815 100644
--- a/src/nvim/tui/input.h
+++ b/src/nvim/tui/input.h
@@ -12,7 +12,9 @@ typedef struct term_input {
// Phases: -1=all 0=disabled 1=first-chunk 2=continue 3=last-chunk
int8_t paste;
bool waiting;
+ bool ttimeout;
int8_t waiting_for_bg_response;
+ long ttimeoutlen;
TermKey *tk;
#if TERMKEY_VERSION_MAJOR > 0 || TERMKEY_VERSION_MINOR > 18
TermKey_Terminfo_Getstr_Hook *tk_ti_hook_fn; ///< libtermkey terminfo hook
diff --git a/src/nvim/tui/terminfo.c b/src/nvim/tui/terminfo.c
index 03173afe07..ff2a357752 100644
--- a/src/nvim/tui/terminfo.c
+++ b/src/nvim/tui/terminfo.c
@@ -31,7 +31,10 @@ bool terminfo_is_term_family(const char *term, const char *family)
return tlen >= flen
&& 0 == memcmp(term, family, flen)
// Per commentary in terminfo, minus is the only valid suffix separator.
- && ('\0' == term[flen] || '-' == term[flen]);
+ // The screen terminfo may have a terminal name like screen.xterm. By making
+ // the dot(.) a valid separator, such terminal names will also be the
+ // terminal family of the screen.
+ && ('\0' == term[flen] || '-' == term[flen] || '.' == term[flen]);
}
bool terminfo_is_bsd_console(const char *term)
@@ -187,7 +190,7 @@ void terminfo_info_msg(const unibi_term *const ut)
msg_printf_attr(0, " %-25s %-10s = ", unibi_name_str(i),
unibi_short_name_str(i));
// Most of these strings will contain escape sequences.
- msg_outtrans_special((char_u *)s, false);
+ msg_outtrans_special((char_u *)s, false, 0);
msg_putchar('\n');
}
}
@@ -214,7 +217,7 @@ void terminfo_info_msg(const unibi_term *const ut)
msg_puts("Extended string capabilities:\n");
for (size_t i = 0; i < unibi_count_ext_str(ut); i++) {
msg_printf_attr(0, " %-25s = ", unibi_get_ext_str_name(ut, i));
- msg_outtrans_special((char_u *)unibi_get_ext_str(ut, i), false);
+ msg_outtrans_special((char_u *)unibi_get_ext_str(ut, i), false, 0);
msg_putchar('\n');
}
}
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index e9276db484..2c4d02812b 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -31,6 +31,7 @@
#include "nvim/event/signal.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
+#include "nvim/os/signal.h"
#include "nvim/os/tty.h"
#include "nvim/strings.h"
#include "nvim/syntax.h"
@@ -48,10 +49,15 @@
#define OUTBUF_SIZE 0xffff
#define TOO_MANY_EVENTS 1000000
-#define STARTS_WITH(str, prefix) (strlen(str) >= (sizeof(prefix) - 1) \
- && 0 == memcmp((str), (prefix), sizeof(prefix) - 1))
-#define TMUX_WRAP(is_tmux, seq) ((is_tmux) \
- ? "\x1bPtmux;\x1b" seq "\x1b\\" : seq)
+#define STARTS_WITH(str, prefix) \
+ (strlen(str) >= (sizeof(prefix) - 1) && 0 == memcmp((str), (prefix), \
+ sizeof(prefix) - 1))
+#define SCREEN_WRAP(is_screen, seq) ((is_screen) \
+ ? DCS_STR seq STERM_STR : seq)
+#define SCREEN_TMUX_WRAP(is_screen, is_tmux, seq) \
+ ((is_screen) \
+ ? DCS_STR seq STERM_STR : (is_tmux) \
+ ? DCS_STR "tmux;\x1b" seq STERM_STR : seq)
#define LINUXSET0C "\x1b[?0c"
#define LINUXSET1C "\x1b[?1c"
@@ -1099,6 +1105,7 @@ static void tui_grid_scroll(UI *ui, Integer g, Integer startrow, Integer endrow,
set_scroll_region(ui, top, bot, left, right);
}
cursor_goto(ui, top, left);
+ update_attrs(ui, 0);
if (rows > 0) {
if (rows == 1) {
@@ -1238,7 +1245,9 @@ static void suspend_event(void **argv)
tui_terminal_stop(ui);
data->cont_received = false;
stream_set_blocking(input_global_fd(), true); // normalize stream (#2598)
+ signal_stop();
kill(0, SIGTSTP);
+ signal_start();
while (!data->cont_received) {
// poll the event loop until SIGCONT is received
loop_poll_events(data->loop, -1);
@@ -1291,6 +1300,12 @@ 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")) {
+ data->input.ttimeout = value.data.boolean;
+ }
+ if (strequal(name.data, "ttimeoutlen")) {
+ data->input.ttimeoutlen = (long)value.data.integer;
+ }
}
static void tui_raw_line(UI *ui, Integer g, Integer linerow, Integer startcol,
@@ -1545,6 +1560,10 @@ static void patch_terminfo_bugs(TUIData *data, const char *term,
bool mate_pretending_xterm = xterm && colorterm
&& strstr(colorterm, "mate-terminal");
bool true_xterm = xterm && !!xterm_version && !bsdvt;
+ bool true_screen = screen && !os_getenv("TMUX");
+ bool screen_host_linuxvt =
+ terminfo_is_term_family(true_screen && term[6] == '.'
+ ? term + 7 : NULL, "linux");
bool cygwin = terminfo_is_term_family(term, "cygwin");
char *fix_normal = (char *)unibi_get_str(ut, unibi_cursor_normal);
@@ -1630,6 +1649,11 @@ static void patch_terminfo_bugs(TUIData *data, const char *term,
// per the screen manual; 2017-04 terminfo.src lacks these.
unibi_set_if_empty(ut, unibi_to_status_line, "\x1b_");
unibi_set_if_empty(ut, unibi_from_status_line, "\x1b\\");
+ // Fix an issue where smglr is inherited by TERM=screen.xterm.
+ if (unibi_get_str(ut, unibi_set_lr_margin)) {
+ ILOG("Disabling smglr with TERM=screen.xterm for screen.");
+ unibi_set_str(ut, unibi_set_lr_margin, NULL);
+ }
} else if (tmux) {
unibi_set_if_empty(ut, unibi_to_status_line, "\x1b_");
unibi_set_if_empty(ut, unibi_from_status_line, "\x1b\\");
@@ -1682,8 +1706,10 @@ static void patch_terminfo_bugs(TUIData *data, const char *term,
#define XTERM_SETAB_16 \
"\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e39%;m"
- data->unibi_ext.get_bg = (int)unibi_add_ext_str(ut, "ext.get_bg",
- "\x1b]11;?\x07");
+ data->unibi_ext.get_bg =
+ (int)unibi_add_ext_str(ut, "ext.get_bg",
+ SCREEN_TMUX_WRAP(true_screen,
+ tmux, "\x1b]11;?\x07"));
// Terminals with 256-colour SGR support despite what terminfo says.
if (unibi_get_num(ut, unibi_max_colors) < 256) {
@@ -1718,6 +1744,32 @@ static void patch_terminfo_bugs(TUIData *data, const char *term,
data->unibi_ext.set_cursor_style = unibi_find_ext_str(ut, "Ss");
}
+ // GNU Screen does not have Ss/Se. When terminfo has Ss/Se, it is wrapped with
+ // DCS because it is inherited from the host terminal.
+ if (true_screen) {
+ size_t len;
+ size_t dcs_st_len = strlen(DCS_STR) + strlen(STERM_STR);
+ if (-1 != data->unibi_ext.set_cursor_style) {
+ const char *orig_ss =
+ unibi_get_ext_str(data->ut, (size_t)data->unibi_ext.reset_cursor_style);
+ len = STRLEN(orig_ss) + dcs_st_len + 1;
+ char *ss = xmalloc(len);
+ snprintf(ss, len, "%s%s%s", DCS_STR, orig_ss, STERM_STR);
+ unibi_set_ext_str(data->ut, (size_t)data->unibi_ext.set_cursor_style, ss);
+ xfree(ss);
+ }
+ if (-1 != data->unibi_ext.reset_cursor_style) {
+ const char *orig_se =
+ unibi_get_ext_str(data->ut, (size_t)data->unibi_ext.reset_cursor_style);
+ len = strlen(orig_se) + dcs_st_len + 1;
+ char *se = xmalloc(len);
+ snprintf(se, len, "%s%s%s", DCS_STR, orig_se, STERM_STR);
+ unibi_set_ext_str(data->ut,
+ (size_t)data->unibi_ext.reset_cursor_style, se);
+ xfree(se);
+ }
+ }
+
// Dickey ncurses terminfo includes Ss/Se capabilities since 2011-07-14. So
// adding them to terminal types, that have such control sequences but lack
// the correct terminfo entries, is a fixup, not an augmentation.
@@ -1733,7 +1785,12 @@ static void patch_terminfo_bugs(TUIData *data, const char *term,
|| (konsolev >= 180770) // #9364
|| tmux // per tmux manual page
// https://lists.gnu.org/archive/html/screen-devel/2013-03/msg00000.html
- || screen
+ || (true_screen
+ && (!screen_host_linuxvt
+ || (screen_host_linuxvt
+ && (xterm_version || (vte_version > 0) || colorterm))))
+ // Since GNU Screen does not support DECSCUSR, DECSCUSR is wrapped
+ // in DCS and output to the host terminal.
|| st // #7641
|| rxvt // per command.C
// per analysis of VT100Terminal.m
@@ -1746,58 +1803,72 @@ static void patch_terminfo_bugs(TUIData *data, const char *term,
|| (linuxvt
&& (xterm_version || (vte_version > 0) || colorterm)))) {
data->unibi_ext.set_cursor_style =
- (int)unibi_add_ext_str(ut, "Ss", "\x1b[%p1%d q");
+ (int)unibi_add_ext_str(ut, "Ss",
+ SCREEN_WRAP(true_screen, "\x1b[%p1%d q"));
if (-1 == data->unibi_ext.reset_cursor_style) {
data->unibi_ext.reset_cursor_style = (int)unibi_add_ext_str(ut, "Se",
"");
}
unibi_set_ext_str(ut, (size_t)data->unibi_ext.reset_cursor_style,
- "\x1b[ q");
- } else if (linuxvt) {
+ SCREEN_WRAP(true_screen, "\x1b[ q"));
+ } else if (linuxvt || screen_host_linuxvt) {
// Linux uses an idiosyncratic escape code to set the cursor shape and
// does not support DECSCUSR.
// See http://linuxgazette.net/137/anonymous.html for more info
- data->unibi_ext.set_cursor_style = (int)unibi_add_ext_str(ut, "Ss",
- "\x1b[?"
- "%?"
- // The parameter passed to Ss is the DECSCUSR parameter, so the
- // terminal capability has to translate into the Linux idiosyncratic
- // parameter.
- //
- // linuxvt only supports block and underline. It is also only
- // possible to have a steady block (no steady underline)
- "%p1%{2}%<" "%t%{8}" // blink block
- "%e%p1%{2}%=" "%t%{112}" // steady block
- "%e%p1%{3}%=" "%t%{4}" // blink underline (set to half block)
- "%e%p1%{4}%=" "%t%{4}" // steady underline
- "%e%p1%{5}%=" "%t%{2}" // blink bar (set to underline)
- "%e%p1%{6}%=" "%t%{2}" // steady bar
- "%e%{0}" // anything else
- "%;" "%dc");
+ //
+ // Since gnu Screen does not have Ss/Se, if the host terminal is a linux
+ // console that does not support xterm extensions, it will wraps the
+ // linux-specific sequence in DCS and outputs it.
+ data->unibi_ext.set_cursor_style = (int)unibi_add_ext_str(
+ ut, "Ss",
+ SCREEN_WRAP(true_screen,
+ "\x1b[?"
+ "%?"
+ // The parameter passed to Ss is the DECSCUSR parameter,
+ // so the
+ // terminal capability has to translate into the Linux
+ // idiosyncratic parameter.
+ //
+ // linuxvt only supports block and underline. It is also
+ // only possible to have a steady block (no steady
+ // underline)
+ "%p1%{2}%<" "%t%{8}" // blink block
+ "%e%p1%{2}%=" "%t%{112}" // steady block
+ "%e%p1%{3}%=" "%t%{4}" // blink underline (set to half
+ // block)
+ "%e%p1%{4}%=" "%t%{4}" // steady underline
+ "%e%p1%{5}%=" "%t%{2}" // blink bar (set to underline)
+ "%e%p1%{6}%=" "%t%{2}" // steady bar
+ "%e%{0}" // anything else
+ "%;" "%dc"));
if (-1 == data->unibi_ext.reset_cursor_style) {
data->unibi_ext.reset_cursor_style = (int)unibi_add_ext_str(ut, "Se",
"");
}
unibi_set_ext_str(ut, (size_t)data->unibi_ext.reset_cursor_style,
- "\x1b[?c");
+ SCREEN_WRAP(true_screen, "\x1b[?c"));
} else if (konsolev > 0 && konsolev < 180770) {
// Konsole before version 18.07.70: set up a nonce profile. This has
// side-effects on temporary font resizing. #6798
- data->unibi_ext.set_cursor_style = (int)unibi_add_ext_str(ut, "Ss",
- TMUX_WRAP(tmux, "\x1b]50;CursorShape=%?"
- "%p1%{3}%<" "%t%{0}" // block
- "%e%p1%{5}%<" "%t%{2}" // underline
- "%e%{1}" // everything else is bar
- "%;%d;BlinkingCursorEnabled=%?"
- "%p1%{1}%<" "%t%{1}" // Fortunately if we exclude zero as special,
- "%e%p1%{1}%&" // in all other cases we can treat bit #0 as a flag.
- "%;%d\x07"));
+ data->unibi_ext.set_cursor_style = (int)unibi_add_ext_str(
+ ut, "Ss",
+ SCREEN_TMUX_WRAP(true_screen, tmux,
+ "\x1b]50;CursorShape=%?"
+ "%p1%{3}%<" "%t%{0}" // block
+ "%e%p1%{5}%<" "%t%{2}" // underline
+ "%e%{1}" // everything else is bar
+ "%;%d;BlinkingCursorEnabled=%?"
+ "%p1%{1}%<" "%t%{1}" // Fortunately if we exclude
+ // zero as special,
+ "%e%p1%{1}%&" // in all other c2ses we can treat bit
+ // #0 as a flag.
+ "%;%d\x07"));
if (-1 == data->unibi_ext.reset_cursor_style) {
data->unibi_ext.reset_cursor_style = (int)unibi_add_ext_str(ut, "Se",
"");
}
unibi_set_ext_str(ut, (size_t)data->unibi_ext.reset_cursor_style,
- "\x1b]50;\x07");
+ SCREEN_TMUX_WRAP(true_screen, tmux, "\x1b]50;\x07"));
}
}
}
@@ -1829,6 +1900,10 @@ static void augment_terminfo(TUIData *data, const char *term,
const char *xterm_version = os_getenv("XTERM_VERSION");
bool true_xterm = xterm && !!xterm_version && !bsdvt;
+ bool true_screen = screen && !os_getenv("TMUX");
+ bool screen_host_rxvt =
+ terminfo_is_term_family(true_screen
+ && term[6] == '.' ? term + 7 : NULL, "rxvt");
// Only define this capability for terminal types that we know understand it.
if (dtterm // originated this extension
@@ -1895,8 +1970,8 @@ static void augment_terminfo(TUIData *data, const char *term,
// all panes, which is not particularly desirable. A better approach
// would use a tmux control sequence and an extra if(screen) test.
data->unibi_ext.set_cursor_color = (int)unibi_add_ext_str(
- ut, NULL, TMUX_WRAP(tmux, "\033]Pl%p1%06x\033\\"));
- } else if ((xterm || rxvt || alacritty)
+ ut, NULL, SCREEN_TMUX_WRAP(true_screen, tmux, "\033]Pl%p1%06x\033\\"));
+ } else if ((xterm || rxvt || tmux || alacritty)
&& (vte_version == 0 || vte_version >= 3900)) {
// Supported in urxvt, newer VTE.
data->unibi_ext.set_cursor_color = (int)unibi_add_ext_str(
@@ -1915,21 +1990,27 @@ static void augment_terminfo(TUIData *data, const char *term,
/// Terminals usually ignore unrecognized private modes, and there is no
/// known ambiguity with these. So we just set them unconditionally.
+ /// If the DECSET is not supported by GNU Screen, it is wrapped with DCS and
+ /// sent to the host terminal.
data->unibi_ext.enable_lr_margin = (int)unibi_add_ext_str(
ut, "ext.enable_lr_margin", "\x1b[?69h");
data->unibi_ext.disable_lr_margin = (int)unibi_add_ext_str(
ut, "ext.disable_lr_margin", "\x1b[?69l");
data->unibi_ext.enable_bracketed_paste = (int)unibi_add_ext_str(
- ut, "ext.enable_bpaste", "\x1b[?2004h");
+ ut, "ext.enable_bpaste", SCREEN_WRAP(true_screen, "\x1b[?2004h"));
data->unibi_ext.disable_bracketed_paste = (int)unibi_add_ext_str(
- ut, "ext.disable_bpaste", "\x1b[?2004l");
+ ut, "ext.disable_bpaste", SCREEN_WRAP(true_screen, "\x1b[?2004l"));
// For urxvt send BOTH xterm and old urxvt sequences. #8695
data->unibi_ext.enable_focus_reporting = (int)unibi_add_ext_str(
ut, "ext.enable_focus",
- rxvt ? "\x1b[?1004h\x1b]777;focus;on\x7" : "\x1b[?1004h");
+ (rxvt || screen_host_rxvt)
+ ? SCREEN_WRAP(true_screen, "\x1b[?1004h\x1b]777;focus;on\x7")
+ : SCREEN_WRAP(true_screen, "\x1b[?1004h"));
data->unibi_ext.disable_focus_reporting = (int)unibi_add_ext_str(
ut, "ext.disable_focus",
- rxvt ? "\x1b[?1004l\x1b]777;focus;off\x7" : "\x1b[?1004l");
+ (rxvt || screen_host_rxvt)
+ ? SCREEN_WRAP(true_screen, "\x1b[?1004l\x1b]777;focus;off\x7")
+ : SCREEN_WRAP(true_screen, "\x1b[?1004l"));
data->unibi_ext.enable_mouse = (int)unibi_add_ext_str(
ut, "ext.enable_mouse", "\x1b[?1002h\x1b[?1006h");
data->unibi_ext.disable_mouse = (int)unibi_add_ext_str(
@@ -1961,7 +2042,23 @@ static void flush_buf(UI *ui)
uv_buf_t *bufp = &bufs[0];
TUIData *data = ui->data;
- if (data->bufpos <= 0 && data->busy == data->is_invisible) {
+ // The content of the output for each condition is shown in the following
+ // table. Therefore, if data->bufpos == 0 and N/A or invis + norm, there is
+ // no need to output it.
+ //
+ // | is_invisible | !is_invisible
+ // ------+-----------------+--------------+---------------
+ // busy | want_invisible | N/A | invis
+ // | !want_invisible | N/A | invis
+ // ------+-----------------+--------------+---------------
+ // !busy | want_invisible | N/A | invis
+ // | !want_invisible | norm | invis + norm
+ // ------+-----------------+--------------+---------------
+ //
+ if (data->bufpos <= 0
+ && ((data->is_invisible && data->busy)
+ || (data->is_invisible && !data->busy && data->want_invisible)
+ || (!data->is_invisible && !data->busy && !data->want_invisible))) {
return;
}
@@ -1988,8 +2085,8 @@ static void flush_buf(UI *ui)
bufp->base = data->norm;
bufp->len = UV_BUF_LEN(data->normlen);
bufp++;
+ data->is_invisible = false;
}
- data->is_invisible = false;
}
uv_write(&req, STRUCT_CAST(uv_stream_t, &data->output_handle),
diff --git a/src/nvim/ui.c b/src/nvim/ui.c
index 0f841760d6..685da77b39 100644
--- a/src/nvim/ui.c
+++ b/src/nvim/ui.c
@@ -226,7 +226,7 @@ int ui_pum_get_height(void)
{
int pum_height = 0;
for (size_t i = 1; i < ui_count; i++) {
- int ui_pum_height = uis[i]->pum_height;
+ int ui_pum_height = uis[i]->pum_nlines;
if (ui_pum_height) {
pum_height =
pum_height != 0 ? MIN(pum_height, ui_pum_height) : ui_pum_height;
@@ -235,6 +235,21 @@ int ui_pum_get_height(void)
return pum_height;
}
+bool ui_pum_get_pos(double *pwidth, double *pheight, double *prow, double *pcol)
+{
+ for (size_t i = 1; i < ui_count; i++) {
+ if (!uis[i]->pum_pos) {
+ continue;
+ }
+ *pwidth = uis[i]->pum_width;
+ *pheight = uis[i]->pum_height;
+ *prow = uis[i]->pum_row;
+ *pcol = uis[i]->pum_col;
+ return true;
+ }
+ return false;
+}
+
static void ui_refresh_event(void **argv)
{
ui_refresh();
@@ -424,7 +439,7 @@ int ui_current_col(void)
void ui_flush(void)
{
cmdline_ui_flush();
- win_ui_flush_positions();
+ win_ui_flush();
msg_ext_ui_flush();
msg_scroll_flush();
diff --git a/src/nvim/ui.h b/src/nvim/ui.h
index 8867b5ee24..d00243d35f 100644
--- a/src/nvim/ui.h
+++ b/src/nvim/ui.h
@@ -53,7 +53,12 @@ struct ui_t {
bool ui_ext[kUIExtCount]; ///< Externalized UI capabilities.
int width;
int height;
- int pum_height;
+ int pum_nlines; /// actual nr. lines shown in PUM
+ bool pum_pos; /// UI reports back pum position?
+ double pum_row;
+ double pum_col;
+ double pum_height;
+ double pum_width;
void *data;
#ifdef INCLUDE_GENERATED_DECLARATIONS
diff --git a/src/nvim/undo.c b/src/nvim/undo.c
index 1f74bada41..97018f6c02 100644
--- a/src/nvim/undo.c
+++ b/src/nvim/undo.c
@@ -2441,10 +2441,11 @@ static void u_undo_end(
uhp = curbuf->b_u_newhead;
}
- if (uhp == NULL)
+ if (uhp == NULL) {
*msgbuf = NUL;
- else
- u_add_time(msgbuf, sizeof(msgbuf), uhp->uh_time);
+ } else {
+ add_time(msgbuf, sizeof(msgbuf), uhp->uh_time);
+ }
{
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
@@ -2509,8 +2510,8 @@ void ex_undolist(exarg_T *eap)
&& uhp->uh_walk != mark) {
vim_snprintf((char *)IObuff, IOSIZE, "%6ld %7d ",
uhp->uh_seq, changes);
- u_add_time(IObuff + STRLEN(IObuff), IOSIZE - STRLEN(IObuff),
- uhp->uh_time);
+ add_time(IObuff + STRLEN(IObuff), IOSIZE - STRLEN(IObuff),
+ uhp->uh_time);
if (uhp->uh_save_nr > 0) {
while (STRLEN(IObuff) < 33)
STRCAT(IObuff, " ");
@@ -2575,30 +2576,6 @@ void ex_undolist(exarg_T *eap)
}
/*
- * Put the timestamp of an undo header in "buf[buflen]" in a nice format.
- */
-static void u_add_time(char_u *buf, size_t buflen, time_t tt)
-{
- struct tm curtime;
-
- if (time(NULL) - tt >= 100) {
- os_localtime_r(&tt, &curtime);
- if (time(NULL) - tt < (60L * 60L * 12L))
- /* within 12 hours */
- (void)strftime((char *)buf, buflen, "%H:%M:%S", &curtime);
- else
- /* longer ago */
- (void)strftime((char *)buf, buflen, "%Y/%m/%d %H:%M:%S", &curtime);
- } else {
- int64_t seconds = time(NULL) - tt;
- vim_snprintf((char *)buf, buflen,
- NGETTEXT("%" PRId64 " second ago",
- "%" PRId64 " seconds ago", (uint32_t)seconds),
- seconds);
- }
-}
-
-/*
* ":undojoin": continue adding to the last entry list
*/
void ex_undojoin(exarg_T *eap)
@@ -2971,7 +2948,10 @@ static char_u *u_save_line(linenr_T lnum)
bool bufIsChanged(buf_T *buf)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
- return !bt_dontwrite(buf) && (buf->b_changed || file_ff_differs(buf, true));
+ // In a "prompt" buffer we do respect 'modified', so that we can control
+ // closing the window by setting or resetting that option.
+ return (!bt_dontwrite(buf) || bt_prompt(buf))
+ && (buf->b_changed || file_ff_differs(buf, true));
}
// Return true if any buffer has changes. Also buffers that are not written.
diff --git a/src/nvim/version.c b/src/nvim/version.c
index c67fd9175f..e9f2b37e6b 100644
--- a/src/nvim/version.c
+++ b/src/nvim/version.c
@@ -129,13 +129,13 @@ static const int included_patches[] = {
1792,
1791,
1790,
- // 1789,
+ 1789,
1788,
1787,
1786,
1785,
1784,
- // 1783,
+ 1783,
1782,
1781,
1780,
@@ -258,7 +258,7 @@ static const int included_patches[] = {
1663,
1662,
1661,
- // 1660,
+ 1660,
1659,
1658,
1657,
@@ -267,7 +267,7 @@ static const int included_patches[] = {
1654,
1653,
1652,
- // 1651,
+ 1651,
1650,
1649,
1648,
@@ -331,7 +331,7 @@ static const int included_patches[] = {
1590,
// 1589,
// 1588,
- // 1587,
+ 1587,
1586,
1585,
1584,
@@ -398,7 +398,7 @@ static const int included_patches[] = {
1523,
1522,
1521,
- // 1520,
+ 1520,
1519,
1518,
1517,
@@ -496,7 +496,7 @@ static const int included_patches[] = {
1425,
1424,
1423,
- // 1422,
+ 1422,
1421,
1420,
1419,
@@ -530,7 +530,7 @@ static const int included_patches[] = {
1391,
1390,
1389,
- // 1388,
+ 1388,
1387,
1386,
1385,
@@ -543,11 +543,11 @@ static const int included_patches[] = {
1378,
1377,
1376,
- // 1375,
+ 1375,
1374,
1373,
1372,
- // 1371,
+ 1371,
1370,
1369,
1368,
@@ -560,11 +560,11 @@ static const int included_patches[] = {
1361,
1360,
1359,
- // 1358,
+ 1358,
1357,
1356,
- // 1355,
- // 1354,
+ 1355,
+ 1354,
1353,
1352,
1351,
@@ -573,7 +573,7 @@ static const int included_patches[] = {
1348,
1347,
1346,
- // 1345,
+ 1345,
1344,
1343,
1342,
@@ -584,7 +584,7 @@ static const int included_patches[] = {
1337,
1336,
// 1335,
- // 1334,
+ 1334,
1333,
1332,
1331,
@@ -626,12 +626,12 @@ static const int included_patches[] = {
1295,
1294,
1293,
- // 1292,
+ 1292,
1291,
1290,
1289,
1288,
- // 1287,
+ 1287,
1286,
1285,
1284,
@@ -776,10 +776,10 @@ static const int included_patches[] = {
1145,
1144,
1143,
- // 1142,
+ 1142,
1141,
1140,
- // 1139,
+ 1139,
1138,
1137,
1136,
@@ -789,13 +789,13 @@ static const int included_patches[] = {
1132,
1131,
1130,
- // 1129,
+ 1129,
1128,
1127,
1126,
- // 1125,
+ 1125,
1124,
- // 1123,
+ 1123,
1122,
1121,
1120,
diff --git a/src/nvim/vim.h b/src/nvim/vim.h
index 51f143a3d7..c1eea1ab90 100644
--- a/src/nvim/vim.h
+++ b/src/nvim/vim.h
@@ -248,6 +248,9 @@ enum { FOLD_TEXT_LEN = 51 }; //!< buffer size for get_foldtext()
# define vim_strpbrk(s, cs) (char_u *)strpbrk((char *)(s), (char *)(cs))
+// Character used as separated in autoload function/variable names.
+#define AUTOLOAD_CHAR '#'
+
#include "nvim/message.h"
// Prefer using emsgf(), because perror() may send the output to the wrong
@@ -257,6 +260,8 @@ enum { FOLD_TEXT_LEN = 51 }; //!< buffer size for get_foldtext()
#define SHOWCMD_COLS 10 // columns needed by shown command
#define STL_MAX_ITEM 80 // max nr of %<flag> in statusline
+#include "nvim/path.h"
+
/// Compare file names
///
/// On some systems case in a file name does not matter, on others it does.
diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c
index b4a0f57e99..b77b80a5f3 100644
--- a/src/nvim/viml/parser/expressions.c
+++ b/src/nvim/viml/parser/expressions.c
@@ -133,9 +133,6 @@ typedef enum {
# include "viml/parser/expressions.c.generated.h"
#endif
-/// Character used as a separator in autoload function/variable names.
-#define AUTOLOAD_CHAR '#'
-
/// Scale number by a given factor
///
/// Used to apply exponent to a number. Idea taken from uClibc.
diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h
index 23e172da75..838a742271 100644
--- a/src/nvim/viml/parser/expressions.h
+++ b/src/nvim/viml/parser/expressions.h
@@ -326,7 +326,7 @@ struct expr_ast_node {
} data;
};
-enum {
+enum ExprParserFlags {
/// Allow multiple expressions in a row: e.g. for :echo
///
/// Parser will still parse only one of them though.
@@ -345,7 +345,7 @@ enum {
// viml_expressions_parser.c, nvim_parse_expression() flags parsing
// alongside with its documentation and flag sets in check_parsing()
// function in expressions parser functional and unit tests.
-} ExprParserFlags;
+};
/// AST error definition
typedef struct {
diff --git a/src/nvim/window.c b/src/nvim/window.c
index 8181883426..0fff93d984 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -618,7 +618,6 @@ void win_set_minimal_style(win_T *wp)
wp->w_p_cuc = false;
wp->w_p_spell = false;
wp->w_p_list = false;
- wp->w_p_fdc = 0;
// Hide EOB region: use " " fillchar and cleared highlighting
if (wp->w_p_fcs_chars.eob != ' ') {
@@ -642,6 +641,12 @@ void win_set_minimal_style(win_T *wp)
wp->w_p_scl = (char_u *)xstrdup("auto");
}
+ // foldcolumn: use 'auto'
+ if (wp->w_p_fdc[0] != '0') {
+ xfree(wp->w_p_fdc);
+ wp->w_p_fdc = (char_u *)xstrdup("0");
+ }
+
// colorcolumn: cleared
if (wp->w_p_cc != NULL && *wp->w_p_cc != NUL) {
xfree(wp->w_p_cc);
@@ -689,6 +694,21 @@ void win_check_anchored_floats(win_T *win)
}
}
+/// Return the number of fold columns to display
+int win_fdccol_count(win_T *wp)
+{
+ const char *fdc = (const char *)wp->w_p_fdc;
+
+ // auto:<NUM>
+ if (strncmp(fdc, "auto:", 5) == 0) {
+ int needed_fdccols = getDeepestNesting(wp);
+ return MIN(fdc[5] - '0', needed_fdccols);
+ } else {
+ return fdc[0] - '0';
+ }
+}
+
+
static void ui_ext_win_position(win_T *wp)
{
if (!wp->w_floating) {
@@ -753,6 +773,21 @@ static void ui_ext_win_position(win_T *wp)
}
+void ui_ext_win_viewport(win_T *wp)
+{
+ if ((wp == curwin || ui_has(kUIMultigrid)) && wp->w_viewport_invalid) {
+ int botline = wp->w_botline;
+ if (botline == wp->w_buffer->b_ml.ml_line_count+1
+ && wp->w_empty_rows == 0) {
+ // TODO(bfredl): The might be more cases to consider, like how does this
+ // interact with incomplete final line? Diff filler lines?
+ botline = wp->w_buffer->b_ml.ml_line_count;
+ }
+ ui_call_win_viewport(wp->w_grid.handle, wp->handle, wp->w_topline-1,
+ botline, wp->w_cursor.lnum-1, wp->w_cursor.col);
+ wp->w_viewport_invalid = false;
+ }
+}
static bool parse_float_anchor(String anchor, FloatAnchor *out)
{
@@ -1077,7 +1112,8 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
// add a status line when p_ls == 1 and splitting the first window
if (one_nonfloat() && p_ls == 1 && oldwin->w_status_height == 0) {
- if (oldwin->w_height <= p_wmh && new_in_layout) {
+ if ((oldwin->w_height + oldwin->w_winbar_height) <= p_wmh
+ && new_in_layout) {
EMSG(_(e_noroom));
return FAIL;
}
@@ -1174,7 +1210,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
* height.
*/
// Current window requires at least 1 space.
- wmh1 = (p_wmh == 0 ? 1 : p_wmh);
+ wmh1 = (p_wmh == 0 ? 1 : p_wmh) + curwin->w_winbar_height;
needed = wmh1 + STATUS_HEIGHT;
if (flags & WSP_ROOM) {
needed += p_wh - wmh1;
@@ -1372,12 +1408,12 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
if (flags & (WSP_TOP | WSP_BOT)) {
/* set height and row of new window to full height */
wp->w_winrow = tabline_height();
- win_new_height(wp, curfrp->fr_height - (p_ls > 0));
+ win_new_height(wp, curfrp->fr_height - (p_ls > 0) - wp->w_winbar_height);
wp->w_status_height = (p_ls > 0);
} else {
/* height and row of new window is same as current window */
wp->w_winrow = oldwin->w_winrow;
- win_new_height(wp, oldwin->w_height);
+ win_new_height(wp, oldwin->w_height + oldwin->w_winbar_height);
wp->w_status_height = oldwin->w_status_height;
}
frp->fr_height = curfrp->fr_height;
@@ -1424,7 +1460,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
* one row for the status line */
win_new_height(wp, new_size);
if (flags & (WSP_TOP | WSP_BOT)) {
- int new_fr_height = curfrp->fr_height - new_size;
+ int new_fr_height = curfrp->fr_height - new_size + wp->w_winbar_height;
if (!((flags & WSP_BOT) && p_ls == 0)) {
new_fr_height -= STATUS_HEIGHT;
@@ -1437,8 +1473,9 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
wp->w_winrow = oldwin->w_winrow;
wp->w_status_height = STATUS_HEIGHT;
oldwin->w_winrow += wp->w_height + STATUS_HEIGHT;
- } else { /* new window below current one */
- wp->w_winrow = oldwin->w_winrow + oldwin->w_height + STATUS_HEIGHT;
+ } else { // new window below current one
+ wp->w_winrow = oldwin->w_winrow + oldwin->w_height
+ + STATUS_HEIGHT + oldwin->w_winbar_height;
wp->w_status_height = oldwin->w_status_height;
if (!(flags & WSP_BOT)) {
oldwin->w_status_height = STATUS_HEIGHT;
@@ -1654,8 +1691,9 @@ make_windows (
maxcount = (curwin->w_width + curwin->w_vsep_width
- (p_wiw - p_wmw)) / (p_wmw + 1);
} else {
- /* Each window needs at least 'winminheight' lines and a status line. */
- maxcount = (curwin->w_height + curwin->w_status_height
+ // Each window needs at least 'winminheight' lines and a status line.
+ maxcount = (curwin->w_height + curwin->w_winbar_height
+ + curwin->w_status_height
- (p_wh - p_wmh)) / (p_wmh + STATUS_HEIGHT);
}
@@ -3109,15 +3147,18 @@ frame_new_height (
int wfh /* obey 'winfixheight' when there is a choice;
may cause the height not to be set */
)
+ FUNC_ATTR_NONNULL_ALL
{
frame_T *frp;
int extra_lines;
int h;
if (topfrp->fr_win != NULL) {
- /* Simple case: just one window. */
+ // Simple case: just one window.
win_new_height(topfrp->fr_win,
- height - topfrp->fr_win->w_status_height);
+ height
+ - topfrp->fr_win->w_status_height
+ - topfrp->fr_win->w_winbar_height);
} else if (topfrp->fr_layout == FR_ROW) {
do {
// All frames in this row get the same new height.
@@ -3422,8 +3463,10 @@ static void frame_fix_width(win_T *wp)
* Set frame height from the window it contains.
*/
static void frame_fix_height(win_T *wp)
+ FUNC_ATTR_NONNULL_ALL
{
- wp->w_frame->fr_height = wp->w_height + wp->w_status_height;
+ wp->w_frame->fr_height =
+ wp->w_height + wp->w_status_height + wp->w_winbar_height;
}
/*
@@ -3440,14 +3483,18 @@ static int frame_minheight(frame_T *topfrp, win_T *next_curwin)
int n;
if (topfrp->fr_win != NULL) {
- if (topfrp->fr_win == next_curwin)
+ if (topfrp->fr_win == next_curwin) {
m = p_wh + topfrp->fr_win->w_status_height;
- else {
- /* window: minimal height of the window plus status line */
+ } else {
+ // window: minimal height of the window plus status line
m = p_wmh + topfrp->fr_win->w_status_height;
- /* Current window is minimal one line high */
- if (p_wmh == 0 && topfrp->fr_win == curwin && next_curwin == NULL)
- ++m;
+ if (topfrp->fr_win == curwin && next_curwin == NULL) {
+ // Current window is minimal one line high and WinBar is visible.
+ if (p_wmh == 0) {
+ m++;
+ }
+ m += curwin->w_winbar_height;
+ }
}
} else if (topfrp->fr_layout == FR_ROW) {
/* get the minimal height from each frame in this row */
@@ -4668,6 +4715,11 @@ static win_T *win_alloc(win_T *after, int hidden)
new_wp->w_scbind_pos = 1;
new_wp->w_floating = 0;
new_wp->w_float_config = FLOAT_CONFIG_INIT;
+ new_wp->w_viewport_invalid = true;
+
+ // use global option for global-local options
+ new_wp->w_p_so = -1;
+ new_wp->w_p_siso = -1;
/* We won't calculate w_fraction until resizing the window */
new_wp->w_fraction = 0;
@@ -4742,6 +4794,7 @@ win_free (
qf_free_all(wp);
+ remove_winbar(wp);
xfree(wp->w_p_cc_cols);
@@ -5008,7 +5061,9 @@ static void frame_comp_pos(frame_T *topfrp, int *row, int *col)
wp->w_redr_status = true;
wp->w_pos_changed = true;
}
- *row += wp->w_height + wp->w_status_height;
+ // WinBar will not show if the window height is zero
+ const int h = wp->w_height + wp->w_winbar_height + wp->w_status_height;
+ *row += h > topfrp->fr_height ? topfrp->fr_height : h;
*col += wp->w_width + wp->w_vsep_width;
} else {
startrow = *row;
@@ -5041,12 +5096,15 @@ void win_setheight(int height)
void win_setheight_win(int height, win_T *win)
{
if (win == curwin) {
- /* Always keep current window at least one line high, even when
- * 'winminheight' is zero. */
- if (height < p_wmh)
+ // Always keep current window at least one line high, even when
+ // 'winminheight' is zero.
+ if (height < p_wmh) {
height = p_wmh;
- if (height == 0)
+ }
+ if (height == 0) {
height = 1;
+ }
+ height += curwin->w_winbar_height;
}
if (win->w_floating) {
@@ -5143,7 +5201,7 @@ static void frame_setheight(frame_T *curfrp, int height)
} else {
win_T *wp = lastwin_nofloating();
room_cmdline = Rows - p_ch - (wp->w_winrow
- + wp->w_height +
+ + wp->w_height + wp->w_winbar_height +
wp->w_status_height);
if (room_cmdline < 0) {
room_cmdline = 0;
@@ -5668,11 +5726,10 @@ void set_fraction(win_T *wp)
}
}
-/*
- * Set the height of a window.
- * This takes care of the things inside the window, not what happens to the
- * window position, the frame or to other windows.
- */
+// Set the height of a window.
+// "height" excludes any window toolbar.
+// This takes care of the things inside the window, not what happens to the
+// window position, the frame or to other windows.
void win_new_height(win_T *wp, int height)
{
// Don't want a negative height. Happens when splitting a tiny window.
@@ -5779,9 +5836,10 @@ void scroll_to_fraction(win_T *wp, int prev_height)
}
if (wp == curwin) {
- if (p_so)
+ if (get_scrolloff_value()) {
update_topline();
- curs_columns(FALSE); /* validate w_wrow */
+ }
+ curs_columns(false); // validate w_wrow
}
if (prev_height > 0) {
wp->w_prev_fraction_row = wp->w_wrow;
@@ -6503,10 +6561,12 @@ void restore_buffer(bufref_T *save_curbuf)
/// @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.
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;
@@ -6543,7 +6603,7 @@ int match_add(win_T *wp, const char *const grp, const char *const pat,
return -1;
}
- /* Find available match ID. */
+ // Find available match ID.
while (id == -1) {
cur = wp->w_match_head;
while (cur != NULL && cur->id != wp->w_next_match_id)
@@ -6553,7 +6613,7 @@ int match_add(win_T *wp, const char *const grp, const char *const pat,
wp->w_next_match_id++;
}
- /* Build new match. */
+ // Build new match.
m = xcalloc(1, sizeof(matchitem_T));
m->id = id;
m->priority = prio;
@@ -6661,9 +6721,9 @@ int match_add(win_T *wp, const char *const grp, const char *const pat,
rtype = VALID;
}
}
-
- /* Insert new match. The match list is in ascending order with regard to
- * the match priorities. */
+
+ // 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) {
@@ -6967,7 +7027,7 @@ void get_framelayout(const frame_T *fr, list_T *l, bool outer)
}
}
-void win_ui_flush_positions(void)
+void win_ui_flush(void)
{
FOR_ALL_TAB_WINDOWS(tp, wp) {
if (wp->w_pos_changed && wp->w_grid.chars != NULL) {
@@ -6978,6 +7038,9 @@ void win_ui_flush_positions(void)
}
wp->w_pos_changed = false;
}
+ if (tp == curtab) {
+ ui_ext_win_viewport(wp);
+ }
}
}
diff --git a/src/nvim/xdiff/xdiff.h b/src/nvim/xdiff/xdiff.h
index bc26fb64fd..8ff4a05bfb 100644
--- a/src/nvim/xdiff/xdiff.h
+++ b/src/nvim/xdiff/xdiff.h
@@ -25,9 +25,9 @@
#ifdef __cplusplus
extern "C" {
-#endif /* #ifdef __cplusplus */
+#endif // #ifdef __cplusplus
-/* xpparm_t.flags */
+// xpparm_t.flags
#define XDF_NEED_MINIMAL (1 << 0)
#define XDF_IGNORE_WHITESPACE (1 << 1)
@@ -48,22 +48,22 @@ extern "C" {
#define XDF_INDENT_HEURISTIC (1 << 23)
-/* xdemitconf_t.flags */
+// xdemitconf_t.flags
#define XDL_EMIT_FUNCNAMES (1 << 0)
#define XDL_EMIT_FUNCCONTEXT (1 << 2)
-/* merge simplification levels */
+// merge simplification levels
#define XDL_MERGE_MINIMAL 0
#define XDL_MERGE_EAGER 1
#define XDL_MERGE_ZEALOUS 2
#define XDL_MERGE_ZEALOUS_ALNUM 3
-/* merge favor modes */
+// merge favor modes
#define XDL_MERGE_FAVOR_OURS 1
#define XDL_MERGE_FAVOR_THEIRS 2
#define XDL_MERGE_FAVOR_UNION 3
-/* merge output styles */
+// merge output styles
#define XDL_MERGE_DIFF3 1
typedef struct s_mmfile {
@@ -79,7 +79,7 @@ typedef struct s_mmbuffer {
typedef struct s_xpparam {
unsigned long flags;
- /* See Documentation/diff-options.txt. */
+ // See Documentation/diff-options.txt.
char **anchors;
size_t anchors_nr;
} xpparam_t;
@@ -126,9 +126,9 @@ typedef struct s_xmparam {
int level;
int favor;
int style;
- const char *ancestor; /* label for orig */
- const char *file1; /* label for mf1 */
- const char *file2; /* label for mf2 */
+ const char *ancestor; // label for orig
+ const char *file1; // label for mf1
+ const char *file2; // label for mf2
} xmparam_t;
#define DEFAULT_CONFLICT_MARKER_SIZE 7
@@ -138,6 +138,6 @@ int xdl_merge(mmfile_t *orig, mmfile_t *mf1, mmfile_t *mf2,
#ifdef __cplusplus
}
-#endif /* #ifdef __cplusplus */
+#endif // #ifdef __cplusplus
-#endif /* #if !defined(XDIFF_H) */
+#endif // #if !defined(XDIFF_H)
diff --git a/src/nvim/xdiff/xdiffi.c b/src/nvim/xdiff/xdiffi.c
index 96d5277027..3806903986 100644
--- a/src/nvim/xdiff/xdiffi.c
+++ b/src/nvim/xdiff/xdiffi.c
@@ -418,24 +418,24 @@ static int xget_indent(xrecord_t *rec)
ret += 1;
else if (c == '\t')
ret += 8 - ret % 8;
- /* ignore other whitespace characters */
+ // ignore other whitespace characters
if (ret >= MAX_INDENT)
return MAX_INDENT;
}
- /* The line contains only whitespace. */
+ // The line contains only whitespace.
return -1;
}
/*
- * If more than this number of consecutive blank rows are found, just return this
- * value. This avoids requiring O(N^2) work for pathological cases, and also
- * ensures that the output of score_split fits in an int.
+ * If more than this number of consecutive blank rows are found, just return
+ * this value. This avoids requiring O(N^2) work for pathological cases, and
+ * also ensures that the output of score_split fits in an int.
*/
#define MAX_BLANKS 20
-/* Characteristics measured about a hypothetical split position. */
+// Characteristics measured about a hypothetical split position.
struct split_measurement {
/*
* Is the split at the end of the file (aside from any blank lines)?
@@ -472,10 +472,10 @@ struct split_measurement {
};
struct split_score {
- /* The effective indent of this split (smaller is preferred). */
+ // The effective indent of this split (smaller is preferred).
int effective_indent;
- /* Penalty for this split (smaller is preferred). */
+ // Penalty for this split (smaller is preferred).
int penalty;
};
@@ -534,16 +534,16 @@ static void measure_split(const xdfile_t *xdf, long split,
* integer math.
*/
-/* Penalty if there are no non-blank lines before the split */
+// Penalty if there are no non-blank lines before the split
#define START_OF_FILE_PENALTY 1
-/* Penalty if there are no non-blank lines after the split */
+// Penalty if there are no non-blank lines after the split
#define END_OF_FILE_PENALTY 21
-/* Multiplier for the number of blank lines around the split */
+// Multiplier for the number of blank lines around the split
#define TOTAL_BLANK_WEIGHT (-30)
-/* Multiplier for the number of blank lines after the split */
+// Multiplier for the number of blank lines after the split
#define POST_BLANK_WEIGHT 6
/*
@@ -610,7 +610,7 @@ static void score_add_split(const struct split_measurement *m, struct split_scor
post_blank = (m->indent == -1) ? 1 + m->post_blank : 0;
total_blank = m->pre_blank + post_blank;
- /* Penalties based on nearby blank lines: */
+ // Penalties based on nearby blank lines:
s->penalty += TOTAL_BLANK_WEIGHT * total_blank;
s->penalty += POST_BLANK_WEIGHT * post_blank;
@@ -621,13 +621,13 @@ static void score_add_split(const struct split_measurement *m, struct split_scor
any_blanks = (total_blank != 0);
- /* Note that the effective indent is -1 at the end of the file: */
+ // Note that the effective indent is -1 at the end of the file:
s->effective_indent += indent;
if (indent == -1) {
- /* No additional adjustments needed. */
+ // No additional adjustments needed.
} else if (m->pre_indent == -1) {
- /* No additional adjustments needed. */
+ // No additional adjustments needed.
} else if (indent > m->pre_indent) {
/*
* The line is indented more than its predecessor.
@@ -669,7 +669,7 @@ static void score_add_split(const struct split_measurement *m, struct split_scor
static int score_cmp(struct split_score *s1, struct split_score *s2)
{
- /* -1 if s1.effective_indent < s2->effective_indent, etc. */
+ // -1 if s1.effective_indent < s2->effective_indent, etc.
int cmp_indents = ((s1->effective_indent > s2->effective_indent) -
(s1->effective_indent < s2->effective_indent));
@@ -809,7 +809,7 @@ int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) {
group_init(xdfo, &go);
while (1) {
- /* If the group is empty in the to-be-compacted file, skip it: */
+ // If the group is empty in the to-be-compacted file, skip it:
if (g.end == g.start)
goto next;
@@ -828,7 +828,7 @@ int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) {
*/
end_matching_other = -1;
- /* Shift the group backward as much as possible: */
+ // Shift the group backward as much as possible:
while (!group_slide_up(xdf, &g, flags))
if (group_previous(xdfo, &go))
xdl_bug("group sync broken sliding up");
@@ -842,7 +842,7 @@ int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) {
if (go.end > go.start)
end_matching_other = g.end;
- /* Now shift the group forward as far as possible: */
+ // Now shift the group forward as far as possible:
while (1) {
if (group_slide_down(xdf, &g, flags))
break;
@@ -863,7 +863,7 @@ int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) {
*/
if (g.end == earliest_end) {
- /* no shifting was possible */
+ // no shifting was possible
} else if (end_matching_other != -1) {
/*
* Move the possibly merged group of changes back to line
@@ -921,7 +921,7 @@ int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) {
}
next:
- /* Move past the just-processed group: */
+ // Move past the just-processed group:
if (group_next(xdf, &g))
break;
if (group_next(xdfo, &go))
diff --git a/src/nvim/xdiff/xdiffi.h b/src/nvim/xdiff/xdiffi.h
index 8f1c7c8b04..467a1e85cd 100644
--- a/src/nvim/xdiff/xdiffi.h
+++ b/src/nvim/xdiff/xdiffi.h
@@ -61,4 +61,4 @@ int xdl_do_patience_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
int xdl_do_histogram_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
xdfenv_t *env);
-#endif /* #if !defined(XDIFFI_H) */
+#endif // #if !defined(XDIFFI_H)
diff --git a/src/nvim/xdiff/xemit.c b/src/nvim/xdiff/xemit.c
index d8a6f1ed38..f1a45139cc 100644
--- a/src/nvim/xdiff/xemit.c
+++ b/src/nvim/xdiff/xemit.c
@@ -54,9 +54,9 @@ xdchange_t *xdl_get_hunk(xdchange_t **xscr, xdemitconf_t const *xecfg)
xdchange_t *xch, *xchp, *lxch;
long max_common = 2 * xecfg->ctxlen + xecfg->interhunkctxlen;
long max_ignorable = xecfg->ctxlen;
- unsigned long ignored = 0; /* number of ignored blank lines */
+ unsigned long ignored = 0; // number of ignored blank lines
- /* remove ignorable changes that are too far before other changes */
+ // remove ignorable changes that are too far before other changes
for (xchp = *xscr; xchp && xchp->ignore; xchp = xchp->next) {
xch = xchp->next;
@@ -99,9 +99,9 @@ xdchange_t *xdl_get_hunk(xdchange_t **xscr, xdemitconf_t const *xecfg)
static long def_ff(const char *rec, long len, char *buf, long sz, void *priv UNUSED)
{
if (len > 0 &&
- (isalpha((unsigned char)*rec) || /* identifier? */
- *rec == '_' || /* also identifier? */
- *rec == '$')) { /* identifiers from VMS and other esoterico */
+ (isalpha((unsigned char)*rec) || // identifier?
+ *rec == '_' || // also identifier?
+ *rec == '$')) { // identifiers from VMS and other esoterico
if (len > sz)
len = sz;
while (0 < len && isspace((unsigned char)rec[len - 1]))
@@ -197,7 +197,7 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
if (xecfg->flags & XDL_EMIT_FUNCCONTEXT) {
long fs1, i1 = xch->i1;
- /* Appended chunk? */
+ // Appended chunk?
if (i1 >= xe->xdf1.nrec) {
long i2 = xch->i2;
diff --git a/src/nvim/xdiff/xemit.h b/src/nvim/xdiff/xemit.h
index 1b9887e670..3ce7e3dd50 100644
--- a/src/nvim/xdiff/xemit.h
+++ b/src/nvim/xdiff/xemit.h
@@ -33,4 +33,4 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
-#endif /* #if !defined(XEMIT_H) */
+#endif // #if !defined(XEMIT_H)
diff --git a/src/nvim/xdiff/xhistogram.c b/src/nvim/xdiff/xhistogram.c
index 3fb8974dd4..28cf8258e5 100644
--- a/src/nvim/xdiff/xhistogram.c
+++ b/src/nvim/xdiff/xhistogram.c
@@ -55,8 +55,8 @@ struct histindex {
struct record {
unsigned int ptr, cnt;
struct record *next;
- } **records, /* an occurrence */
- **line_map; /* map of line to record chain */
+ } **records, // an occurrence
+ **line_map; // map of line to record chain
chastore_t rcha;
unsigned int *next_ptrs;
unsigned int table_bits,
@@ -128,7 +128,7 @@ static int scanA(struct histindex *index, int line1, int count1)
*/
NEXT_PTR(index, ptr) = rec->ptr;
rec->ptr = ptr;
- /* cap rec->cnt at MAX_CNT */
+ // cap rec->cnt at MAX_CNT
rec->cnt = XDL_MIN(MAX_CNT, rec->cnt + 1);
LINE_MAP(index, ptr) = rec;
goto continue_scan;
@@ -154,7 +154,7 @@ static int scanA(struct histindex *index, int line1, int count1)
LINE_MAP(index, ptr) = rec;
continue_scan:
- ; /* no op */
+ ; // no op
}
return 0;
@@ -266,7 +266,7 @@ static int find_lcs(xpparam_t const *xpp, xdfenv_t *env,
index.records = NULL;
index.line_map = NULL;
- /* in case of early xdl_cha_free() */
+ // in case of early xdl_cha_free()
index.rcha.head = NULL;
index.table_bits = xdl_hashbits(count1);
@@ -288,7 +288,7 @@ static int find_lcs(xpparam_t const *xpp, xdfenv_t *env,
goto cleanup;
memset(index.next_ptrs, 0, sz);
- /* lines / 4 + 1 comes from xprepare.c:xdl_prepare_ctx() */
+ // lines / 4 + 1 comes from xprepare.c:xdl_prepare_ctx()
if (xdl_cha_init(&index.rcha, sizeof(struct record), count1 / 4 + 1) < 0)
goto cleanup;
diff --git a/src/nvim/xdiff/xinclude.h b/src/nvim/xdiff/xinclude.h
index 46b8608314..5a359d1431 100644
--- a/src/nvim/xdiff/xinclude.h
+++ b/src/nvim/xdiff/xinclude.h
@@ -20,13 +20,13 @@
*
*/
-/* defines HAVE_ATTRIBUTE_UNUSED */
+// defines HAVE_ATTRIBUTE_UNUSED
#ifdef HAVE_CONFIG_H
# include "../auto/config.h"
#endif
-/* Mark unused function arguments with UNUSED, so that gcc -Wunused-parameter
- * can be used to check for mistakes. */
+// Mark unused function arguments with UNUSED, so that gcc -Wunused-parameter
+// can be used to check for mistakes.
#ifdef HAVE_ATTRIBUTE_UNUSED
# define UNUSED __attribute__((unused))
#else
@@ -58,4 +58,4 @@
#include "xemit.h"
-#endif /* #if !defined(XINCLUDE_H) */
+#endif // #if !defined(XINCLUDE_H)
diff --git a/src/nvim/xdiff/xmacros.h b/src/nvim/xdiff/xmacros.h
index 2809a28ca9..1167ebbb05 100644
--- a/src/nvim/xdiff/xmacros.h
+++ b/src/nvim/xdiff/xmacros.h
@@ -51,4 +51,4 @@ do { \
} while (0)
-#endif /* #if !defined(XMACROS_H) */
+#endif // #if !defined(XMACROS_H)
diff --git a/src/nvim/xdiff/xpatience.c b/src/nvim/xdiff/xpatience.c
index 2c65aac386..f6c84c67d8 100644
--- a/src/nvim/xdiff/xpatience.c
+++ b/src/nvim/xdiff/xpatience.c
@@ -69,7 +69,7 @@ struct hashmap {
*/
unsigned anchor : 1;
} *entries, *first, *last;
- /* were common records found? */
+ // were common records found?
unsigned long has_matches;
mmfile_t *file1, *file2;
xdfenv_t *env;
@@ -86,7 +86,7 @@ static int is_anchor(xpparam_t const *xpp, const char *line)
return 0;
}
-/* The argument "pass" is 1 for the first file, 2 for the second. */
+// The argument "pass" is 1 for the first file, 2 for the second.
static void insert_record(xpparam_t const *xpp, int line, struct hashmap *map,
int pass)
{
@@ -155,7 +155,7 @@ static int fill_hashmap(mmfile_t *file1, mmfile_t *file2,
result->xpp = xpp;
result->env = env;
- /* We know exactly how large we want the hash map */
+ // We know exactly how large we want the hash map
result->alloc = count1 * 2;
result->entries = (struct entry *)
xdl_malloc(result->alloc * sizeof(struct entry));
@@ -163,11 +163,11 @@ static int fill_hashmap(mmfile_t *file1, mmfile_t *file2,
return -1;
memset(result->entries, 0, result->alloc * sizeof(struct entry));
- /* First, fill with entries from the first file */
+ // First, fill with entries from the first file
while (count1--)
insert_record(xpp, line1++, result, 1);
- /* Then search for matches in the second file */
+ // Then search for matches in the second file
while (count2--)
insert_record(xpp, line2++, result, 2);
@@ -185,13 +185,13 @@ static int binary_search(struct entry **sequence, int longest,
while (left + 1 < right) {
int middle = left + (right - left) / 2;
- /* by construction, no two entries can be equal */
+ // by construction, no two entries can be equal
if (sequence[middle]->line2 > entry->line2)
right = middle;
else
left = middle;
}
- /* return the index in "sequence", _not_ the sequence length */
+ // return the index in "sequence", _not_ the sequence length
return left;
}
@@ -216,7 +216,7 @@ static struct entry *find_longest_common_sequence(struct hashmap *map)
*/
int anchor_i = -1;
- /* Added to silence Coverity. */
+ // Added to silence Coverity.
if (sequence == NULL)
return map->first;
@@ -237,13 +237,13 @@ static struct entry *find_longest_common_sequence(struct hashmap *map)
}
}
- /* No common unique lines were found */
+ // No common unique lines were found
if (!longest) {
xdl_free(sequence);
return NULL;
}
- /* Iterate starting at the last element, adjusting the "next" members */
+ // Iterate starting at the last element, adjusting the "next" members
entry = sequence[longest - 1];
entry->next = NULL;
while (entry->previous) {
@@ -273,7 +273,7 @@ static int walk_common_sequence(struct hashmap *map, struct entry *first,
int next1, next2;
for (;;) {
- /* Try to grow the line ranges of common lines */
+ // Try to grow the line ranges of common lines
if (first) {
next1 = first->line1;
next2 = first->line2;
@@ -292,7 +292,7 @@ static int walk_common_sequence(struct hashmap *map, struct entry *first,
line2++;
}
- /* Recurse */
+ // Recurse
if (next1 > line1 || next2 > line2) {
struct hashmap submap;
@@ -343,7 +343,7 @@ static int patience_diff(mmfile_t *file1, mmfile_t *file2,
struct entry *first;
int result = 0;
- /* trivial case: one side is empty */
+ // trivial case: one side is empty
if (!count1) {
while(count2--)
env->xdf2.rchg[line2++ - 1] = 1;
@@ -359,7 +359,7 @@ static int patience_diff(mmfile_t *file1, mmfile_t *file2,
line1, count1, line2, count2))
return -1;
- /* are there any matching lines at all? */
+ // are there any matching lines at all?
if (!map.has_matches) {
while(count1--)
env->xdf1.rchg[line1++ - 1] = 1;
@@ -387,7 +387,7 @@ int xdl_do_patience_diff(mmfile_t *file1, mmfile_t *file2,
if (xdl_prepare_env(file1, file2, xpp, env) < 0)
return -1;
- /* environment is cleaned up in xdl_diff() */
+ // environment is cleaned up in xdl_diff()
return patience_diff(file1, file2, xpp, env,
1, env->xdf1.nrec, 1, env->xdf2.nrec);
}
diff --git a/src/nvim/xdiff/xprepare.h b/src/nvim/xdiff/xprepare.h
index 947d9fc1bb..b67b3b25ab 100644
--- a/src/nvim/xdiff/xprepare.h
+++ b/src/nvim/xdiff/xprepare.h
@@ -31,4 +31,4 @@ void xdl_free_env(xdfenv_t *xe);
-#endif /* #if !defined(XPREPARE_H) */
+#endif // #if !defined(XPREPARE_H)
diff --git a/src/nvim/xdiff/xtypes.h b/src/nvim/xdiff/xtypes.h
index 8442bd436e..026999c1bf 100644
--- a/src/nvim/xdiff/xtypes.h
+++ b/src/nvim/xdiff/xtypes.h
@@ -64,4 +64,4 @@ typedef struct s_xdfenv {
-#endif /* #if !defined(XTYPES_H) */
+#endif // #if !defined(XTYPES_H)
diff --git a/src/nvim/xdiff/xutils.c b/src/nvim/xdiff/xutils.c
index 25a090fb73..e8c7d2f884 100644
--- a/src/nvim/xdiff/xutils.c
+++ b/src/nvim/xdiff/xutils.c
@@ -168,7 +168,7 @@ static int ends_with_optional_cr(const char *l, long s, long i)
s--;
if (s == i)
return 1;
- /* do not ignore CR at the end of an incomplete line */
+ // do not ignore CR at the end of an incomplete line
if (complete && s == i + 1 && l[i] == '\r')
return 1;
return 0;
@@ -208,7 +208,7 @@ int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags)
} else if (flags & XDF_IGNORE_WHITESPACE_CHANGE) {
while (i1 < s1 && i2 < s2) {
if (XDL_ISSPACE(l1[i1]) && XDL_ISSPACE(l2[i2])) {
- /* Skip matching spaces and try again */
+ // Skip matching spaces and try again
while (i1 < s1 && XDL_ISSPACE(l1[i1]))
i1++;
while (i2 < s2 && XDL_ISSPACE(l2[i2]))
@@ -224,7 +224,7 @@ int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags)
i2++;
}
} else if (flags & XDF_IGNORE_CR_AT_EOL) {
- /* Find the first difference and see how the line ends */
+ // Find the first difference and see how the line ends
while (i1 < s1 && i2 < s2 && l1[i1] == l2[i2]) {
i1++;
i2++;
@@ -261,7 +261,7 @@ static unsigned long xdl_hash_record_with_whitespace(char const **data,
for (; ptr < top && *ptr != '\n'; ptr++) {
if (cr_at_eol_only) {
- /* do not ignore CR at the end of an incomplete line */
+ // do not ignore CR at the end of an incomplete line
if (*ptr == '\r' &&
(ptr + 1 < top && ptr[1] == '\n'))
continue;
@@ -274,7 +274,7 @@ static unsigned long xdl_hash_record_with_whitespace(char const **data,
ptr++;
at_eol = (top <= ptr + 1 || ptr[1] == '\n');
if (flags & XDF_IGNORE_WHITESPACE)
- ; /* already handled */
+ ; // already handled
else if (flags & XDF_IGNORE_WHITESPACE_CHANGE
&& !at_eol) {
ha += (ha << 5);
diff --git a/src/nvim/xdiff/xutils.h b/src/nvim/xdiff/xutils.h
index fba7bae03c..0bebd93022 100644
--- a/src/nvim/xdiff/xutils.h
+++ b/src/nvim/xdiff/xutils.h
@@ -44,4 +44,4 @@ int xdl_fall_back_diff(xdfenv_t *diff_env, xpparam_t const *xpp,
-#endif /* #if !defined(XUTILS_H) */
+#endif // #if !defined(XUTILS_H)
diff --git a/src/tree_sitter/api.h b/src/tree_sitter/api.h
index 40187e3db0..9d832e6ec4 100644
--- a/src/tree_sitter/api.h
+++ b/src/tree_sitter/api.h
@@ -174,8 +174,19 @@ const TSLanguage *ts_parser_language(const TSParser *self);
* The second and third parameters specify the location and length of an array
* of ranges. The parser does *not* take ownership of these ranges; it copies
* the data, so it doesn't matter how these ranges are allocated.
+ *
+ * If `length` is zero, then the entire document will be parsed. Otherwise,
+ * the given ranges must be ordered from earliest to latest in the document,
+ * and they must not overlap. That is, the following must hold for all
+ * `i` < `length - 1`:
+ *
+ * ranges[i].end_byte <= ranges[i + 1].start_byte
+ *
+ * If this requirement is not satisfied, the operation will fail, the ranges
+ * will not be assigned, and this function will return `false`. On success,
+ * this function returns `true`
*/
-void ts_parser_set_included_ranges(
+bool ts_parser_set_included_ranges(
TSParser *self,
const TSRange *ranges,
uint32_t length
@@ -325,14 +336,6 @@ TSLogger ts_parser_logger(const TSParser *self);
*/
void ts_parser_print_dot_graphs(TSParser *self, int file);
-/**
- * Set whether or not the parser should halt immediately upon detecting an
- * error. This will generally result in a syntax tree with an error at the
- * root, and one or more partial syntax trees within the error. This behavior
- * may not be supported long-term.
- */
-void ts_parser_halt_on_error(TSParser *self, bool halt);
-
/******************/
/* Section - Tree */
/******************/
@@ -732,13 +735,23 @@ const char *ts_query_string_value_for_id(
);
/**
- * Disable a certain capture within a query. This prevents the capture
- * from being returned in matches, and also avoids any resource usage
- * associated with recording the capture.
+ * Disable a certain capture within a query.
+ *
+ * This prevents the capture from being returned in matches, and also avoids
+ * any resource usage associated with recording the capture. Currently, there
+ * is no way to undo this.
*/
void ts_query_disable_capture(TSQuery *, const char *, uint32_t);
/**
+ * Disable a certain pattern within a query.
+ *
+ * This prevents the pattern from matching and removes most of the overhead
+ * associated with the pattern. Currently, there is no way to undo this.
+ */
+void ts_query_disable_pattern(TSQuery *, uint32_t);
+
+/**
* Create a new cursor for executing a given query.
*
* The cursor stores the state that is needed to iteratively search
diff --git a/src/tree_sitter/array.h b/src/tree_sitter/array.h
index bc77e687bf..26cb8448f1 100644
--- a/src/tree_sitter/array.h
+++ b/src/tree_sitter/array.h
@@ -126,12 +126,28 @@ static inline void array__splice(VoidArray *self, size_t element_size,
array__reserve(self, element_size, new_size);
char *contents = (char *)self->contents;
- if (self->size > old_end)
- memmove(contents + new_end * element_size, contents + old_end * element_size,
- (self->size - old_end) * element_size);
- if (new_count > 0)
- memcpy((contents + index * element_size), elements,
- new_count * element_size);
+ if (self->size > old_end) {
+ memmove(
+ contents + new_end * element_size,
+ contents + old_end * element_size,
+ (self->size - old_end) * element_size
+ );
+ }
+ if (new_count > 0) {
+ if (elements) {
+ memcpy(
+ (contents + index * element_size),
+ elements,
+ new_count * element_size
+ );
+ } else {
+ memset(
+ (contents + index * element_size),
+ 0,
+ new_count * element_size
+ );
+ }
+ }
self->size += new_count - old_count;
}
diff --git a/src/tree_sitter/atomic.h b/src/tree_sitter/atomic.h
index 301ee36700..7bd0e850a9 100644
--- a/src/tree_sitter/atomic.h
+++ b/src/tree_sitter/atomic.h
@@ -12,11 +12,11 @@ static inline size_t atomic_load(const volatile size_t *p) {
}
static inline uint32_t atomic_inc(volatile uint32_t *p) {
- return InterlockedIncrement(p);
+ return InterlockedIncrement((long volatile *)p);
}
static inline uint32_t atomic_dec(volatile uint32_t *p) {
- return InterlockedDecrement(p);
+ return InterlockedDecrement((long volatile *)p);
}
#else
diff --git a/src/tree_sitter/bits.h b/src/tree_sitter/bits.h
index 3bec455dd1..ce7a715567 100644
--- a/src/tree_sitter/bits.h
+++ b/src/tree_sitter/bits.h
@@ -7,7 +7,7 @@ static inline uint32_t bitmask_for_index(uint16_t id) {
return (1u << (31 - id));
}
-#ifdef _WIN32
+#if defined _WIN32 && !defined __GNUC__
#include <intrin.h>
diff --git a/src/tree_sitter/language.c b/src/tree_sitter/language.c
index e240ef2a53..a396b4b0b6 100644
--- a/src/tree_sitter/language.c
+++ b/src/tree_sitter/language.c
@@ -72,8 +72,10 @@ const char *ts_language_symbol_name(
return "ERROR";
} else if (symbol == ts_builtin_sym_error_repeat) {
return "_ERROR";
- } else {
+ } else if (symbol < ts_language_symbol_count(self)) {
return self->symbol_names[symbol];
+ } else {
+ return NULL;
}
}
@@ -119,7 +121,7 @@ const char *ts_language_field_name_for_id(
TSFieldId id
) {
uint32_t count = ts_language_field_count(self);
- if (count) {
+ if (count && id <= count) {
return self->field_names[id];
} else {
return NULL;
diff --git a/src/tree_sitter/language.h b/src/tree_sitter/language.h
index d7e17c3d70..f908b4593a 100644
--- a/src/tree_sitter/language.h
+++ b/src/tree_sitter/language.h
@@ -29,10 +29,12 @@ static inline bool ts_language_is_symbol_external(const TSLanguage *self, TSSymb
return 0 < symbol && symbol < self->external_token_count + 1;
}
-static inline const TSParseAction *ts_language_actions(const TSLanguage *self,
- TSStateId state,
- TSSymbol symbol,
- uint32_t *count) {
+static inline const TSParseAction *ts_language_actions(
+ const TSLanguage *self,
+ TSStateId state,
+ TSSymbol symbol,
+ uint32_t *count
+) {
TableEntry entry;
ts_language_table_entry(self, state, symbol, &entry);
*count = entry.action_count;
@@ -90,8 +92,8 @@ static inline TSStateId ts_language_next_state(const TSLanguage *self,
const TSParseAction *actions = ts_language_actions(self, state, symbol, &count);
if (count > 0) {
TSParseAction action = actions[count - 1];
- if (action.type == TSParseActionTypeShift || action.type == TSParseActionTypeRecover) {
- return action.params.state;
+ if (action.type == TSParseActionTypeShift) {
+ return action.params.extra ? state : action.params.state;
}
}
return 0;
diff --git a/src/tree_sitter/lexer.c b/src/tree_sitter/lexer.c
index e2ca851973..3f8a4c0ae8 100644
--- a/src/tree_sitter/lexer.c
+++ b/src/tree_sitter/lexer.c
@@ -355,7 +355,7 @@ void ts_lexer_mark_end(Lexer *self) {
ts_lexer__mark_end(&self->data);
}
-void ts_lexer_set_included_ranges(
+bool ts_lexer_set_included_ranges(
Lexer *self,
const TSRange *ranges,
uint32_t count
@@ -363,6 +363,16 @@ void ts_lexer_set_included_ranges(
if (count == 0 || !ranges) {
ranges = &DEFAULT_RANGE;
count = 1;
+ } else {
+ uint32_t previous_byte = 0;
+ for (unsigned i = 0; i < count; i++) {
+ const TSRange *range = &ranges[i];
+ if (
+ range->start_byte < previous_byte ||
+ range->end_byte < range->start_byte
+ ) return false;
+ previous_byte = range->end_byte;
+ }
}
size_t size = count * sizeof(TSRange);
@@ -370,6 +380,7 @@ void ts_lexer_set_included_ranges(
memcpy(self->included_ranges, ranges, size);
self->included_range_count = count;
ts_lexer_goto(self, self->current_position);
+ return true;
}
TSRange *ts_lexer_included_ranges(const Lexer *self, uint32_t *count) {
diff --git a/src/tree_sitter/lexer.h b/src/tree_sitter/lexer.h
index 8cd9c26706..5e39294529 100644
--- a/src/tree_sitter/lexer.h
+++ b/src/tree_sitter/lexer.h
@@ -38,7 +38,7 @@ void ts_lexer_start(Lexer *);
void ts_lexer_finish(Lexer *, uint32_t *);
void ts_lexer_advance_to_end(Lexer *);
void ts_lexer_mark_end(Lexer *);
-void ts_lexer_set_included_ranges(Lexer *self, const TSRange *ranges, uint32_t count);
+bool ts_lexer_set_included_ranges(Lexer *self, const TSRange *ranges, uint32_t count);
TSRange *ts_lexer_included_ranges(const Lexer *self, uint32_t *count);
#ifdef __cplusplus
diff --git a/src/tree_sitter/node.c b/src/tree_sitter/node.c
index b03e2fc979..576f3ef38e 100644
--- a/src/tree_sitter/node.c
+++ b/src/tree_sitter/node.c
@@ -150,7 +150,9 @@ static inline TSNode ts_node__child(
while (ts_node_child_iterator_next(&iterator, &child)) {
if (ts_node__is_relevant(child, include_anonymous)) {
if (index == child_index) {
- ts_tree_set_cached_parent(self.tree, &child, &self);
+ if (ts_node__is_relevant(self, true)) {
+ ts_tree_set_cached_parent(self.tree, &child, &self);
+ }
return child;
}
index++;
diff --git a/src/tree_sitter/parser.c b/src/tree_sitter/parser.c
index f381afccab..d4b227308b 100644
--- a/src/tree_sitter/parser.c
+++ b/src/tree_sitter/parser.c
@@ -71,7 +71,6 @@ struct TSParser {
unsigned accept_count;
unsigned operation_count;
const volatile size_t *cancellation_flag;
- bool halt_on_error;
Subtree old_tree;
TSRangeArray included_range_differences;
unsigned included_range_difference_index;
@@ -325,6 +324,12 @@ static bool ts_parser__can_reuse_first_leaf(
TSStateId leaf_state = ts_subtree_leaf_parse_state(tree);
TSLexMode leaf_lex_mode = self->language->lex_modes[leaf_state];
+ // At the end of a non-terminal extra node, the lexer normally returns
+ // NULL, which indicates that the parser should look for a reduce action
+ // at symbol `0`. Avoid reusing tokens in this situation to ensure that
+ // the same thing happens when incrementally reparsing.
+ if (current_lex_mode.lex_state == (uint16_t)(-1)) return false;
+
// If the token was created in a state with the same set of lookaheads, it is reusable.
if (
table_entry->action_count > 0 &&
@@ -594,6 +599,10 @@ static Subtree ts_parser__reuse_node(
uint32_t byte_offset = reusable_node_byte_offset(&self->reusable_node);
uint32_t end_byte_offset = byte_offset + ts_subtree_total_bytes(result);
+ // Do not reuse an EOF node if the included ranges array has changes
+ // later on in the file.
+ if (ts_subtree_is_eof(result)) end_byte_offset = UINT32_MAX;
+
if (byte_offset > position) {
LOG("before_reusable_node symbol:%s", TREE_NAME(result));
break;
@@ -1014,7 +1023,9 @@ static void ts_parser__handle_error(
TSStateId state_after_missing_symbol = ts_language_next_state(
self->language, state, missing_symbol
);
- if (state_after_missing_symbol == 0) continue;
+ if (state_after_missing_symbol == 0 || state_after_missing_symbol == state) {
+ continue;
+ }
if (ts_language_has_reduce_action(
self->language,
@@ -1067,46 +1078,6 @@ static void ts_parser__handle_error(
LOG_STACK();
}
-static void ts_parser__halt_parse(TSParser *self) {
- LOG("halting_parse");
- LOG_STACK();
-
- ts_lexer_advance_to_end(&self->lexer);
- Length remaining_length = length_sub(
- self->lexer.current_position,
- ts_stack_position(self->stack, 0)
- );
-
- Subtree filler_node = ts_subtree_new_error(
- &self->tree_pool,
- 0,
- length_zero(),
- remaining_length,
- remaining_length.bytes,
- 0,
- self->language
- );
- ts_subtree_to_mut_unsafe(filler_node).ptr->visible = false;
- ts_stack_push(self->stack, 0, filler_node, false, 0);
-
- SubtreeArray children = array_new();
- Subtree root_error = ts_subtree_new_error_node(&self->tree_pool, &children, false, self->language);
- ts_stack_push(self->stack, 0, root_error, false, 0);
-
- Subtree eof = ts_subtree_new_leaf(
- &self->tree_pool,
- ts_builtin_sym_end,
- length_zero(),
- length_zero(),
- 0,
- 0,
- false,
- false,
- self->language
- );
- ts_parser__accept(self, 0, eof);
-}
-
static bool ts_parser__recover_to_state(
TSParser *self,
StackVersion version,
@@ -1644,8 +1615,8 @@ static unsigned ts_parser__condense_stack(TSParser *self) {
static bool ts_parser_has_outstanding_parse(TSParser *self) {
return (
- self->lexer.current_position.bytes > 0 ||
- ts_stack_state(self->stack, 0) != 1
+ ts_stack_state(self->stack, 0) != 1 ||
+ ts_stack_node_count_since_error(self->stack, 0) != 0
);
}
@@ -1661,7 +1632,6 @@ TSParser *ts_parser_new(void) {
self->finished_tree = NULL_SUBTREE;
self->reusable_node = reusable_node_new();
self->dot_graph_file = NULL;
- self->halt_on_error = false;
self->cancellation_flag = NULL;
self->timeout_duration = 0;
self->end_clock = clock_null();
@@ -1741,10 +1711,6 @@ void ts_parser_print_dot_graphs(TSParser *self, int fd) {
}
}
-void ts_parser_halt_on_error(TSParser *self, bool should_halt_on_error) {
- self->halt_on_error = should_halt_on_error;
-}
-
const size_t *ts_parser_cancellation_flag(const TSParser *self) {
return (const size_t *)self->cancellation_flag;
}
@@ -1761,8 +1727,12 @@ void ts_parser_set_timeout_micros(TSParser *self, uint64_t timeout_micros) {
self->timeout_duration = duration_from_micros(timeout_micros);
}
-void ts_parser_set_included_ranges(TSParser *self, const TSRange *ranges, uint32_t count) {
- ts_lexer_set_included_ranges(&self->lexer, ranges, count);
+bool ts_parser_set_included_ranges(
+ TSParser *self,
+ const TSRange *ranges,
+ uint32_t count
+) {
+ return ts_lexer_set_included_ranges(&self->lexer, ranges, count);
}
const TSRange *ts_parser_included_ranges(const TSParser *self, uint32_t *count) {
@@ -1858,9 +1828,6 @@ TSTree *ts_parser_parse(
unsigned min_error_cost = ts_parser__condense_stack(self);
if (self->finished_tree.ptr && ts_subtree_error_cost(self->finished_tree) < min_error_cost) {
break;
- } else if (self->halt_on_error && min_error_cost > 0) {
- ts_parser__halt_parse(self);
- break;
}
while (self->included_range_difference_index < self->included_range_differences.size) {
diff --git a/src/tree_sitter/query.c b/src/tree_sitter/query.c
index 2563325248..92a8006179 100644
--- a/src/tree_sitter/query.c
+++ b/src/tree_sitter/query.c
@@ -19,6 +19,8 @@ typedef struct {
uint8_t next_size;
} Stream;
+#define MAX_STEP_CAPTURE_COUNT 4
+
/*
* QueryStep - A step in the process of matching a query. Each node within
* a query S-expression maps to one of these steps. An entire pattern is
@@ -33,13 +35,21 @@ typedef struct {
* captured in this pattern.
* - `depth` - The depth where this node occurs in the pattern. The root node
* of the pattern has depth zero.
+ * - `repeat_step_index` - If this step is part of a repetition, the index of
+ * the beginning of the repetition. A `NONE` value means this step is not
+ * part of a repetition.
*/
typedef struct {
TSSymbol symbol;
TSFieldId field;
- uint16_t capture_id;
- uint16_t depth: 15;
+ uint16_t capture_ids[MAX_STEP_CAPTURE_COUNT];
+ uint16_t repeat_step_index;
+ uint16_t depth: 11;
bool contains_captures: 1;
+ bool is_pattern_start: 1;
+ bool is_immediate: 1;
+ bool is_last: 1;
+ bool is_repeated: 1;
} QueryStep;
/*
@@ -62,10 +72,12 @@ typedef struct {
} SymbolTable;
/*
- * PatternEntry - The set of steps needed to match a particular pattern,
- * represented as a slice of a shared array. These entries are stored in a
- * 'pattern map' - a sorted array that makes it possible to efficiently lookup
- * patterns based on the symbol for their first step.
+ * PatternEntry - Information about the starting point for matching a
+ * particular pattern, consisting of the index of the pattern within the query,
+ * and the index of the patter's first step in the shared `steps` array. These
+ * entries are stored in a 'pattern map' - a sorted array that makes it
+ * possible to efficiently lookup patterns based on the symbol for their first
+ * step.
*/
typedef struct {
uint16_t step_index;
@@ -79,23 +91,27 @@ typedef struct {
* represented as one of these states.
*/
typedef struct {
+ uint32_t id;
uint16_t start_depth;
uint16_t pattern_index;
uint16_t step_index;
- uint16_t capture_count;
- uint16_t capture_list_id;
uint16_t consumed_capture_count;
- uint32_t id;
+ uint16_t repeat_match_count;
+ uint16_t step_index_on_failure;
+ uint8_t capture_list_id;
+ bool seeking_non_match;
} QueryState;
+typedef Array(TSQueryCapture) CaptureList;
+
/*
* CaptureListPool - A collection of *lists* of captures. Each QueryState
- * needs to maintain its own list of captures. They are all represented as
- * slices of one shared array. The CaptureListPool keeps track of which
- * parts of the shared array are currently in use by a QueryState.
+ * needs to maintain its own list of captures. To avoid repeated allocations,
+ * the reuses a fixed set of capture lists, and keeps track of which ones
+ * are currently in use.
*/
typedef struct {
- Array(TSQueryCapture) list;
+ CaptureList list[32];
uint32_t usage_map;
} CaptureListPool;
@@ -113,7 +129,6 @@ struct TSQuery {
Array(Slice) predicates_by_pattern;
Array(uint32_t) start_bytes_by_pattern;
const TSLanguage *language;
- uint16_t max_capture_count;
uint16_t wildcard_root_pattern_count;
TSSymbol *symbol_map;
};
@@ -140,6 +155,7 @@ static const TSQueryError PARENT_DONE = -1;
static const uint8_t PATTERN_DONE_MARKER = UINT8_MAX;
static const uint16_t NONE = UINT16_MAX;
static const TSSymbol WILDCARD_SYMBOL = 0;
+static const TSSymbol NAMED_WILDCARD_SYMBOL = UINT16_MAX - 1;
static const uint16_t MAX_STATE_COUNT = 32;
// #define LOG(...) fprintf(stderr, __VA_ARGS__)
@@ -226,24 +242,25 @@ static void stream_scan_identifier(Stream *stream) {
static CaptureListPool capture_list_pool_new(void) {
return (CaptureListPool) {
- .list = array_new(),
.usage_map = UINT32_MAX,
};
}
-static void capture_list_pool_reset(CaptureListPool *self, uint16_t list_size) {
+static void capture_list_pool_reset(CaptureListPool *self) {
self->usage_map = UINT32_MAX;
- uint32_t total_size = MAX_STATE_COUNT * list_size;
- array_reserve(&self->list, total_size);
- self->list.size = total_size;
+ for (unsigned i = 0; i < 32; i++) {
+ array_clear(&self->list[i]);
+ }
}
static void capture_list_pool_delete(CaptureListPool *self) {
- array_delete(&self->list);
+ for (unsigned i = 0; i < 32; i++) {
+ array_delete(&self->list[i]);
+ }
}
-static TSQueryCapture *capture_list_pool_get(CaptureListPool *self, uint16_t id) {
- return &self->list.contents[id * (self->list.size / MAX_STATE_COUNT)];
+static CaptureList *capture_list_pool_get(CaptureListPool *self, uint16_t id) {
+ return &self->list[id];
}
static bool capture_list_pool_is_empty(const CaptureListPool *self) {
@@ -262,6 +279,7 @@ static uint16_t capture_list_pool_acquire(CaptureListPool *self) {
}
static void capture_list_pool_release(CaptureListPool *self, uint16_t id) {
+ array_clear(&self->list[id]);
self->usage_map |= bitmask_for_index(id);
}
@@ -324,6 +342,114 @@ static uint16_t symbol_table_insert_name(
return self->slices.size - 1;
}
+static uint16_t symbol_table_insert_name_with_escapes(
+ SymbolTable *self,
+ const char *escaped_name,
+ uint32_t escaped_length
+) {
+ Slice slice = {
+ .offset = self->characters.size,
+ .length = 0,
+ };
+ array_grow_by(&self->characters, escaped_length + 1);
+
+ // Copy the contents of the literal into the characters buffer, processing escape
+ // sequences like \n and \". This needs to be done before checking if the literal
+ // is already present, in order to do the string comparison.
+ bool is_escaped = false;
+ for (unsigned i = 0; i < escaped_length; i++) {
+ const char *src = &escaped_name[i];
+ char *dest = &self->characters.contents[slice.offset + slice.length];
+ if (is_escaped) {
+ switch (*src) {
+ case 'n':
+ *dest = '\n';
+ break;
+ case 'r':
+ *dest = '\r';
+ break;
+ case 't':
+ *dest = '\t';
+ break;
+ case '0':
+ *dest = '\0';
+ break;
+ default:
+ *dest = *src;
+ break;
+ }
+ is_escaped = false;
+ slice.length++;
+ } else {
+ if (*src == '\\') {
+ is_escaped = true;
+ } else {
+ *dest = *src;
+ slice.length++;
+ }
+ }
+ }
+
+ // If the string is already present, remove the redundant content from the characters
+ // buffer and return the existing id.
+ int id = symbol_table_id_for_name(self, &self->characters.contents[slice.offset], slice.length);
+ if (id >= 0) {
+ self->characters.size -= (escaped_length + 1);
+ return id;
+ }
+
+ self->characters.contents[slice.offset + slice.length] = 0;
+ array_push(&self->slices, slice);
+ return self->slices.size - 1;
+}
+
+/************
+ * QueryStep
+ ************/
+
+static QueryStep query_step__new(
+ TSSymbol symbol,
+ uint16_t depth,
+ bool is_immediate
+) {
+ return (QueryStep) {
+ .symbol = symbol,
+ .depth = depth,
+ .field = 0,
+ .capture_ids = {NONE, NONE, NONE, NONE},
+ .contains_captures = false,
+ .is_repeated = false,
+ .is_last = false,
+ .is_pattern_start = false,
+ .is_immediate = is_immediate,
+ .repeat_step_index = NONE,
+ };
+}
+
+static void query_step__add_capture(QueryStep *self, uint16_t capture_id) {
+ for (unsigned i = 0; i < MAX_STEP_CAPTURE_COUNT; i++) {
+ if (self->capture_ids[i] == NONE) {
+ self->capture_ids[i] = capture_id;
+ break;
+ }
+ }
+}
+
+static void query_step__remove_capture(QueryStep *self, uint16_t capture_id) {
+ for (unsigned i = 0; i < MAX_STEP_CAPTURE_COUNT; i++) {
+ if (self->capture_ids[i] == capture_id) {
+ self->capture_ids[i] = NONE;
+ while (i + 1 < MAX_STEP_CAPTURE_COUNT) {
+ if (self->capture_ids[i + 1] == NONE) break;
+ self->capture_ids[i] = self->capture_ids[i + 1];
+ self->capture_ids[i + 1] = NONE;
+ i++;
+ }
+ break;
+ }
+ }
+}
+
/*********
* Query
*********/
@@ -333,7 +459,7 @@ static uint16_t symbol_table_insert_name(
// to quickly find the starting steps of all of the patterns whose root matches
// that node. Each entry has two fields: a `pattern_index`, which identifies one
// of the patterns in the query, and a `step_index`, which indicates the start
-// offset of that pattern's steps pattern within the `steps` array.
+// offset of that pattern's steps within the `steps` array.
//
// The entries are sorted by the patterns' root symbols, and lookups use a
// binary search. This ensures that the cost of this initial lookup step
@@ -399,14 +525,14 @@ static void ts_query__finalize_steps(TSQuery *self) {
for (unsigned i = 0; i < self->steps.size; i++) {
QueryStep *step = &self->steps.contents[i];
uint32_t depth = step->depth;
- if (step->capture_id != NONE) {
+ if (step->capture_ids[0] != NONE) {
step->contains_captures = true;
} else {
step->contains_captures = false;
for (unsigned j = i + 1; j < self->steps.size; j++) {
QueryStep *s = &self->steps.contents[j];
if (s->depth == PATTERN_DONE_MARKER || s->depth <= depth) break;
- if (s->capture_id != NONE) step->contains_captures = true;
+ if (s->capture_ids[0] != NONE) step->contains_captures = true;
}
}
}
@@ -473,9 +599,22 @@ static TSQueryError ts_query__parse_predicate(
stream_advance(stream);
// Parse the string content
+ bool is_escaped = false;
const char *string_content = stream->input;
- while (stream->next != '"') {
- if (stream->next == '\n' || !stream_advance(stream)) {
+ for (;;) {
+ if (is_escaped) {
+ is_escaped = false;
+ } else {
+ if (stream->next == '\\') {
+ is_escaped = true;
+ } else if (stream->next == '"') {
+ break;
+ } else if (stream->next == '\n') {
+ stream_reset(stream, string_content - 1);
+ return TSQueryErrorSyntax;
+ }
+ }
+ if (!stream_advance(stream)) {
stream_reset(stream, string_content - 1);
return TSQueryErrorSyntax;
}
@@ -483,7 +622,7 @@ static TSQueryError ts_query__parse_predicate(
uint32_t length = stream->input - string_content;
// Add a step for the node
- uint16_t id = symbol_table_insert_name(
+ uint16_t id = symbol_table_insert_name_with_escapes(
&self->predicate_values,
string_content,
length
@@ -533,7 +672,8 @@ static TSQueryError ts_query__parse_pattern(
TSQuery *self,
Stream *stream,
uint32_t depth,
- uint32_t *capture_count
+ uint32_t *capture_count,
+ bool is_immediate
) {
uint16_t starting_step_index = self->steps.size;
@@ -552,7 +692,7 @@ static TSQueryError ts_query__parse_pattern(
// Parse a nested list, which represents a pattern followed by
// zero-or-more predicates.
if (stream->next == '(' && depth == 0) {
- TSQueryError e = ts_query__parse_pattern(self, stream, 0, capture_count);
+ TSQueryError e = ts_query__parse_pattern(self, stream, 0, capture_count, is_immediate);
if (e) return e;
// Parse the predicates.
@@ -573,7 +713,7 @@ static TSQueryError ts_query__parse_pattern(
// Parse the wildcard symbol
if (stream->next == '*') {
- symbol = WILDCARD_SYMBOL;
+ symbol = depth > 0 ? NAMED_WILDCARD_SYMBOL : WILDCARD_SYMBOL;
stream_advance(stream);
}
@@ -597,24 +737,37 @@ static TSQueryError ts_query__parse_pattern(
}
// Add a step for the node.
- array_push(&self->steps, ((QueryStep) {
- .depth = depth,
- .symbol = symbol,
- .field = 0,
- .capture_id = NONE,
- .contains_captures = false,
- }));
+ array_push(&self->steps, query_step__new(symbol, depth, is_immediate));
// Parse the child patterns
stream_skip_whitespace(stream);
+ bool child_is_immediate = false;
+ uint16_t child_start_step_index = self->steps.size;
for (;;) {
- TSQueryError e = ts_query__parse_pattern(self, stream, depth + 1, capture_count);
+ if (stream->next == '.') {
+ child_is_immediate = true;
+ stream_advance(stream);
+ stream_skip_whitespace(stream);
+ }
+
+ TSQueryError e = ts_query__parse_pattern(
+ self,
+ stream,
+ depth + 1,
+ capture_count,
+ child_is_immediate
+ );
if (e == PARENT_DONE) {
+ if (child_is_immediate) {
+ self->steps.contents[child_start_step_index].is_last = true;
+ }
stream_advance(stream);
break;
} else if (e) {
return e;
}
+
+ child_is_immediate = false;
}
}
@@ -643,13 +796,7 @@ static TSQueryError ts_query__parse_pattern(
stream_reset(stream, string_content);
return TSQueryErrorNodeType;
}
- array_push(&self->steps, ((QueryStep) {
- .depth = depth,
- .symbol = symbol,
- .field = 0,
- .capture_id = NONE,
- .contains_captures = false,
- }));
+ array_push(&self->steps, query_step__new(symbol, depth, is_immediate));
if (stream->next != '"') return TSQueryErrorSyntax;
stream_advance(stream);
@@ -672,7 +819,13 @@ static TSQueryError ts_query__parse_pattern(
// Parse the pattern
uint32_t step_index = self->steps.size;
- TSQueryError e = ts_query__parse_pattern(self, stream, depth, capture_count);
+ TSQueryError e = ts_query__parse_pattern(
+ self,
+ stream,
+ depth,
+ capture_count,
+ is_immediate
+ );
if (e == PARENT_DONE) return TSQueryErrorSyntax;
if (e) return e;
@@ -695,12 +848,7 @@ static TSQueryError ts_query__parse_pattern(
stream_skip_whitespace(stream);
// Add a step that matches any kind of node
- array_push(&self->steps, ((QueryStep) {
- .depth = depth,
- .symbol = WILDCARD_SYMBOL,
- .field = 0,
- .contains_captures = false,
- }));
+ array_push(&self->steps, query_step__new(WILDCARD_SYMBOL, depth, is_immediate));
}
else {
@@ -709,26 +857,43 @@ static TSQueryError ts_query__parse_pattern(
stream_skip_whitespace(stream);
- // Parse an '@'-prefixed capture pattern
- if (stream->next == '@') {
- stream_advance(stream);
+ // Parse suffixes modifiers for this pattern
+ for (;;) {
+ QueryStep *step = &self->steps.contents[starting_step_index];
- // Parse the capture name
- if (!stream_is_ident_start(stream)) return TSQueryErrorSyntax;
- const char *capture_name = stream->input;
- stream_scan_identifier(stream);
- uint32_t length = stream->input - capture_name;
+ if (stream->next == '+') {
+ stream_advance(stream);
+ step->is_repeated = true;
+ array_back(&self->steps)->repeat_step_index = starting_step_index;
+ stream_skip_whitespace(stream);
+ }
- // Add the capture id to the first step of the pattern
- uint16_t capture_id = symbol_table_insert_name(
- &self->captures,
- capture_name,
- length
- );
- self->steps.contents[starting_step_index].capture_id = capture_id;
- (*capture_count)++;
+ // Parse an '@'-prefixed capture pattern
+ else if (stream->next == '@') {
+ stream_advance(stream);
- stream_skip_whitespace(stream);
+ // Parse the capture name
+ if (!stream_is_ident_start(stream)) return TSQueryErrorSyntax;
+ const char *capture_name = stream->input;
+ stream_scan_identifier(stream);
+ uint32_t length = stream->input - capture_name;
+
+ // Add the capture id to the first step of the pattern
+ uint16_t capture_id = symbol_table_insert_name(
+ &self->captures,
+ capture_name,
+ length
+ );
+ query_step__add_capture(step, capture_id);
+ (*capture_count)++;
+
+ stream_skip_whitespace(stream);
+ }
+
+ // No more suffix modifiers
+ else {
+ break;
+ }
}
return 0;
@@ -778,24 +943,22 @@ TSQuery *ts_query_new(
.predicates_by_pattern = array_new(),
.symbol_map = symbol_map,
.wildcard_root_pattern_count = 0,
- .max_capture_count = 0,
.language = language,
};
// Parse all of the S-expressions in the given string.
Stream stream = stream_new(source, source_len);
stream_skip_whitespace(&stream);
- uint32_t start_step_index;
while (stream.input < stream.end) {
- start_step_index = self->steps.size;
+ uint32_t start_step_index = self->steps.size;
uint32_t capture_count = 0;
array_push(&self->start_bytes_by_pattern, stream.input - source);
array_push(&self->predicates_by_pattern, ((Slice) {
.offset = self->predicate_steps.size,
.length = 0,
}));
- *error_type = ts_query__parse_pattern(self, &stream, 0, &capture_count);
- array_push(&self->steps, ((QueryStep) { .depth = PATTERN_DONE_MARKER }));
+ *error_type = ts_query__parse_pattern(self, &stream, 0, &capture_count, false);
+ array_push(&self->steps, query_step__new(0, PATTERN_DONE_MARKER, false));
// If any pattern could not be parsed, then report the error information
// and terminate.
@@ -805,7 +968,19 @@ TSQuery *ts_query_new(
return NULL;
}
+ // If a pattern has a wildcard at its root, optimize the matching process
+ // by skipping matching the wildcard.
+ if (
+ self->steps.contents[start_step_index].symbol == WILDCARD_SYMBOL
+ ) {
+ QueryStep *second_step = &self->steps.contents[start_step_index + 1];
+ if (second_step->symbol != WILDCARD_SYMBOL && second_step->depth != PATTERN_DONE_MARKER) {
+ start_step_index += 1;
+ }
+ }
+
// Maintain a map that can look up patterns for a given root symbol.
+ self->steps.contents[start_step_index].is_pattern_start = true;
ts_query__pattern_map_insert(
self,
self->steps.contents[start_step_index].symbol,
@@ -814,13 +989,6 @@ TSQuery *ts_query_new(
if (self->steps.contents[start_step_index].symbol == WILDCARD_SYMBOL) {
self->wildcard_root_pattern_count++;
}
-
- // Keep track of the maximum number of captures in pattern, because
- // that numer determines how much space is needed to store each capture
- // list.
- if (capture_count > self->max_capture_count) {
- self->max_capture_count = capture_count;
- }
}
ts_query__finalize_steps(self);
@@ -891,16 +1059,31 @@ void ts_query_disable_capture(
const char *name,
uint32_t length
) {
+ // Remove capture information for any pattern step that previously
+ // captured with the given name.
int id = symbol_table_id_for_name(&self->captures, name, length);
if (id != -1) {
for (unsigned i = 0; i < self->steps.size; i++) {
QueryStep *step = &self->steps.contents[i];
- if (step->capture_id == id) {
- step->capture_id = NONE;
- }
+ query_step__remove_capture(step, id);
+ }
+ ts_query__finalize_steps(self);
+ }
+}
+
+void ts_query_disable_pattern(
+ TSQuery *self,
+ uint32_t pattern_index
+) {
+ // Remove the given pattern from the pattern map. Its steps will still
+ // be in the `steps` array, but they will never be read.
+ for (unsigned i = 0; i < self->pattern_map.size; i++) {
+ PatternEntry *pattern = &self->pattern_map.contents[i];
+ if (pattern->pattern_index == pattern_index) {
+ array_erase(&self->pattern_map, i);
+ i--;
}
}
- ts_query__finalize_steps(self);
}
/***************
@@ -940,7 +1123,7 @@ void ts_query_cursor_exec(
array_clear(&self->states);
array_clear(&self->finished_states);
ts_tree_cursor_reset(&self->cursor, node);
- capture_list_pool_reset(&self->capture_list_pool, query->max_capture_count);
+ capture_list_pool_reset(&self->capture_list_pool);
self->next_state_id = 0;
self->depth = 0;
self->ascending = false;
@@ -984,12 +1167,12 @@ static bool ts_query_cursor__first_in_progress_capture(
bool result = false;
for (unsigned i = 0; i < self->states.size; i++) {
const QueryState *state = &self->states.contents[i];
- if (state->capture_count > 0) {
- const TSQueryCapture *captures = capture_list_pool_get(
- &self->capture_list_pool,
- state->capture_list_id
- );
- uint32_t capture_byte = ts_node_start_byte(captures[0].node);
+ const CaptureList *captures = capture_list_pool_get(
+ &self->capture_list_pool,
+ state->capture_list_id
+ );
+ if (captures->size > 0) {
+ uint32_t capture_byte = ts_node_start_byte(captures->contents[0].node);
if (
!result ||
capture_byte < *byte_offset ||
@@ -1010,8 +1193,21 @@ static bool ts_query_cursor__first_in_progress_capture(
static bool ts_query__cursor_add_state(
TSQueryCursor *self,
- const PatternEntry *slice
+ const PatternEntry *pattern
) {
+ QueryStep *step = &self->query->steps.contents[pattern->step_index];
+
+ // If this pattern begins with a repetition, then avoid creating
+ // new states after already matching the repetition one or more times.
+ // The query should only one match for the repetition - the one that
+ // started the earliest.
+ if (step->is_repeated) {
+ for (unsigned i = 0; i < self->states.size; i++) {
+ QueryState *state = &self->states.contents[i];
+ if (state->step_index == pattern->step_index) return true;
+ }
+ }
+
uint32_t list_id = capture_list_pool_acquire(&self->capture_list_pool);
// If there are no capture lists left in the pool, then terminate whichever
@@ -1037,14 +1233,20 @@ static bool ts_query__cursor_add_state(
}
}
- LOG(" start state. pattern:%u\n", slice->pattern_index);
+ LOG(
+ " start state. pattern:%u, step:%u\n",
+ pattern->pattern_index,
+ pattern->step_index
+ );
array_push(&self->states, ((QueryState) {
.capture_list_id = list_id,
- .step_index = slice->step_index,
- .pattern_index = slice->pattern_index,
- .start_depth = self->depth,
- .capture_count = 0,
+ .step_index = pattern->step_index,
+ .pattern_index = pattern->pattern_index,
+ .start_depth = self->depth - step->depth,
.consumed_capture_count = 0,
+ .repeat_match_count = 0,
+ .step_index_on_failure = NONE,
+ .seeking_non_match = false,
}));
return true;
}
@@ -1058,15 +1260,15 @@ static QueryState *ts_query__cursor_copy_state(
array_push(&self->states, *state);
QueryState *new_state = array_back(&self->states);
new_state->capture_list_id = new_list_id;
- TSQueryCapture *old_captures = capture_list_pool_get(
+ CaptureList *old_captures = capture_list_pool_get(
&self->capture_list_pool,
state->capture_list_id
);
- TSQueryCapture *new_captures = capture_list_pool_get(
+ CaptureList *new_captures = capture_list_pool_get(
&self->capture_list_pool,
new_list_id
);
- memcpy(new_captures, old_captures, state->capture_count * sizeof(TSQueryCapture));
+ array_push_all(new_captures, old_captures);
return new_state;
}
@@ -1113,15 +1315,16 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) {
return self->finished_states.size > 0;
}
} else {
- bool can_have_later_siblings;
+ bool has_later_siblings;
bool can_have_later_siblings_with_this_field;
TSFieldId field_id = ts_tree_cursor_current_status(
&self->cursor,
- &can_have_later_siblings,
+ &has_later_siblings,
&can_have_later_siblings_with_this_field
);
TSNode node = ts_tree_cursor_current_node(&self->cursor);
TSSymbol symbol = ts_node_symbol(node);
+ bool is_named = ts_node_is_named(node);
if (symbol != ts_builtin_sym_error && self->query->symbol_map) {
symbol = self->query->symbol_map[symbol];
}
@@ -1145,43 +1348,46 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) {
) return false;
LOG(
- "enter node. type:%s, field:%s, row:%u state_count:%u, finished_state_count:%u, can_have_later_siblings:%d, can_have_later_siblings_with_this_field:%d\n",
+ "enter node. "
+ "type:%s, field:%s, row:%u state_count:%u, "
+ "finished_state_count:%u, has_later_siblings:%d, "
+ "can_have_later_siblings_with_this_field:%d\n",
ts_node_type(node),
ts_language_field_name_for_id(self->query->language, field_id),
ts_node_start_point(node).row,
self->states.size,
self->finished_states.size,
- can_have_later_siblings,
+ has_later_siblings,
can_have_later_siblings_with_this_field
);
// Add new states for any patterns whose root node is a wildcard.
for (unsigned i = 0; i < self->query->wildcard_root_pattern_count; i++) {
- PatternEntry *slice = &self->query->pattern_map.contents[i];
- QueryStep *step = &self->query->steps.contents[slice->step_index];
+ PatternEntry *pattern = &self->query->pattern_map.contents[i];
+ QueryStep *step = &self->query->steps.contents[pattern->step_index];
// If this node matches the first step of the pattern, then add a new
// state at the start of this pattern.
if (step->field && field_id != step->field) continue;
- if (!ts_query__cursor_add_state(self, slice)) break;
+ if (!ts_query__cursor_add_state(self, pattern)) break;
}
// Add new states for any patterns whose root node matches this node.
unsigned i;
if (ts_query__pattern_map_search(self->query, symbol, &i)) {
- PatternEntry *slice = &self->query->pattern_map.contents[i];
- QueryStep *step = &self->query->steps.contents[slice->step_index];
+ PatternEntry *pattern = &self->query->pattern_map.contents[i];
+ QueryStep *step = &self->query->steps.contents[pattern->step_index];
do {
// If this node matches the first step of the pattern, then add a new
// state at the start of this pattern.
if (step->field && field_id != step->field) continue;
- if (!ts_query__cursor_add_state(self, slice)) break;
+ if (!ts_query__cursor_add_state(self, pattern)) break;
// Advance to the next pattern whose root node matches this node.
i++;
if (i == self->query->pattern_map.size) break;
- slice = &self->query->pattern_map.contents[i];
- step = &self->query->steps.contents[slice->step_index];
+ pattern = &self->query->pattern_map.contents[i];
+ step = &self->query->steps.contents[pattern->step_index];
} while (step->symbol == symbol);
}
@@ -1191,14 +1397,23 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) {
QueryStep *step = &self->query->steps.contents[state->step_index];
// Check that the node matches all of the criteria for the next
- // step of the pattern.if (
+ // step of the pattern.
if ((uint32_t)state->start_depth + (uint32_t)step->depth != self->depth) continue;
// Determine if this node matches this step of the pattern, and also
// if this node can have later siblings that match this step of the
// pattern.
- bool node_does_match = !step->symbol || step->symbol == symbol;
- bool later_sibling_can_match = can_have_later_siblings;
+ bool node_does_match =
+ step->symbol == symbol ||
+ step->symbol == WILDCARD_SYMBOL ||
+ (step->symbol == NAMED_WILDCARD_SYMBOL && is_named);
+ bool later_sibling_can_match = has_later_siblings;
+ if (step->is_immediate && is_named) {
+ later_sibling_can_match = false;
+ }
+ if (step->is_last && has_later_siblings) {
+ node_does_match = false;
+ }
if (step->field) {
if (step->field == field_id) {
if (!can_have_later_siblings_with_this_field) {
@@ -1210,6 +1425,24 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) {
}
if (!node_does_match) {
+ // If this QueryState has processed a repeating sequence, and that repeating
+ // sequence has ended, move on to the *next* step of this state's pattern.
+ if (
+ state->step_index_on_failure != NONE &&
+ (!later_sibling_can_match || step->is_repeated)
+ ) {
+ LOG(
+ " finish repetition state. pattern:%u, step:%u\n",
+ state->pattern_index,
+ state->step_index
+ );
+ state->step_index = state->step_index_on_failure;
+ state->step_index_on_failure = NONE;
+ state->repeat_match_count = 0;
+ i--;
+ continue;
+ }
+
if (!later_sibling_can_match) {
LOG(
" discard state. pattern:%u, step:%u\n",
@@ -1224,9 +1457,17 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) {
i--;
n--;
}
+
+ state->seeking_non_match = false;
continue;
}
+ // The `seeking_non_match` flag indicates that a previous QueryState
+ // has already begun processing this repeating sequence, so that *this*
+ // QueryState should not begin matching until a separate repeating sequence
+ // is found.
+ if (state->seeking_non_match) continue;
+
// Some patterns can match their root node in multiple ways,
// capturing different children. If this pattern step could match
// later children within the same parent, then this query state
@@ -1236,11 +1477,20 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) {
// siblings.
QueryState *next_state = state;
if (
- step->depth > 0 &&
+ !step->is_pattern_start &&
step->contains_captures &&
- later_sibling_can_match
+ later_sibling_can_match &&
+ state->repeat_match_count == 0
) {
QueryState *copy = ts_query__cursor_copy_state(self, state);
+
+ // The QueryState that matched this node has begun matching a repeating
+ // sequence. The QueryState that *skipped* this node should not start
+ // matching later elements of the same repeating sequence.
+ if (step->is_repeated) {
+ state->seeking_non_match = true;
+ }
+
if (copy) {
LOG(
" split state. pattern:%u, step:%u\n",
@@ -1249,53 +1499,71 @@ static inline bool ts_query_cursor__advance(TSQueryCursor *self) {
);
next_state = copy;
} else {
- LOG(" canot split state.\n");
+ LOG(" cannot split state.\n");
}
}
- LOG(
- " advance state. pattern:%u, step:%u\n",
- next_state->pattern_index,
- next_state->step_index
- );
-
// If the current node is captured in this pattern, add it to the
// capture list.
- if (step->capture_id != NONE) {
- LOG(
- " capture node. pattern:%u, capture_id:%u\n",
- next_state->pattern_index,
- step->capture_id
- );
- TSQueryCapture *capture_list = capture_list_pool_get(
+ for (unsigned j = 0; j < MAX_STEP_CAPTURE_COUNT; j++) {
+ uint16_t capture_id = step->capture_ids[j];
+ if (step->capture_ids[j] == NONE) break;
+ CaptureList *capture_list = capture_list_pool_get(
&self->capture_list_pool,
next_state->capture_list_id
);
- capture_list[next_state->capture_count++] = (TSQueryCapture) {
+ array_push(capture_list, ((TSQueryCapture) {
node,
- step->capture_id
- };
+ capture_id
+ }));
+ LOG(
+ " capture node. pattern:%u, capture_id:%u, capture_count:%u\n",
+ next_state->pattern_index,
+ capture_id,
+ capture_list->size
+ );
}
- // If the pattern is now done, then remove it from the list of
- // in-progress states, and add it to the list of finished states.
- next_state->step_index++;
- QueryStep *next_step = step + 1;
- if (next_step->depth == PATTERN_DONE_MARKER) {
- LOG(" finish pattern %u\n", next_state->pattern_index);
+ // If this is the end of a repetition, then jump back to the beginning
+ // of that repetition.
+ if (step->repeat_step_index != NONE) {
+ next_state->step_index_on_failure = next_state->step_index + 1;
+ next_state->step_index = step->repeat_step_index;
+ next_state->repeat_match_count++;
+ LOG(
+ " continue repeat. pattern:%u, match_count:%u\n",
+ next_state->pattern_index,
+ next_state->repeat_match_count
+ );
+ } else {
+ next_state->step_index++;
+ LOG(
+ " advance state. pattern:%u, step:%u\n",
+ next_state->pattern_index,
+ next_state->step_index
+ );
- next_state->id = self->next_state_id++;
- array_push(&self->finished_states, *next_state);
- if (next_state == state) {
- array_erase(&self->states, i);
- i--;
- n--;
- } else {
- self->states.size--;
+ QueryStep *next_step = step + 1;
+
+ // If the pattern is now done, then remove it from the list of
+ // in-progress states, and add it to the list of finished states.
+ if (next_step->depth == PATTERN_DONE_MARKER) {
+ LOG(" finish pattern %u\n", next_state->pattern_index);
+
+ next_state->id = self->next_state_id++;
+ array_push(&self->finished_states, *next_state);
+ if (next_state == state) {
+ array_erase(&self->states, i);
+ i--;
+ n--;
+ } else {
+ self->states.size--;
+ }
}
}
}
+
// Continue descending if possible.
if (ts_tree_cursor_goto_first_child(&self->cursor)) {
self->depth++;
@@ -1321,11 +1589,12 @@ bool ts_query_cursor_next_match(
QueryState *state = &self->finished_states.contents[0];
match->id = state->id;
match->pattern_index = state->pattern_index;
- match->capture_count = state->capture_count;
- match->captures = capture_list_pool_get(
+ CaptureList *captures = capture_list_pool_get(
&self->capture_list_pool,
state->capture_list_id
);
+ match->captures = captures->contents;
+ match->capture_count = captures->size;
capture_list_pool_release(&self->capture_list_pool, state->capture_list_id);
array_erase(&self->finished_states, 0);
return true;
@@ -1378,13 +1647,13 @@ bool ts_query_cursor_next_capture(
uint32_t first_finished_pattern_index = first_unfinished_pattern_index;
for (unsigned i = 0; i < self->finished_states.size; i++) {
const QueryState *state = &self->finished_states.contents[i];
- if (state->capture_count > state->consumed_capture_count) {
- const TSQueryCapture *captures = capture_list_pool_get(
- &self->capture_list_pool,
- state->capture_list_id
- );
+ CaptureList *captures = capture_list_pool_get(
+ &self->capture_list_pool,
+ state->capture_list_id
+ );
+ if (captures->size > state->consumed_capture_count) {
uint32_t capture_byte = ts_node_start_byte(
- captures[state->consumed_capture_count].node
+ captures->contents[state->consumed_capture_count].node
);
if (
capture_byte < first_finished_capture_byte ||
@@ -1416,11 +1685,12 @@ bool ts_query_cursor_next_capture(
];
match->id = state->id;
match->pattern_index = state->pattern_index;
- match->capture_count = state->capture_count;
- match->captures = capture_list_pool_get(
+ CaptureList *captures = capture_list_pool_get(
&self->capture_list_pool,
state->capture_list_id
);
+ match->captures = captures->contents;
+ match->capture_count = captures->size;
*capture_index = state->consumed_capture_count;
state->consumed_capture_count++;
return true;
diff --git a/src/tree_sitter/stack.c b/src/tree_sitter/stack.c
index 3e842c99c3..ade1577566 100644
--- a/src/tree_sitter/stack.c
+++ b/src/tree_sitter/stack.c
@@ -11,7 +11,7 @@
#define MAX_NODE_POOL_SIZE 50
#define MAX_ITERATOR_COUNT 64
-#ifdef _WIN32
+#if defined _WIN32 && !defined __GNUC__
#define inline __forceinline
#else
#define inline static inline __attribute__((always_inline))
diff --git a/src/tree_sitter/subtree.c b/src/tree_sitter/subtree.c
index 30144fa175..b98f172339 100644
--- a/src/tree_sitter/subtree.c
+++ b/src/tree_sitter/subtree.c
@@ -322,12 +322,9 @@ void ts_subtree_balance(Subtree self, SubtreePool *pool, const TSLanguage *langu
if (tree.ptr->repeat_depth > 0) {
Subtree child1 = tree.ptr->children[0];
Subtree child2 = tree.ptr->children[tree.ptr->child_count - 1];
- if (
- ts_subtree_child_count(child1) > 0 &&
- ts_subtree_child_count(child2) > 0 &&
- child1.ptr->repeat_depth > child2.ptr->repeat_depth
- ) {
- unsigned n = child1.ptr->repeat_depth - child2.ptr->repeat_depth;
+ long repeat_delta = (long)ts_subtree_repeat_depth(child1) - (long)ts_subtree_repeat_depth(child2);
+ if (repeat_delta > 0) {
+ unsigned n = repeat_delta;
for (unsigned i = n / 2; i > 0; i /= 2) {
ts_subtree__compress(tree, i, language, &pool->tree_stack);
n -= i;
@@ -344,10 +341,6 @@ void ts_subtree_balance(Subtree self, SubtreePool *pool, const TSLanguage *langu
}
}
-static inline uint32_t ts_subtree_repeat_depth(Subtree self) {
- return ts_subtree_child_count(self) ? self.ptr->repeat_depth : 0;
-}
-
void ts_subtree_set_children(
MutableSubtree self, Subtree *children, uint32_t child_count, const TSLanguage *language
) {
diff --git a/src/tree_sitter/subtree.h b/src/tree_sitter/subtree.h
index 79ccd92390..18c48dcbd0 100644
--- a/src/tree_sitter/subtree.h
+++ b/src/tree_sitter/subtree.h
@@ -206,6 +206,10 @@ static inline uint32_t ts_subtree_child_count(Subtree self) {
return self.data.is_inline ? 0 : self.ptr->child_count;
}
+static inline uint32_t ts_subtree_repeat_depth(Subtree self) {
+ return self.data.is_inline ? 0 : self.ptr->repeat_depth;
+}
+
static inline uint32_t ts_subtree_node_count(Subtree self) {
return (self.data.is_inline || self.ptr->child_count == 0) ? 1 : self.ptr->node_count;
}
diff --git a/src/tree_sitter/utf16.c b/src/tree_sitter/utf16.c
deleted file mode 100644
index 3956c01cb9..0000000000
--- a/src/tree_sitter/utf16.c
+++ /dev/null
@@ -1,33 +0,0 @@
-#include "./utf16.h"
-
-utf8proc_ssize_t utf16_iterate(
- const utf8proc_uint8_t *string,
- utf8proc_ssize_t length,
- utf8proc_int32_t *code_point
-) {
- if (length < 2) {
- *code_point = -1;
- return 0;
- }
-
- uint16_t *units = (uint16_t *)string;
- uint16_t unit = units[0];
-
- if (unit < 0xd800 || unit >= 0xe000) {
- *code_point = unit;
- return 2;
- }
-
- if (unit < 0xdc00) {
- if (length >= 4) {
- uint16_t next_unit = units[1];
- if (next_unit >= 0xdc00 && next_unit < 0xe000) {
- *code_point = 0x10000 + ((unit - 0xd800) << 10) + (next_unit - 0xdc00);
- return 4;
- }
- }
- }
-
- *code_point = -1;
- return 2;
-}
diff --git a/src/tree_sitter/utf16.h b/src/tree_sitter/utf16.h
deleted file mode 100644
index 32fd05e6db..0000000000
--- a/src/tree_sitter/utf16.h
+++ /dev/null
@@ -1,21 +0,0 @@
-#ifndef TREE_SITTER_UTF16_H_
-#define TREE_SITTER_UTF16_H_
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#include <stdint.h>
-#include <stdlib.h>
-#include "utf8proc.h"
-
-// Analogous to utf8proc's utf8proc_iterate function. Reads one code point from
-// the given UTF16 string and stores it in the location pointed to by `code_point`.
-// Returns the number of bytes in `string` that were read.
-utf8proc_ssize_t utf16_iterate(const utf8proc_uint8_t *, utf8proc_ssize_t, utf8proc_int32_t *);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif // TREE_SITTER_UTF16_H_