aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rwxr-xr-xsrc/clint.py44
-rw-r--r--src/nvim/CMakeLists.txt7
-rw-r--r--src/nvim/README.md4
-rw-r--r--src/nvim/api/buffer.c56
-rw-r--r--src/nvim/api/private/dispatch.h6
-rw-r--r--src/nvim/api/ui.c1
-rw-r--r--src/nvim/api/ui_events.in.h2
-rw-r--r--src/nvim/api/vim.c120
-rw-r--r--src/nvim/api/window.c23
-rw-r--r--src/nvim/assert.h7
-rw-r--r--src/nvim/auevents.lua1
-rw-r--r--src/nvim/buffer.c57
-rw-r--r--src/nvim/buffer_defs.h37
-rw-r--r--src/nvim/buffer_updates.c41
-rw-r--r--src/nvim/change.c1798
-rw-r--r--src/nvim/change.h11
-rw-r--r--src/nvim/channel.c231
-rw-r--r--src/nvim/channel.h11
-rw-r--r--src/nvim/context.c383
-rw-r--r--src/nvim/context.h46
-rw-r--r--src/nvim/cursor.c1
-rw-r--r--src/nvim/diff.c11
-rw-r--r--src/nvim/digraph.c9
-rw-r--r--src/nvim/edit.c242
-rw-r--r--src/nvim/eval.c1167
-rw-r--r--src/nvim/eval.h7
-rw-r--r--src/nvim/eval.lua15
-rw-r--r--src/nvim/eval/executor.c4
-rw-r--r--src/nvim/eval/typval.c12
-rw-r--r--src/nvim/eval/typval.h1
-rw-r--r--src/nvim/eval/typval_encode.c.h2
-rw-r--r--src/nvim/event/libuv_process.c6
-rw-r--r--src/nvim/event/process.c20
-rw-r--r--src/nvim/event/process.h4
-rw-r--r--src/nvim/event/stream.c5
-rw-r--r--src/nvim/ex_cmds.c90
-rw-r--r--src/nvim/ex_cmds.lua32
-rw-r--r--src/nvim/ex_cmds2.c41
-rw-r--r--src/nvim/ex_cmds_defs.h2
-rw-r--r--src/nvim/ex_docmd.c143
-rw-r--r--src/nvim/ex_getln.c654
-rw-r--r--src/nvim/fileio.c384
-rw-r--r--src/nvim/fold.c1
-rw-r--r--src/nvim/func_attr.h4
-rw-r--r--src/nvim/garray.c8
-rw-r--r--src/nvim/generators/c_grammar.lua6
-rw-r--r--src/nvim/generators/gen_api_dispatch.lua99
-rw-r--r--src/nvim/generators/gen_api_ui_events.lua39
-rw-r--r--src/nvim/generators/gen_char_blob.lua11
-rwxr-xr-xsrc/nvim/generators/gen_declarations.lua11
-rw-r--r--src/nvim/generators/gen_eval.lua16
-rw-r--r--src/nvim/generators/gen_events.lua8
-rw-r--r--src/nvim/generators/gen_ex_cmds.lua9
-rw-r--r--src/nvim/generators/gen_options.lua7
-rw-r--r--src/nvim/generators/gen_unicode_tables.lua24
-rw-r--r--src/nvim/getchar.c329
-rw-r--r--src/nvim/globals.h66
-rw-r--r--src/nvim/grid_defs.h7
-rw-r--r--src/nvim/hardcopy.c14
-rw-r--r--src/nvim/highlight.c110
-rw-r--r--src/nvim/highlight_defs.h3
-rw-r--r--src/nvim/iconv.h46
-rw-r--r--src/nvim/if_cscope.c8
-rw-r--r--src/nvim/indent.c104
-rw-r--r--src/nvim/lua/converter.c41
-rw-r--r--src/nvim/lua/executor.c250
-rw-r--r--src/nvim/lua/vim.lua11
-rw-r--r--src/nvim/macros.h11
-rw-r--r--src/nvim/main.c54
-rw-r--r--src/nvim/map.c2
-rw-r--r--src/nvim/mark.c77
-rw-r--r--src/nvim/mbyte.c280
-rw-r--r--src/nvim/mbyte.h2
-rw-r--r--src/nvim/memline.c64
-rw-r--r--src/nvim/memory.c4
-rw-r--r--src/nvim/message.c26
-rw-r--r--src/nvim/misc1.c1755
-rw-r--r--src/nvim/move.c36
-rw-r--r--src/nvim/msgpack_rpc/channel.c2
-rw-r--r--src/nvim/msgpack_rpc/helpers.c5
-rw-r--r--src/nvim/normal.c77
-rw-r--r--src/nvim/ops.c214
-rw-r--r--src/nvim/option.c80
-rw-r--r--src/nvim/option_defs.h58
-rw-r--r--src/nvim/options.lua50
-rw-r--r--src/nvim/os/dl.c1
-rw-r--r--src/nvim/os/env.c52
-rw-r--r--src/nvim/os/fs.c145
-rw-r--r--src/nvim/os/input.c11
-rw-r--r--src/nvim/os/process.c19
-rw-r--r--src/nvim/os/pty_process_unix.c2
-rw-r--r--src/nvim/os/pty_process_win.c2
-rw-r--r--src/nvim/os/time.c25
-rw-r--r--src/nvim/os_unix.c7
-rw-r--r--src/nvim/path.c12
-rw-r--r--src/nvim/popupmnu.c9
-rw-r--r--src/nvim/profile.c95
-rw-r--r--src/nvim/quickfix.c90
-rw-r--r--src/nvim/regexp.c29
-rw-r--r--src/nvim/regexp_nfa.c184
-rw-r--r--src/nvim/screen.c274
-rw-r--r--src/nvim/search.c59
-rw-r--r--src/nvim/shada.c316
-rw-r--r--src/nvim/shada.h11
-rw-r--r--src/nvim/sign.c96
-rw-r--r--src/nvim/spell.c17
-rw-r--r--src/nvim/spellfile.c25
-rw-r--r--src/nvim/state.c2
-rw-r--r--src/nvim/syntax.c157
-rw-r--r--src/nvim/tag.c43
-rw-r--r--src/nvim/terminal.c3
-rw-r--r--src/nvim/testdir/Makefile35
-rw-r--r--src/nvim/testdir/runnvim.vim5
-rw-r--r--src/nvim/testdir/runtest.vim36
-rw-r--r--src/nvim/testdir/samples/memfile_test.c2
-rw-r--r--src/nvim/testdir/setup.vim17
-rw-r--r--src/nvim/testdir/shared.vim33
-rw-r--r--src/nvim/testdir/test49.vim3
-rw-r--r--src/nvim/testdir/test_arabic.vim48
-rw-r--r--src/nvim/testdir/test_arglist.vim69
-rw-r--r--src/nvim/testdir/test_assert.vim13
-rw-r--r--src/nvim/testdir/test_autocmd.vim22
-rw-r--r--src/nvim/testdir/test_bufline.vim67
-rw-r--r--src/nvim/testdir/test_cmdline.vim49
-rw-r--r--src/nvim/testdir/test_compiler.vim6
-rw-r--r--src/nvim/testdir/test_const.vim237
-rw-r--r--src/nvim/testdir/test_cscope.vim58
-rw-r--r--src/nvim/testdir/test_cursor_func.vim20
-rw-r--r--src/nvim/testdir/test_debugger.vim16
-rw-r--r--src/nvim/testdir/test_diffmode.vim3
-rw-r--r--src/nvim/testdir/test_edit.vim7
-rw-r--r--src/nvim/testdir/test_environ.vim44
-rw-r--r--src/nvim/testdir/test_excmd.vim10
-rw-r--r--src/nvim/testdir/test_filetype.vim29
-rw-r--r--src/nvim/testdir/test_filter_cmd.vim58
-rw-r--r--src/nvim/testdir/test_filter_map.vim5
-rw-r--r--src/nvim/testdir/test_functions.vim98
-rw-r--r--src/nvim/testdir/test_getvar.vim44
-rw-r--r--src/nvim/testdir/test_ins_complete.vim36
-rw-r--r--src/nvim/testdir/test_lambda.vim38
-rw-r--r--src/nvim/testdir/test_makeencoding.py4
-rw-r--r--src/nvim/testdir/test_maparg.vim4
-rw-r--r--src/nvim/testdir/test_mapping.vim25
-rw-r--r--src/nvim/testdir/test_mksession.vim11
-rw-r--r--src/nvim/testdir/test_modeline.vim95
-rw-r--r--src/nvim/testdir/test_normal.vim15
-rw-r--r--src/nvim/testdir/test_options.vim117
-rw-r--r--src/nvim/testdir/test_partial.vim5
-rw-r--r--src/nvim/testdir/test_popup.vim36
-rw-r--r--src/nvim/testdir/test_profile.vim100
-rw-r--r--src/nvim/testdir/test_quickfix.vim46
-rw-r--r--src/nvim/testdir/test_recover.vim7
-rw-r--r--src/nvim/testdir/test_regexp_latin.vim13
-rw-r--r--src/nvim/testdir/test_regexp_utf8.vim9
-rw-r--r--src/nvim/testdir/test_registers.vim19
-rw-r--r--src/nvim/testdir/test_search.vim43
-rw-r--r--src/nvim/testdir/test_source.vim48
-rw-r--r--src/nvim/testdir/test_spell.vim54
-rw-r--r--src/nvim/testdir/test_startup.vim82
-rw-r--r--src/nvim/testdir/test_statusline.vim3
-rw-r--r--src/nvim/testdir/test_suspend.vim29
-rw-r--r--src/nvim/testdir/test_syntax.vim2
-rw-r--r--src/nvim/testdir/test_tabline.vim39
-rw-r--r--src/nvim/testdir/test_tagcase.vim1
-rw-r--r--src/nvim/testdir/test_tagjump.vim5
-rw-r--r--src/nvim/testdir/test_taglist.vim49
-rw-r--r--src/nvim/testdir/test_usercommands.vim95
-rw-r--r--src/nvim/testdir/test_vimscript.vim42
-rw-r--r--src/nvim/testdir/test_virtualedit.vim16
-rw-r--r--src/nvim/testdir/test_window_cmd.vim37
-rw-r--r--src/nvim/testdir/test_windows_home.vim2
-rw-r--r--src/nvim/testdir/test_writefile.vim2
-rw-r--r--src/nvim/tui/input.c126
-rw-r--r--src/nvim/tui/tui.c18
-rw-r--r--src/nvim/ugrid.c4
-rw-r--r--src/nvim/ui.c20
-rw-r--r--src/nvim/ui_compositor.c102
-rw-r--r--src/nvim/undo.c16
-rw-r--r--src/nvim/version.c215
-rw-r--r--src/nvim/viml/parser/expressions.c2
-rw-r--r--src/nvim/window.c371
181 files changed, 9208 insertions, 5282 deletions
diff --git a/src/clint.py b/src/clint.py
index 862fdbc06b..675b67ccef 100755
--- a/src/clint.py
+++ b/src/clint.py
@@ -7,15 +7,14 @@
# modification, are permitted provided that the following conditions are
# met:
#
-# * Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-# * Redistributions in binary form must reproduce the above
-# copyright notice, this list of conditions and the following disclaimer
-# in the documentation and/or other materials provided with the
-# distribution.
-# * Neither the name of Google Inc. nor the names of its
-# contributors may be used to endorse or promote products derived from
-# this software without specific prior written permission.
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# * Neither the name of Google Inc. nor the names of its contributors may be
+# used to endorse or promote products derived from this software without
+# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
@@ -49,7 +48,6 @@ from __future__ import unicode_literals
import codecs
import copy
-import fileinput
import getopt
import math # for log
import os
@@ -567,6 +565,7 @@ class _CppLintState(object):
return
self.record_errors_file = open(fname, 'w')
+
_cpplint_state = _CppLintState()
@@ -2124,7 +2123,7 @@ def CheckExpressionAlignment(filename, clean_lines, linenum, error, startpos=0):
'Inner expression indentation should be 4')
else:
if (pos != level_starts[depth][0] + 1
- + (level_starts[depth][2] == '{')):
+ + (level_starts[depth][2] == '{')):
if depth not in ignore_error_levels:
error(filename, linenum, 'whitespace/alignment', 2,
('Inner expression should be aligned '
@@ -2297,7 +2296,7 @@ def CheckSpacing(filename, clean_lines, linenum, nesting_state, error):
line = clean_lines.elided[linenum] # get rid of comments and strings
# Don't try to do spacing checks for operator methods
- line = re.sub(r'operator(==|!=|<|<<|<=|>=|>>|>)\(', 'operator\(', line)
+ line = re.sub(r'operator(==|!=|<|<<|<=|>=|>>|>)\(', r'operator\(', line)
# We allow no-spaces around = within an if: "if ( (a=Foo()) == 0 )".
# Otherwise not. Note we only check for non-spaces on *both* sides;
@@ -2598,8 +2597,8 @@ def CheckBraces(filename, clean_lines, linenum, error):
if (not Search(r'[,;:}{(]\s*$', prevline) and
not Match(r'\s*#', prevline)):
error(filename, linenum, 'whitespace/braces', 4,
- '{ should almost always be at the end'
- ' of the previous line')
+ '{ should almost always be at the end'
+ ' of the previous line')
# Brace must appear after function signature, but on the *next* line
if Match(r'^(?:\w+(?: ?\*+)? )+\w+\(', line):
@@ -2611,9 +2610,13 @@ def CheckBraces(filename, clean_lines, linenum, error):
'Brace starting function body must be placed on its own line')
else:
func_start_linenum = end_linenum + 1
- while not clean_lines.lines[func_start_linenum] == '{':
- attrline = Match(r'^((?!# *define).*?)(?:FUNC_ATTR|FUNC_API|REAL_FATTR)_\w+(?:\(\d+(, \d+)*\))?',
- clean_lines.lines[func_start_linenum])
+ while not clean_lines.lines[func_start_linenum] == "{":
+ attrline = Match(
+ r'^((?!# *define).*?)'
+ r'(?:FUNC_ATTR|FUNC_API|REAL_FATTR)_\w+'
+ r'(?:\(\d+(, \d+)*\))?',
+ clean_lines.lines[func_start_linenum],
+ )
if attrline:
if len(attrline.group(1)) != 2:
error(filename, func_start_linenum,
@@ -3179,11 +3182,12 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension,
if not Search(r'eval/typval\.[ch]$', filename):
match = Search(r'(?:\.|->)'
r'(?:lv_(?:first|last|refcount|len|watch|idx(?:_item)?'
- r'|copylist|lock)'
- r'|li_(?:next|prev|tv))\b', line)
+ r'|copylist|lock)'
+ r'|li_(?:next|prev|tv))\b', line)
if match:
error(filename, linenum, 'runtime/deprecated', 4,
- 'Accessing list_T internals directly is prohibited (hint: see commit d46e37cb4c71)')
+ 'Accessing list_T internals directly is prohibited '
+ '(hint: see commit d46e37cb4c71)')
# Check for suspicious usage of "if" like
# } if (a == b) {
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt
index 8abc43c2aa..0fc7c780ca 100644
--- a/src/nvim/CMakeLists.txt
+++ b/src/nvim/CMakeLists.txt
@@ -7,6 +7,7 @@ if(USE_GCOV)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --coverage")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --coverage")
+ add_definitions(-DUSE_GCOV)
endif()
endif()
@@ -138,7 +139,6 @@ set(CONV_SOURCES
eval.c
ex_cmds.c
ex_docmd.c
- ex_getln.c
fileio.c
mbyte.c
memline.c
@@ -163,8 +163,8 @@ if(NOT MSVC)
set_source_files_properties(
${CONV_SOURCES} PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-conversion")
# gperf generates ANSI-C with incorrect linkage, ignore it.
- check_c_compiler_flag(-Wno-static-in-inline HAS_WNO_STATIC_IN_INLINE_FLAG)
- if(HAS_WNO_STATIC_IN_INLINE_FLAG)
+ 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")
else()
@@ -383,6 +383,7 @@ endif()
# Put these last on the link line, since multiple things may depend on them.
list(APPEND NVIM_LINK_LIBRARIES
+ ${LIBLUV_LIBRARIES}
${LIBUV_LIBRARIES}
${MSGPACK_LIBRARIES}
${LIBVTERM_LIBRARIES}
diff --git a/src/nvim/README.md b/src/nvim/README.md
index 02464c2500..3c956cb2e9 100644
--- a/src/nvim/README.md
+++ b/src/nvim/README.md
@@ -60,9 +60,9 @@ Enable the sanitizer(s) via these environment variables:
# Change to detect_leaks=1 to detect memory leaks (slower).
export ASAN_OPTIONS="detect_leaks=0:log_path=$HOME/logs/asan"
- export ASAN_SYMBOLIZER_PATH=/usr/lib/llvm-5.0/bin/llvm-symbolizer
+ export ASAN_SYMBOLIZER_PATH=/usr/bin/llvm-symbolizer
- export MSAN_SYMBOLIZER_PATH=/usr/lib/llvm-5.0/bin/llvm-symbolizer
+ export MSAN_SYMBOLIZER_PATH=/usr/bin/llvm-symbolizer
export TSAN_OPTIONS="external_symbolizer_path=/usr/lib/llvm-5.0/bin/llvm-symbolizer log_path=${HOME}/logs/tsan"
Logs will be written to `${HOME}/logs/*san.PID`.
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 81b3851c53..41d7d8ba6b 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -12,8 +12,10 @@
#include "nvim/api/buffer.h"
#include "nvim/api/private/helpers.h"
#include "nvim/api/private/defs.h"
+#include "nvim/lua/executor.h"
#include "nvim/vim.h"
#include "nvim/buffer.h"
+#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/getchar.h"
@@ -108,9 +110,11 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err)
/// `nvim_buf_lines_event`. Otherwise, the first notification will be
/// a `nvim_buf_changedtick_event`. Not used for lua callbacks.
/// @param opts Optional parameters.
-/// `on_lines`: lua callback received on change.
+/// `on_lines`: lua callback received on change.
/// `on_changedtick`: lua callback received on changedtick
/// increment without text change.
+/// `utf_sizes`: include UTF-32 and UTF-16 size of
+/// the replaced region.
/// See |api-buffer-updates-lua| for more information
/// @param[out] err Error details, if any
/// @return False when updates couldn't be enabled because the buffer isn't
@@ -137,24 +141,44 @@ Boolean nvim_buf_attach(uint64_t channel_id,
if (is_lua && strequal("on_lines", k.data)) {
if (v->type != kObjectTypeLuaRef) {
api_set_error(err, kErrorTypeValidation, "callback is not a function");
- return false;
+ goto error;
}
cb.on_lines = v->data.luaref;
v->data.integer = LUA_NOREF;
} else if (is_lua && strequal("on_changedtick", k.data)) {
if (v->type != kObjectTypeLuaRef) {
api_set_error(err, kErrorTypeValidation, "callback is not a function");
- return false;
+ goto error;
}
cb.on_changedtick = v->data.luaref;
v->data.integer = LUA_NOREF;
+ } else if (is_lua && strequal("on_detach", k.data)) {
+ if (v->type != kObjectTypeLuaRef) {
+ api_set_error(err, kErrorTypeValidation, "callback is not a function");
+ goto error;
+ }
+ cb.on_detach = v->data.luaref;
+ v->data.integer = LUA_NOREF;
+ } else if (is_lua && strequal("utf_sizes", k.data)) {
+ if (v->type != kObjectTypeBoolean) {
+ api_set_error(err, kErrorTypeValidation, "utf_sizes must be boolean");
+ goto error;
+ }
+ cb.utf_sizes = v->data.boolean;
} else {
api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
- return false;
+ goto error;
}
}
return buf_updates_register(buf, channel_id, cb, send_buffer);
+
+error:
+ // TODO(bfredl): ASAN build should check that the ref table is empty?
+ executor_free_luaref(cb.on_lines);
+ executor_free_luaref(cb.on_changedtick);
+ executor_free_luaref(cb.on_detach);
+ return false;
}
/// Deactivates buffer-update events on the channel.
@@ -1161,6 +1185,30 @@ free_exit:
return 0;
}
+Dictionary nvim__buf_stats(Buffer buffer, Error *err)
+{
+ Dictionary rv = ARRAY_DICT_INIT;
+
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (!buf) {
+ return rv;
+ }
+
+ // Number of times the cached line was flushed.
+ // This should generally not increase while editing the same
+ // line in the same mode.
+ PUT(rv, "flush_count", INTEGER_OBJ(buf->flush_count));
+ // lnum of current line
+ PUT(rv, "current_lnum", INTEGER_OBJ(buf->b_ml.ml_line_lnum));
+ // whether the line has unflushed changes.
+ PUT(rv, "line_dirty", BOOLEAN_OBJ(buf->b_ml.ml_flags & ML_LINE_DIRTY));
+ // NB: this should be zero at any time API functions are called,
+ // this exists to debug issues
+ PUT(rv, "dirty_bytes", INTEGER_OBJ((Integer)buf->deleted_bytes));
+
+ return rv;
+}
+
// Check if deleting lines made the cursor position invalid.
// Changed lines from `lo` to `hi`; added `extra` lines (negative if deleted).
static void fix_cursor(linenr_T lo, linenr_T hi, linenr_T extra)
diff --git a/src/nvim/api/private/dispatch.h b/src/nvim/api/private/dispatch.h
index 39aabd708a..bad5a13934 100644
--- a/src/nvim/api/private/dispatch.h
+++ b/src/nvim/api/private/dispatch.h
@@ -11,8 +11,10 @@ typedef Object (*ApiDispatchWrapper)(uint64_t channel_id,
/// functions of this type.
typedef struct {
ApiDispatchWrapper fn;
- bool async; // function is always safe to run immediately instead of being
- // put in a request queue for handling when nvim waits for input.
+ bool fast; // Function is safe to be executed immediately while running the
+ // uv loop (the loop is run very frequently due to breakcheck).
+ // If "fast" is false, the function is deferred, i e the call will
+ // be put in the event queue, for safe handling later.
} MsgpackRpcRequestHandler;
#ifdef INCLUDE_GENERATED_DECLARATIONS
diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c
index 4f28ea5af3..20ed77afad 100644
--- a/src/nvim/api/ui.c
+++ b/src/nvim/api/ui.c
@@ -123,6 +123,7 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height,
ui->mode_change = remote_ui_mode_change;
ui->grid_scroll = remote_ui_grid_scroll;
ui->hl_attr_define = remote_ui_hl_attr_define;
+ ui->hl_group_set = remote_ui_hl_group_set;
ui->raw_line = remote_ui_raw_line;
ui->bell = remote_ui_bell;
ui->visual_bell = remote_ui_visual_bell;
diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h
index a1d25766fe..41bf0af65b 100644
--- a/src/nvim/api/ui_events.in.h
+++ b/src/nvim/api/ui_events.in.h
@@ -73,6 +73,8 @@ void default_colors_set(Integer rgb_fg, Integer rgb_bg, Integer rgb_sp,
void hl_attr_define(Integer id, HlAttrs rgb_attrs, HlAttrs cterm_attrs,
Array info)
FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_BRIDGE_IMPL;
+void hl_group_set(String name, Integer id)
+ FUNC_API_SINCE(6) FUNC_API_BRIDGE_IMPL;
void grid_resize(Integer grid, Integer width, Integer height)
FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL;
void grid_clear(Integer grid)
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 2e8ca384b4..ed6a28bcda 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -21,6 +21,7 @@
#include "nvim/lua/executor.h"
#include "nvim/vim.h"
#include "nvim/buffer.h"
+#include "nvim/context.h"
#include "nvim/file_search.h"
#include "nvim/highlight.h"
#include "nvim/window.h"
@@ -30,6 +31,7 @@
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
+#include "nvim/popupmnu.h"
#include "nvim/edit.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
@@ -208,7 +210,7 @@ void nvim_feedkeys(String keys, String mode, Boolean escape_csi)
/// @return Number of bytes actually written (can be fewer than
/// requested if the buffer becomes full).
Integer nvim_input(String keys)
- FUNC_API_SINCE(1) FUNC_API_ASYNC
+ FUNC_API_SINCE(1) FUNC_API_FAST
{
return (Integer)input_enqueue(keys);
}
@@ -237,7 +239,7 @@ Integer nvim_input(String keys)
/// @param[out] err Error details, if any
void nvim_input_mouse(String button, String action, String modifier,
Integer grid, Integer row, Integer col, Error *err)
- FUNC_API_SINCE(6) FUNC_API_ASYNC
+ FUNC_API_SINCE(6) FUNC_API_FAST
{
if (button.data == NULL || action.data == NULL) {
goto error;
@@ -1063,6 +1065,19 @@ fail:
/// - `external`: GUI should display the window as an external
/// top-level window. Currently accepts no other positioning
/// configuration together with this.
+/// - `style`: Configure the apparance of the window. Currently only takes
+/// one non-empty value:
+/// - "minimal" Nvim will display the window with many UI options
+/// disabled. This is useful when displaing a temporary
+/// float where the text should not be edited. Disables
+/// 'number', 'relativenumber', 'cursorline', 'cursorcolumn',
+/// 'spell' and 'list' options. 'signcolumn' is changed to
+/// `auto`. The end-of-buffer region is hidden by setting
+/// `eob` flag of 'fillchars' to a space char, and clearing
+/// the |EndOfBuffer| region in 'winhighlight'.
+///
+/// top-level window. Currently accepts no other positioning
+/// configuration together with this.
/// @param[out] err Error details, if any
///
/// @return Window handle, or 0 on error
@@ -1084,6 +1099,11 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dictionary config,
if (buffer > 0) {
nvim_win_set_buf(wp->handle, buffer, err);
}
+
+ if (fconfig.style == kWinStyleMinimal) {
+ win_set_minimal_style(wp);
+ didset_window_options(wp);
+ }
return wp->handle;
}
@@ -1249,13 +1269,74 @@ Dictionary nvim_get_color_map(void)
return colors;
}
+/// Gets a map of the current editor state.
+///
+/// @param types Context types ("regs", "jumps", "buflist", "gvars", ...)
+/// to gather, or NIL for all.
+///
+/// @return map of global context
+Dictionary nvim_get_context(Array types)
+ FUNC_API_SINCE(6)
+{
+ int int_types = 0;
+ if (types.size == 1 && types.items[0].type == kObjectTypeNil) {
+ int_types = kCtxAll;
+ } else {
+ for (size_t i = 0; i < types.size; i++) {
+ if (types.items[i].type == kObjectTypeString) {
+ const char *const current = types.items[i].data.string.data;
+ if (strequal(current, "regs")) {
+ int_types |= kCtxRegs;
+ } else if (strequal(current, "jumps")) {
+ int_types |= kCtxJumps;
+ } else if (strequal(current, "buflist")) {
+ int_types |= kCtxBuflist;
+ } else if (strequal(current, "gvars")) {
+ int_types |= kCtxGVars;
+ } else if (strequal(current, "sfuncs")) {
+ int_types |= kCtxSFuncs;
+ } else if (strequal(current, "funcs")) {
+ int_types |= kCtxFuncs;
+ }
+ }
+ }
+ }
+
+ Context ctx = CONTEXT_INIT;
+ ctx_save(&ctx, int_types);
+ Dictionary dict = ctx_to_dict(&ctx);
+ ctx_free(&ctx);
+ return dict;
+}
+
+/// Sets the current editor state to that in given context dictionary.
+///
+/// @param ctx_dict Context dictionary.
+Object nvim_load_context(Dictionary dict)
+ FUNC_API_SINCE(6)
+{
+ Context ctx = CONTEXT_INIT;
+
+ int save_did_emsg = did_emsg;
+ did_emsg = false;
+
+ ctx_from_dict(dict, &ctx);
+ if (!did_emsg) {
+ ctx_restore(&ctx, kCtxAll);
+ }
+
+ ctx_free(&ctx);
+
+ did_emsg = save_did_emsg;
+ return (Object)OBJECT_INIT;
+}
/// Gets the current mode. |mode()|
/// "blocking" is true if Nvim is waiting for input.
///
/// @returns Dictionary { "mode": String, "blocking": Boolean }
Dictionary nvim_get_mode(void)
- FUNC_API_SINCE(2) FUNC_API_ASYNC
+ FUNC_API_SINCE(2) FUNC_API_FAST
{
Dictionary rv = ARRAY_DICT_INIT;
char *modestr = get_mode();
@@ -1341,7 +1422,7 @@ Dictionary nvim_get_commands(Dictionary opts, Error *err)
///
/// @returns 2-tuple [{channel-id}, {api-metadata}]
Array nvim_get_api_info(uint64_t channel_id)
- FUNC_API_SINCE(1) FUNC_API_ASYNC FUNC_API_REMOTE_ONLY
+ FUNC_API_SINCE(1) FUNC_API_FAST FUNC_API_REMOTE_ONLY
{
Array rv = ARRAY_DICT_INIT;
@@ -1651,7 +1732,7 @@ typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack;
/// @param[out] err Error details, if any
Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight,
Error *err)
- FUNC_API_SINCE(4) FUNC_API_ASYNC
+ FUNC_API_SINCE(4) FUNC_API_FAST
{
int pflags = 0;
for (size_t i = 0 ; i < flags.size ; i++) {
@@ -2239,16 +2320,33 @@ void nvim_select_popupmenu_item(Integer item, Boolean insert, Boolean finish,
}
/// NB: if your UI doesn't use hlstate, this will not return hlstate first time
-Array nvim__inspect_cell(Integer row, Integer col, Error *err)
+Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Error *err)
{
Array ret = ARRAY_DICT_INIT;
- if (row < 0 || row >= default_grid.Rows
- || col < 0 || col >= default_grid.Columns) {
+
+ // TODO(bfredl): if grid == 0 we should read from the compositor's buffer.
+ // The only problem is that it does not yet exist.
+ ScreenGrid *g = &default_grid;
+ if (grid == pum_grid.handle) {
+ g = &pum_grid;
+ } else if (grid > 1) {
+ win_T *wp = get_win_by_grid_handle((handle_T)grid);
+ if (wp != NULL && wp->w_grid.chars != NULL) {
+ g = &wp->w_grid;
+ } else {
+ api_set_error(err, kErrorTypeValidation,
+ "No grid with the given handle");
+ return ret;
+ }
+ }
+
+ if (row < 0 || row >= g->Rows
+ || col < 0 || col >= g->Columns) {
return ret;
}
- size_t off = default_grid.line_offset[(size_t)row] + (size_t)col;
- ADD(ret, STRING_OBJ(cstr_to_string((char *)default_grid.chars[off])));
- int attr = default_grid.attrs[off];
+ size_t off = g->line_offset[(size_t)row] + (size_t)col;
+ ADD(ret, STRING_OBJ(cstr_to_string((char *)g->chars[off])));
+ int attr = g->attrs[off];
ADD(ret, DICTIONARY_OBJ(hl_get_attr_by_id(attr, true, err)));
// will not work first time
if (!highlight_use_hlstate()) {
diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c
index 9fd1818a5c..e279edebde 100644
--- a/src/nvim/api/window.c
+++ b/src/nvim/api/window.c
@@ -6,6 +6,8 @@
#include <stdlib.h>
#include <limits.h>
+#include "nvim/ascii.h"
+#include "nvim/globals.h"
#include "nvim/api/window.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
@@ -13,11 +15,11 @@
#include "nvim/vim.h"
#include "nvim/buffer.h"
#include "nvim/cursor.h"
+#include "nvim/option.h"
#include "nvim/window.h"
#include "nvim/screen.h"
#include "nvim/move.h"
-
/// Gets the current buffer in a window
///
/// @param window Window handle
@@ -475,6 +477,10 @@ void nvim_win_set_config(Window window, Dictionary config, Error *err)
win_config_float(win, fconfig);
win->w_pos_changed = true;
}
+ if (fconfig.style == kWinStyleMinimal) {
+ win_set_minimal_style(win);
+ didset_window_options(win);
+ }
}
/// Return window configuration.
@@ -521,9 +527,7 @@ Dictionary nvim_win_get_config(Window window, Error *err)
return rv;
}
-/// Close a window.
-///
-/// This is equivalent to |:close| with count except that it takes a window id.
+/// Closes the window (like |:close| with a |window-ID|).
///
/// @param window Window handle
/// @param force Behave like `:close!` The last window of a buffer with
@@ -537,8 +541,17 @@ void nvim_win_close(Window window, Boolean force, Error *err)
if (!win) {
return;
}
- tabpage_T *tabpage = win_find_tabpage(win);
+ if (cmdwin_type != 0) {
+ if (win == curwin) {
+ cmdwin_result = Ctrl_C;
+ } else {
+ api_set_error(err, kErrorTypeException, "%s", _(e_cmdwin));
+ }
+ return;
+ }
+
+ tabpage_T *tabpage = win_find_tabpage(win);
TryState tstate;
try_enter(&tstate);
ex_win_close(force, win, tabpage == curtab ? NULL : tabpage);
diff --git a/src/nvim/assert.h b/src/nvim/assert.h
index 34734f294d..1361879876 100644
--- a/src/nvim/assert.h
+++ b/src/nvim/assert.h
@@ -133,8 +133,10 @@
/// Alternative for compilers without __builtin_xx_overflow ?
/// https://stackoverflow.com/a/44830670/152142
///
-/// @param MAX Maximum value of the narrowest type of operand.
-/// Not used if compiler supports __builtin_add_overflow.
+/// @param a Operand 1.
+/// @param b Operand 2.
+/// @param c Where to store the result.
+/// @param t Result type. Not used if compiler supports __builtin_add_overflow.
#ifdef HAVE_BUILTIN_ADD_OVERFLOW
# define STRICT_ADD(a, b, c, t) \
do { \
@@ -150,6 +152,7 @@
/// @def STRICT_SUB
/// @brief Subtracts (a - b) and stores result in `c`. Aborts on overflow.
+/// @see STRICT_ADD
#ifdef HAVE_BUILTIN_ADD_OVERFLOW
# define STRICT_SUB(a, b, c, t) \
do { \
diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua
index ef528f72b8..12fc8fd02a 100644
--- a/src/nvim/auevents.lua
+++ b/src/nvim/auevents.lua
@@ -77,6 +77,7 @@ return {
'Signal', -- after nvim process received a signal
'SourceCmd', -- sourcing a Vim script using command
'SourcePre', -- before sourcing a Vim script
+ 'SourcePost', -- after sourcing a Vim script
'SpellFileMissing', -- spell file missing
'StdinReadPost', -- after reading from stdin
'StdinReadPre', -- before reading from stdin
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index 52dc359716..a545112360 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -29,8 +29,10 @@
#include "nvim/api/vim.h"
#include "nvim/ascii.h"
#include "nvim/assert.h"
+#include "nvim/channel.h"
#include "nvim/vim.h"
#include "nvim/buffer.h"
+#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/diff.h"
@@ -1425,7 +1427,7 @@ do_buffer(
}
}
if (bufIsChanged(curbuf)) {
- EMSG(_(e_nowrtmsg));
+ no_write_message();
return FAIL;
}
}
@@ -1626,6 +1628,16 @@ void do_autochdir(void)
}
}
+void no_write_message(void)
+{
+ EMSG(_("E37: No write since last change (add ! to override)"));
+}
+
+void no_write_message_nobang(void)
+{
+ EMSG(_("E37: No write since last change"));
+}
+
//
// functions for dealing with the buffer list
//
@@ -2532,6 +2544,11 @@ void get_winopts(buf_T *buf)
} else
copy_winopt(&curwin->w_allbuf_opt, &curwin->w_onebuf_opt);
+ if (curwin->w_float_config.style == kWinStyleMinimal) {
+ didset_window_options(curwin);
+ win_set_minimal_style(curwin);
+ }
+
// Set 'foldlevel' to 'foldlevelstart' if it's not negative.
if (p_fdls >= 0) {
curwin->w_p_fdl = p_fdls;
@@ -2595,14 +2612,23 @@ void buflist_list(exarg_T *eap)
continue;
}
+ const int changed_char = (buf->b_flags & BF_READERR)
+ ? 'x'
+ : (bufIsChanged(buf) ? '+' : ' ');
+ int ro_char = !MODIFIABLE(buf) ? '-' : (buf->b_p_ro ? '=' : ' ');
+ if (buf->terminal) {
+ ro_char = channel_job_running((uint64_t)buf->b_p_channel) ? 'R' : 'F';
+ }
+
msg_putchar('\n');
- len = vim_snprintf((char *)IObuff, IOSIZE - 20, "%3d%c%c%c%c%c \"%s\"",
+ len = vim_snprintf(
+ (char *)IObuff, IOSIZE - 20, "%3d%c%c%c%c%c \"%s\"",
buf->b_fnum,
buf->b_p_bl ? ' ' : 'u',
buf == curbuf ? '%' : (curwin->w_alt_fnum == buf->b_fnum ? '#' : ' '),
buf->b_ml.ml_mfp == NULL ? ' ' : (buf->b_nwindows == 0 ? 'h' : 'a'),
- !MODIFIABLE(buf) ? '-' : (buf->b_p_ro ? '=' : ' '),
- (buf->b_flags & BF_READERR) ? 'x' : (bufIsChanged(buf) ? '+' : ' '),
+ ro_char,
+ changed_char,
NameBuff);
if (len > IOSIZE - 20) {
@@ -3341,7 +3367,6 @@ int build_stl_str_hl(
char_u *usefmt = fmt;
const int save_must_redraw = must_redraw;
const int save_redr_type = curwin->w_redr_type;
- const int save_highlight_shcnaged = need_highlight_changed;
// When the format starts with "%!" then evaluate it as an expression and
// use the result as the actual format string.
@@ -4405,12 +4430,12 @@ int build_stl_str_hl(
cur_tab_rec->def.func = NULL;
}
- // We do not want redrawing a stausline, ruler, title, etc. to trigger
- // another redraw, it may cause an endless loop. This happens when a
- // statusline changes a highlight group.
- must_redraw = save_must_redraw;
- curwin->w_redr_type = save_redr_type;
- need_highlight_changed = save_highlight_shcnaged;
+ // When inside update_screen we do not want redrawing a stausline, ruler,
+ // title, etc. to trigger another redraw, it may cause an endless loop.
+ if (updating_screen) {
+ must_redraw = save_must_redraw;
+ curwin->w_redr_type = save_redr_type;
+ }
return width;
}
@@ -5147,18 +5172,21 @@ chk_modeline(
// Return true if "buf" is a help buffer.
bool bt_help(const buf_T *const buf)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
return buf != NULL && buf->b_help;
}
// Return true if "buf" is the quickfix buffer.
bool bt_quickfix(const buf_T *const buf)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
return buf != NULL && buf->b_p_bt[0] == 'q';
}
// Return true if "buf" is a terminal buffer.
bool bt_terminal(const buf_T *const buf)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
return buf != NULL && buf->b_p_bt[0] == 't';
}
@@ -5166,6 +5194,7 @@ bool bt_terminal(const buf_T *const buf)
// Return true if "buf" is a "nofile", "acwrite" or "terminal" buffer.
// This means the buffer name is not a file name.
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);
@@ -5173,11 +5202,13 @@ bool bt_nofile(const buf_T *const buf)
// 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);
}
bool bt_dontwrite_msg(const buf_T *const buf)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
if (bt_dontwrite(buf)) {
EMSG(_("E382: Cannot write, 'buftype' option is set"));
@@ -5223,8 +5254,8 @@ char_u *buf_spname(buf_T *buf)
// There is no _file_ when 'buftype' is "nofile", b_sfname
// contains the name as specified by the user.
if (bt_nofile(buf)) {
- if (buf->b_sfname != NULL) {
- return buf->b_sfname;
+ if (buf->b_fname != NULL) {
+ return buf->b_fname;
}
return (char_u *)_("[Scratch]");
}
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index 117a9183a4..b11eaefdd0 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -252,6 +252,8 @@ typedef struct {
# define w_p_fcs w_onebuf_opt.wo_fcs // 'fillchars'
char_u *wo_lcs;
# define w_p_lcs w_onebuf_opt.wo_lcs // 'listchars'
+ long wo_winbl;
+# define w_p_winbl w_onebuf_opt.wo_winbl // 'winblend'
LastSet wo_scriptID[WV_COUNT]; // SIDs for window-local options
# define w_p_scriptID w_onebuf_opt.wo_scriptID
@@ -456,8 +458,10 @@ typedef TV_DICTITEM_STRUCT(sizeof("changedtick")) ChangedtickDictItem;
typedef struct {
LuaRef on_lines;
LuaRef on_changedtick;
+ LuaRef on_detach;
+ bool utf_sizes;
} BufUpdateCallbacks;
-#define BUF_UPDATE_CALLBACKS_INIT { LUA_NOREF, LUA_NOREF }
+#define BUF_UPDATE_CALLBACKS_INIT { LUA_NOREF, LUA_NOREF, LUA_NOREF, false }
#define BUF_HAS_QF_ENTRY 1
#define BUF_HAS_LL_ENTRY 2
@@ -799,11 +803,26 @@ struct file_buffer {
kvec_t(BufhlLine *) b_bufhl_move_space; // temporary space for highlights
- // array of channelids which have asked to receive updates for this
+ // array of channel_id:s which have asked to receive updates for this
// buffer.
kvec_t(uint64_t) update_channels;
+ // array of lua callbacks for buffer updates.
kvec_t(BufUpdateCallbacks) update_callbacks;
+ // whether an update callback has requested codepoint size of deleted regions.
+ bool update_need_codepoints;
+
+ // Measurements of the deleted or replaced region since the last update
+ // event. Some consumers of buffer changes need to know the byte size (like
+ // tree-sitter) or the corresponding UTF-32/UTF-16 size (like LSP) of the
+ // deleted text.
+ size_t deleted_bytes;
+ size_t deleted_codepoints;
+ size_t deleted_codeunits;
+
+ // The number for times the current line has been flushed in the memline.
+ int flush_count;
+
int b_diff_failed; // internal diff failed for this buffer
};
@@ -969,7 +988,6 @@ struct matchitem {
};
typedef int FloatAnchor;
-typedef int FloatRelative;
enum {
kFloatAnchorEast = 1,
@@ -982,15 +1000,20 @@ enum {
// SE -> kFloatAnchorSouth | kFloatAnchorEast
EXTERN const char *const float_anchor_str[] INIT(= { "NW", "NE", "SW", "SE" });
-enum {
+typedef enum {
kFloatRelativeEditor = 0,
kFloatRelativeWindow = 1,
kFloatRelativeCursor = 2,
-};
+} FloatRelative;
EXTERN const char *const float_relative_str[] INIT(= { "editor", "window",
"cursor" });
+typedef enum {
+ kWinStyleUnused = 0,
+ kWinStyleMinimal, /// Minimal UI: no number column, eob markers, etc
+} WinStyle;
+
typedef struct {
Window window;
int height, width;
@@ -999,12 +1022,14 @@ typedef struct {
FloatRelative relative;
bool external;
bool focusable;
+ WinStyle style;
} FloatConfig;
#define FLOAT_CONFIG_INIT ((FloatConfig){ .height = 0, .width = 0, \
.row = 0, .col = 0, .anchor = 0, \
.relative = 0, .external = false, \
- .focusable = true })
+ .focusable = true, \
+ .style = kWinStyleUnused })
// Structure to store last cursor position and topline. Used by check_lnums()
// and reset_lnums().
diff --git a/src/nvim/buffer_updates.c b/src/nvim/buffer_updates.c
index 2515e3f8aa..3604578b50 100644
--- a/src/nvim/buffer_updates.c
+++ b/src/nvim/buffer_updates.c
@@ -26,6 +26,9 @@ bool buf_updates_register(buf_T *buf, uint64_t channel_id,
if (channel_id == LUA_INTERNAL_CALL) {
kv_push(buf->update_callbacks, cb);
+ if (cb.utf_sizes) {
+ buf->update_need_codepoints = true;
+ }
return true;
}
@@ -143,7 +146,21 @@ void buf_updates_unregister_all(buf_T *buf)
}
for (size_t i = 0; i < kv_size(buf->update_callbacks); i++) {
- free_update_callbacks(kv_A(buf->update_callbacks, i));
+ BufUpdateCallbacks cb = kv_A(buf->update_callbacks, i);
+ if (cb.on_detach != LUA_NOREF) {
+ Array args = ARRAY_DICT_INIT;
+ Object items[1];
+ args.size = 1;
+ args.items = items;
+
+ // the first argument is always the buffer handle
+ args.items[0] = BUFFER_OBJ(buf->handle);
+
+ textlock++;
+ executor_exec_lua_cb(cb.on_detach, "detach", args, false);
+ textlock--;
+ }
+ free_update_callbacks(cb);
}
kv_destroy(buf->update_callbacks);
kv_init(buf->update_callbacks);
@@ -155,6 +172,10 @@ void buf_updates_send_changes(buf_T *buf,
int64_t num_removed,
bool send_tick)
{
+ size_t deleted_codepoints, deleted_codeunits;
+ size_t deleted_bytes = ml_flush_deleted_bytes(buf, &deleted_codepoints,
+ &deleted_codeunits);
+
if (!buf_updates_active(buf)) {
return;
}
@@ -217,8 +238,8 @@ void buf_updates_send_changes(buf_T *buf,
bool keep = true;
if (cb.on_lines != LUA_NOREF) {
Array args = ARRAY_DICT_INIT;
- Object items[5];
- args.size = 5;
+ Object items[8];
+ args.size = 6; // may be increased to 8 below
args.items = items;
// the first argument is always the buffer handle
@@ -236,14 +257,22 @@ void buf_updates_send_changes(buf_T *buf,
// the last line in the updated range
args.items[4] = INTEGER_OBJ(firstline - 1 + num_added);
+ // byte count of previous contents
+ args.items[5] = INTEGER_OBJ((Integer)deleted_bytes);
+ if (cb.utf_sizes) {
+ args.size = 8;
+ args.items[6] = INTEGER_OBJ((Integer)deleted_codepoints);
+ args.items[7] = INTEGER_OBJ((Integer)deleted_codeunits);
+ }
textlock++;
- Object res = executor_exec_lua_cb(cb.on_lines, "lines", args);
+ Object res = executor_exec_lua_cb(cb.on_lines, "lines", args, true);
textlock--;
if (res.type == kObjectTypeBoolean && res.data.boolean == true) {
free_update_callbacks(cb);
keep = false;
}
+ api_free_object(res);
}
if (keep) {
kv_A(buf->update_callbacks, j++) = kv_A(buf->update_callbacks, i);
@@ -276,13 +305,15 @@ void buf_updates_changedtick(buf_T *buf)
args.items[1] = INTEGER_OBJ(buf_get_changedtick(buf));
textlock++;
- Object res = executor_exec_lua_cb(cb.on_changedtick, "changedtick", args);
+ Object res = executor_exec_lua_cb(cb.on_changedtick, "changedtick",
+ args, true);
textlock--;
if (res.type == kObjectTypeBoolean && res.data.boolean == true) {
free_update_callbacks(cb);
keep = false;
}
+ api_free_object(res);
}
if (keep) {
kv_A(buf->update_callbacks, j++) = kv_A(buf->update_callbacks, i);
diff --git a/src/nvim/change.c b/src/nvim/change.c
new file mode 100644
index 0000000000..2363578139
--- /dev/null
+++ b/src/nvim/change.c
@@ -0,0 +1,1798 @@
+// 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
+
+/// change.c: functions related to changing text
+
+#include "nvim/assert.h"
+#include "nvim/buffer.h"
+#include "nvim/buffer_updates.h"
+#include "nvim/change.h"
+#include "nvim/charset.h"
+#include "nvim/cursor.h"
+#include "nvim/diff.h"
+#include "nvim/edit.h"
+#include "nvim/eval.h"
+#include "nvim/fileio.h"
+#include "nvim/fold.h"
+#include "nvim/indent.h"
+#include "nvim/indent_c.h"
+#include "nvim/mark.h"
+#include "nvim/memline.h"
+#include "nvim/misc1.h"
+#include "nvim/move.h"
+#include "nvim/option.h"
+#include "nvim/screen.h"
+#include "nvim/search.h"
+#include "nvim/state.h"
+#include "nvim/ui.h"
+#include "nvim/undo.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "change.c.generated.h"
+#endif
+
+/// If the file is readonly, give a warning message with the first change.
+/// Don't do this for autocommands.
+/// Doesn't use emsg(), because it flushes the macro buffer.
+/// If we have undone all changes b_changed will be false, but "b_did_warn"
+/// will be true.
+/// "col" is the column for the message; non-zero when in insert mode and
+/// 'showmode' is on.
+/// Careful: may trigger autocommands that reload the buffer.
+void change_warning(int col)
+{
+ static char *w_readonly = N_("W10: Warning: Changing a readonly file");
+
+ if (curbuf->b_did_warn == false
+ && curbufIsChanged() == 0
+ && !autocmd_busy
+ && curbuf->b_p_ro) {
+ curbuf_lock++;
+ apply_autocmds(EVENT_FILECHANGEDRO, NULL, NULL, false, curbuf);
+ curbuf_lock--;
+ if (!curbuf->b_p_ro) {
+ return;
+ }
+ // Do what msg() does, but with a column offset if the warning should
+ // be after the mode message.
+ msg_start();
+ if (msg_row == Rows - 1) {
+ msg_col = col;
+ }
+ msg_source(HL_ATTR(HLF_W));
+ msg_ext_set_kind("wmsg");
+ MSG_PUTS_ATTR(_(w_readonly), HL_ATTR(HLF_W) | MSG_HIST);
+ set_vim_var_string(VV_WARNINGMSG, _(w_readonly), -1);
+ msg_clr_eos();
+ (void)msg_end();
+ if (msg_silent == 0 && !silent_mode && ui_active()) {
+ ui_flush();
+ os_delay(1000L, true); // give the user time to think about it
+ }
+ curbuf->b_did_warn = true;
+ redraw_cmdline = false; // don't redraw and erase the message
+ if (msg_row < Rows - 1) {
+ showmode();
+ }
+ }
+}
+
+/// Call this function when something in the current buffer is changed.
+///
+/// Most often called through changed_bytes() and changed_lines(), which also
+/// mark the area of the display to be redrawn.
+///
+/// Careful: may trigger autocommands that reload the buffer.
+void changed(void)
+{
+ if (!curbuf->b_changed) {
+ int save_msg_scroll = msg_scroll;
+
+ // Give a warning about changing a read-only file. This may also
+ // check-out the file, thus change "curbuf"!
+ change_warning(0);
+
+ // Create a swap file if that is wanted.
+ // Don't do this for "nofile" and "nowrite" buffer types.
+ if (curbuf->b_may_swap
+ && !bt_dontwrite(curbuf)
+ ) {
+ int save_need_wait_return = need_wait_return;
+
+ need_wait_return = false;
+ ml_open_file(curbuf);
+
+ // The ml_open_file() can cause an ATTENTION message.
+ // Wait two seconds, to make sure the user reads this unexpected
+ // message. Since we could be anywhere, call wait_return() now,
+ // and don't let the emsg() set msg_scroll.
+ if (need_wait_return && emsg_silent == 0) {
+ ui_flush();
+ os_delay(2000L, true);
+ wait_return(true);
+ msg_scroll = save_msg_scroll;
+ } else {
+ need_wait_return = save_need_wait_return;
+ }
+ }
+ changed_internal();
+ }
+ buf_inc_changedtick(curbuf);
+
+ // If a pattern is highlighted, the position may now be invalid.
+ highlight_match = false;
+}
+
+/// Internal part of changed(), no user interaction.
+/// Also used for recovery.
+void changed_internal(void)
+{
+ curbuf->b_changed = true;
+ ml_setflags(curbuf);
+ check_status(curbuf);
+ redraw_tabline = true;
+ need_maketitle = true; // set window title later
+}
+
+/// Common code for when a change was made.
+/// See changed_lines() for the arguments.
+/// Careful: may trigger autocommands that reload the buffer.
+static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume,
+ long xtra)
+{
+ int i;
+ int cols;
+ pos_T *p;
+ int add;
+
+ // mark the buffer as modified
+ changed();
+
+ if (curwin->w_p_diff && diff_internal()) {
+ curtab->tp_diff_update = true;
+ }
+
+ // set the '. mark
+ if (!cmdmod.keepjumps) {
+ RESET_FMARK(&curbuf->b_last_change, ((pos_T) { lnum, col, 0 }), 0);
+
+ // Create a new entry if a new undo-able change was started or we
+ // don't have an entry yet.
+ if (curbuf->b_new_change || curbuf->b_changelistlen == 0) {
+ if (curbuf->b_changelistlen == 0) {
+ add = true;
+ } else {
+ // Don't create a new entry when the line number is the same
+ // as the last one and the column is not too far away. Avoids
+ // creating many entries for typing "xxxxx".
+ p = &curbuf->b_changelist[curbuf->b_changelistlen - 1].mark;
+ if (p->lnum != lnum) {
+ add = true;
+ } else {
+ cols = comp_textwidth(false);
+ if (cols == 0) {
+ cols = 79;
+ }
+ add = (p->col + cols < col || col + cols < p->col);
+ }
+ }
+ if (add) {
+ // This is the first of a new sequence of undo-able changes
+ // and it's at some distance of the last change. Use a new
+ // position in the changelist.
+ curbuf->b_new_change = false;
+
+ if (curbuf->b_changelistlen == JUMPLISTSIZE) {
+ // changelist is full: remove oldest entry
+ curbuf->b_changelistlen = JUMPLISTSIZE - 1;
+ memmove(curbuf->b_changelist, curbuf->b_changelist + 1,
+ sizeof(curbuf->b_changelist[0]) * (JUMPLISTSIZE - 1));
+ FOR_ALL_TAB_WINDOWS(tp, wp) {
+ // Correct position in changelist for other windows on
+ // this buffer.
+ if (wp->w_buffer == curbuf && wp->w_changelistidx > 0) {
+ wp->w_changelistidx--;
+ }
+ }
+ }
+ FOR_ALL_TAB_WINDOWS(tp, wp) {
+ // For other windows, if the position in the changelist is
+ // at the end it stays at the end.
+ if (wp->w_buffer == curbuf
+ && wp->w_changelistidx == curbuf->b_changelistlen) {
+ wp->w_changelistidx++;
+ }
+ }
+ curbuf->b_changelistlen++;
+ }
+ }
+ curbuf->b_changelist[curbuf->b_changelistlen - 1] =
+ curbuf->b_last_change;
+ // The current window is always after the last change, so that "g,"
+ // takes you back to it.
+ curwin->w_changelistidx = curbuf->b_changelistlen;
+ }
+
+ FOR_ALL_TAB_WINDOWS(tp, wp) {
+ if (wp->w_buffer == curbuf) {
+ // Mark this window to be redrawn later.
+ if (wp->w_redr_type < VALID) {
+ wp->w_redr_type = VALID;
+ }
+
+ // Check if a change in the buffer has invalidated the cached
+ // values for the cursor.
+ // Update the folds for this window. Can't postpone this, because
+ // a following operator might work on the whole fold: ">>dd".
+ foldUpdate(wp, lnum, lnume + xtra - 1);
+
+ // The change may cause lines above or below the change to become
+ // included in a fold. Set lnum/lnume to the first/last line that
+ // might be displayed differently.
+ // Set w_cline_folded here as an efficient way to update it when
+ // inserting lines just above a closed fold. */
+ bool folded = hasFoldingWin(wp, lnum, &lnum, NULL, false, NULL);
+ if (wp->w_cursor.lnum == lnum) {
+ wp->w_cline_folded = folded;
+ }
+ folded = hasFoldingWin(wp, lnume, NULL, &lnume, false, NULL);
+ if (wp->w_cursor.lnum == lnume) {
+ wp->w_cline_folded = folded;
+ }
+
+ // If the changed line is in a range of previously folded lines,
+ // compare with the first line in that range.
+ if (wp->w_cursor.lnum <= lnum) {
+ i = find_wl_entry(wp, lnum);
+ if (i >= 0 && wp->w_cursor.lnum > wp->w_lines[i].wl_lnum) {
+ changed_line_abv_curs_win(wp);
+ }
+ }
+
+ if (wp->w_cursor.lnum > lnum) {
+ changed_line_abv_curs_win(wp);
+ } else if (wp->w_cursor.lnum == lnum && wp->w_cursor.col >= col) {
+ changed_cline_bef_curs_win(wp);
+ }
+ if (wp->w_botline >= lnum) {
+ // Assume that botline doesn't change (inserted lines make
+ // other lines scroll down below botline).
+ approximate_botline_win(wp);
+ }
+
+ // Check if any w_lines[] entries have become invalid.
+ // For entries below the change: Correct the lnums for
+ // inserted/deleted lines. Makes it possible to stop displaying
+ // after the change.
+ for (i = 0; i < wp->w_lines_valid; i++) {
+ if (wp->w_lines[i].wl_valid) {
+ if (wp->w_lines[i].wl_lnum >= lnum) {
+ if (wp->w_lines[i].wl_lnum < lnume) {
+ // line included in change
+ wp->w_lines[i].wl_valid = false;
+ } else if (xtra != 0) {
+ // line below change
+ wp->w_lines[i].wl_lnum += xtra;
+ wp->w_lines[i].wl_lastlnum += xtra;
+ }
+ } else if (wp->w_lines[i].wl_lastlnum >= lnum) {
+ // change somewhere inside this range of folded lines,
+ // may need to be redrawn
+ wp->w_lines[i].wl_valid = false;
+ }
+ }
+ }
+
+ // Take care of side effects for setting w_topline when folds have
+ // changed. Esp. when the buffer was changed in another window.
+ if (hasAnyFolding(wp)) {
+ set_topline(wp, wp->w_topline);
+ }
+
+ // relative numbering may require updating more
+ if (wp->w_p_rnu) {
+ redraw_win_later(wp, SOME_VALID);
+ }
+ }
+ }
+
+ // Call update_screen() later, which checks out what needs to be redrawn,
+ // since it notices b_mod_set and then uses b_mod_*.
+ if (must_redraw < VALID) {
+ must_redraw = VALID;
+ }
+
+ // when the cursor line is changed always trigger CursorMoved
+ if (lnum <= curwin->w_cursor.lnum
+ && lnume + (xtra < 0 ? -xtra : xtra) > curwin->w_cursor.lnum) {
+ curwin->w_last_cursormoved.lnum = 0;
+ }
+}
+
+static void changedOneline(buf_T *buf, linenr_T lnum)
+{
+ if (buf->b_mod_set) {
+ // find the maximum area that must be redisplayed
+ if (lnum < buf->b_mod_top) {
+ buf->b_mod_top = lnum;
+ } else if (lnum >= buf->b_mod_bot) {
+ buf->b_mod_bot = lnum + 1;
+ }
+ } else {
+ // set the area that must be redisplayed to one line
+ buf->b_mod_set = true;
+ buf->b_mod_top = lnum;
+ buf->b_mod_bot = lnum + 1;
+ buf->b_mod_xlines = 0;
+ }
+}
+
+/// Changed bytes within a single line for the current buffer.
+/// - marks the windows on this buffer to be redisplayed
+/// - marks the buffer changed by calling changed()
+/// - invalidates cached values
+/// Careful: may trigger autocommands that reload the buffer.
+void changed_bytes(linenr_T lnum, colnr_T col)
+{
+ changedOneline(curbuf, lnum);
+ changed_common(lnum, col, lnum + 1, 0L);
+ // notify any channels that are watching
+ buf_updates_send_changes(curbuf, lnum, 1, 1, true);
+
+ // Diff highlighting in other diff windows may need to be updated too.
+ if (curwin->w_p_diff) {
+ linenr_T wlnum;
+
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (wp->w_p_diff && wp != curwin) {
+ redraw_win_later(wp, VALID);
+ wlnum = diff_lnum_win(lnum, wp);
+ if (wlnum > 0) {
+ changedOneline(wp->w_buffer, wlnum);
+ }
+ }
+ }
+ }
+}
+
+/// Appended "count" lines below line "lnum" in the current buffer.
+/// Must be called AFTER the change and after mark_adjust().
+/// Takes care of marking the buffer to be redrawn and sets the changed flag.
+void appended_lines(linenr_T lnum, long count)
+{
+ changed_lines(lnum + 1, 0, lnum + 1, count, true);
+}
+
+/// Like appended_lines(), but adjust marks first.
+void appended_lines_mark(linenr_T lnum, long count)
+{
+ // Skip mark_adjust when adding a line after the last one, there can't
+ // be marks there. But it's still needed in diff mode.
+ if (lnum + count < curbuf->b_ml.ml_line_count || curwin->w_p_diff) {
+ mark_adjust(lnum + 1, (linenr_T)MAXLNUM, count, 0L, false);
+ }
+ changed_lines(lnum + 1, 0, lnum + 1, count, true);
+}
+
+/// Deleted "count" lines at line "lnum" in the current buffer.
+/// Must be called AFTER the change and after mark_adjust().
+/// Takes care of marking the buffer to be redrawn and sets the changed flag.
+void deleted_lines(linenr_T lnum, long count)
+{
+ changed_lines(lnum, 0, lnum + count, -count, true);
+}
+
+/// Like deleted_lines(), but adjust marks first.
+/// Make sure the cursor is on a valid line before calling, a GUI callback may
+/// be triggered to display the cursor.
+void deleted_lines_mark(linenr_T lnum, long count)
+{
+ mark_adjust(lnum, (linenr_T)(lnum + count - 1), (long)MAXLNUM, -count, false);
+ changed_lines(lnum, 0, lnum + count, -count, true);
+}
+
+/// Marks the area to be redrawn after a change.
+///
+/// @param buf the buffer where lines were changed
+/// @param lnum first line with change
+/// @param lnume line below last changed line
+/// @param xtra number of extra lines (negative when deleting)
+void changed_lines_buf(buf_T *buf, linenr_T lnum, linenr_T lnume, long xtra)
+{
+ if (buf->b_mod_set) {
+ // find the maximum area that must be redisplayed
+ if (lnum < buf->b_mod_top) {
+ buf->b_mod_top = lnum;
+ }
+ if (lnum < buf->b_mod_bot) {
+ // adjust old bot position for xtra lines
+ buf->b_mod_bot += xtra;
+ if (buf->b_mod_bot < lnum) {
+ buf->b_mod_bot = lnum;
+ }
+ }
+ if (lnume + xtra > buf->b_mod_bot) {
+ buf->b_mod_bot = lnume + xtra;
+ }
+ buf->b_mod_xlines += xtra;
+ } else {
+ // set the area that must be redisplayed
+ buf->b_mod_set = true;
+ buf->b_mod_top = lnum;
+ buf->b_mod_bot = lnume + xtra;
+ buf->b_mod_xlines = xtra;
+ }
+}
+
+/// Changed lines for the current buffer.
+/// Must be called AFTER the change and after mark_adjust().
+/// - mark the buffer changed by calling changed()
+/// - mark the windows on this buffer to be redisplayed
+/// - invalidate cached values
+/// "lnum" is the first line that needs displaying, "lnume" the first line
+/// below the changed lines (BEFORE the change).
+/// When only inserting lines, "lnum" and "lnume" are equal.
+/// Takes care of calling changed() and updating b_mod_*.
+/// Careful: may trigger autocommands that reload the buffer.
+void
+changed_lines(
+ linenr_T lnum, // first line with change
+ colnr_T col, // column in first line with change
+ linenr_T lnume, // line below last changed line
+ long xtra, // number of extra lines (negative when deleting)
+ bool do_buf_event // some callers like undo/redo call changed_lines()
+ // and then increment changedtick *again*. This flag
+ // allows these callers to send the nvim_buf_lines_event
+ // events after they're done modifying changedtick.
+)
+{
+ changed_lines_buf(curbuf, lnum, lnume, xtra);
+
+ if (xtra == 0 && curwin->w_p_diff && !diff_internal()) {
+ // When the number of lines doesn't change then mark_adjust() isn't
+ // called and other diff buffers still need to be marked for
+ // displaying.
+ linenr_T wlnum;
+
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (wp->w_p_diff && wp != curwin) {
+ redraw_win_later(wp, VALID);
+ wlnum = diff_lnum_win(lnum, wp);
+ if (wlnum > 0) {
+ changed_lines_buf(wp->w_buffer, wlnum,
+ lnume - lnum + wlnum, 0L);
+ }
+ }
+ }
+ }
+
+ changed_common(lnum, col, lnume, xtra);
+
+ if (do_buf_event) {
+ int64_t num_added = (int64_t)(lnume + xtra - lnum);
+ int64_t num_removed = lnume - lnum;
+ buf_updates_send_changes(curbuf, lnum, num_added, num_removed, true);
+ }
+}
+
+/// Called when the changed flag must be reset for buffer "buf".
+/// When "ff" is true also reset 'fileformat'.
+void unchanged(buf_T *buf, int ff)
+{
+ if (buf->b_changed || (ff && file_ff_differs(buf, false))) {
+ buf->b_changed = false;
+ ml_setflags(buf);
+ if (ff) {
+ save_file_ff(buf);
+ }
+ check_status(buf);
+ redraw_tabline = true;
+ need_maketitle = true; // set window title later
+ }
+ buf_inc_changedtick(buf);
+}
+
+/// Insert string "p" at the cursor position. Stops at a NUL byte.
+/// Handles Replace mode and multi-byte characters.
+void ins_bytes(char_u *p)
+{
+ ins_bytes_len(p, STRLEN(p));
+}
+
+/// Insert string "p" with length "len" at the cursor position.
+/// Handles Replace mode and multi-byte characters.
+void ins_bytes_len(char_u *p, size_t len)
+{
+ size_t n;
+ for (size_t i = 0; i < len; i += n) {
+ if (enc_utf8) {
+ // avoid reading past p[len]
+ n = (size_t)utfc_ptr2len_len(p + i, (int)(len - i));
+ } else {
+ n = (size_t)(*mb_ptr2len)(p + i);
+ }
+ ins_char_bytes(p + i, n);
+ }
+}
+
+/// Insert or replace a single character at the cursor position.
+/// When in REPLACE or VREPLACE mode, replace any existing character.
+/// Caller must have prepared for undo.
+/// For multi-byte characters we get the whole character, the caller must
+/// convert bytes to a character.
+void ins_char(int c)
+{
+ char_u buf[MB_MAXBYTES + 1];
+ size_t n = (size_t)utf_char2bytes(c, buf);
+
+ // When "c" is 0x100, 0x200, etc. we don't want to insert a NUL byte.
+ // Happens for CTRL-Vu9900.
+ if (buf[0] == 0) {
+ buf[0] = '\n';
+ }
+ ins_char_bytes(buf, n);
+}
+
+void ins_char_bytes(char_u *buf, size_t charlen)
+{
+ // Break tabs if needed.
+ if (virtual_active() && curwin->w_cursor.coladd > 0) {
+ coladvance_force(getviscol());
+ }
+
+ size_t col = (size_t)curwin->w_cursor.col;
+ linenr_T lnum = curwin->w_cursor.lnum;
+ char_u *oldp = ml_get(lnum);
+ size_t linelen = STRLEN(oldp) + 1; // length of old line including NUL
+
+ // The lengths default to the values for when not replacing.
+ size_t oldlen = 0; // nr of bytes inserted
+ size_t newlen = charlen; // nr of bytes deleted (0 when not replacing)
+
+ if (State & REPLACE_FLAG) {
+ if (State & VREPLACE_FLAG) {
+ // Disable 'list' temporarily, unless 'cpo' contains the 'L' flag.
+ // Returns the old value of list, so when finished,
+ // curwin->w_p_list should be set back to this.
+ int old_list = curwin->w_p_list;
+ if (old_list && vim_strchr(p_cpo, CPO_LISTWM) == NULL) {
+ curwin->w_p_list = false;
+ }
+ // In virtual replace mode each character may replace one or more
+ // characters (zero if it's a TAB). Count the number of bytes to
+ // be deleted to make room for the new character, counting screen
+ // cells. May result in adding spaces to fill a gap.
+ colnr_T vcol;
+ getvcol(curwin, &curwin->w_cursor, NULL, &vcol, NULL);
+ colnr_T new_vcol = vcol + chartabsize(buf, vcol);
+ while (oldp[col + oldlen] != NUL && vcol < new_vcol) {
+ vcol += chartabsize(oldp + col + oldlen, vcol);
+ // Don't need to remove a TAB that takes us to the right
+ // position.
+ if (vcol > new_vcol && oldp[col + oldlen] == TAB) {
+ break;
+ }
+ oldlen += (size_t)(*mb_ptr2len)(oldp + col + oldlen);
+ // Deleted a bit too much, insert spaces.
+ if (vcol > new_vcol) {
+ newlen += (size_t)(vcol - new_vcol);
+ }
+ }
+ curwin->w_p_list = old_list;
+ } else if (oldp[col] != NUL) {
+ // normal replace
+ oldlen = (size_t)(*mb_ptr2len)(oldp + col);
+ }
+
+
+ // Push the replaced bytes onto the replace stack, so that they can be
+ // put back when BS is used. The bytes of a multi-byte character are
+ // done the other way around, so that the first byte is popped off
+ // first (it tells the byte length of the character).
+ replace_push(NUL);
+ for (size_t i = 0; i < oldlen; i++) {
+ i += (size_t)replace_push_mb(oldp + col + i) - 1;
+ }
+ }
+
+ char_u *newp = xmalloc((size_t)(linelen + newlen - oldlen));
+
+ // Copy bytes before the cursor.
+ if (col > 0) {
+ memmove(newp, oldp, (size_t)col);
+ }
+
+ // Copy bytes after the changed character(s).
+ char_u *p = newp + col;
+ if (linelen > col + oldlen) {
+ memmove(p + newlen, oldp + col + oldlen,
+ (size_t)(linelen - col - oldlen));
+ }
+
+ // Insert or overwrite the new character.
+ memmove(p, buf, charlen);
+
+ // Fill with spaces when necessary.
+ for (size_t i = charlen; i < newlen; i++) {
+ p[i] = ' ';
+ }
+
+ // Replace the line in the buffer.
+ ml_replace(lnum, newp, false);
+
+ // mark the buffer as changed and prepare for displaying
+ changed_bytes(lnum, (colnr_T)col);
+
+ // If we're in Insert or Replace mode and 'showmatch' is set, then briefly
+ // show the match for right parens and braces.
+ if (p_sm && (State & INSERT)
+ && msg_silent == 0
+ && !ins_compl_active()
+ ) {
+ showmatch(utf_ptr2char(buf));
+ }
+
+ if (!p_ri || (State & REPLACE_FLAG)) {
+ // Normal insert: move cursor right
+ curwin->w_cursor.col += (int)charlen;
+ }
+ // TODO(Bram): should try to update w_row here, to avoid recomputing it later.
+}
+
+/// Insert a string at the cursor position.
+/// Note: Does NOT handle Replace mode.
+/// Caller must have prepared for undo.
+void ins_str(char_u *s)
+{
+ char_u *oldp, *newp;
+ int newlen = (int)STRLEN(s);
+ int oldlen;
+ colnr_T col;
+ linenr_T lnum = curwin->w_cursor.lnum;
+
+ if (virtual_active() && curwin->w_cursor.coladd > 0) {
+ coladvance_force(getviscol());
+ }
+
+ col = curwin->w_cursor.col;
+ oldp = ml_get(lnum);
+ oldlen = (int)STRLEN(oldp);
+
+ newp = (char_u *)xmalloc((size_t)oldlen + (size_t)newlen + 1);
+ if (col > 0) {
+ memmove(newp, oldp, (size_t)col);
+ }
+ memmove(newp + col, s, (size_t)newlen);
+ int bytes = oldlen - col + 1;
+ assert(bytes >= 0);
+ memmove(newp + col + newlen, oldp + col, (size_t)bytes);
+ ml_replace(lnum, newp, false);
+ changed_bytes(lnum, col);
+ curwin->w_cursor.col += newlen;
+}
+
+// Delete one character under the cursor.
+// If "fixpos" is true, don't leave the cursor on the NUL after the line.
+// Caller must have prepared for undo.
+//
+// return FAIL for failure, OK otherwise
+int del_char(bool fixpos)
+{
+ // Make sure the cursor is at the start of a character.
+ mb_adjust_cursor();
+ if (*get_cursor_pos_ptr() == NUL) {
+ return FAIL;
+ }
+ return del_chars(1L, fixpos);
+}
+
+/// Like del_bytes(), but delete characters instead of bytes.
+int del_chars(long count, int fixpos)
+{
+ int bytes = 0;
+ long i;
+ char_u *p;
+ int l;
+
+ p = get_cursor_pos_ptr();
+ for (i = 0; i < count && *p != NUL; i++) {
+ l = (*mb_ptr2len)(p);
+ bytes += l;
+ p += l;
+ }
+ return del_bytes(bytes, fixpos, true);
+}
+
+/// Delete "count" bytes under the cursor.
+/// If "fixpos" is true, don't leave the cursor on the NUL after the line.
+/// Caller must have prepared for undo.
+///
+/// @param count number of bytes to be deleted
+/// @param fixpos_arg leave the cursor on the NUL after the line
+/// @param use_delcombine 'delcombine' option applies
+///
+/// @return FAIL for failure, OK otherwise
+int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine)
+{
+ linenr_T lnum = curwin->w_cursor.lnum;
+ colnr_T col = curwin->w_cursor.col;
+ bool fixpos = fixpos_arg;
+ char_u *oldp = ml_get(lnum);
+ colnr_T oldlen = (colnr_T)STRLEN(oldp);
+
+ // Can't do anything when the cursor is on the NUL after the line.
+ if (col >= oldlen) {
+ return FAIL;
+ }
+ // If "count" is zero there is nothing to do.
+ if (count == 0) {
+ return OK;
+ }
+ // If "count" is negative the caller must be doing something wrong.
+ if (count < 1) {
+ IEMSGN("E950: Invalid count for del_bytes(): %ld", count);
+ return FAIL;
+ }
+
+ // If 'delcombine' is set and deleting (less than) one character, only
+ // delete the last combining character.
+ if (p_deco && use_delcombine && enc_utf8
+ && utfc_ptr2len(oldp + col) >= count) {
+ int cc[MAX_MCO];
+ int n;
+
+ (void)utfc_ptr2char(oldp + col, cc);
+ if (cc[0] != NUL) {
+ // Find the last composing char, there can be several.
+ n = col;
+ do {
+ col = n;
+ count = utf_ptr2len(oldp + n);
+ n += count;
+ } while (UTF_COMPOSINGLIKE(oldp + col, oldp + n));
+ fixpos = false;
+ }
+ }
+
+ // When count is too big, reduce it.
+ int movelen = oldlen - col - count + 1; // includes trailing NUL
+ if (movelen <= 1) {
+ // If we just took off the last character of a non-blank line, and
+ // fixpos is TRUE, we don't want to end up positioned at the NUL,
+ // unless "restart_edit" is set or 'virtualedit' contains "onemore".
+ if (col > 0 && fixpos && restart_edit == 0
+ && (ve_flags & VE_ONEMORE) == 0
+ ) {
+ curwin->w_cursor.col--;
+ curwin->w_cursor.coladd = 0;
+ curwin->w_cursor.col -= utf_head_off(oldp, oldp + curwin->w_cursor.col);
+ }
+ count = oldlen - col;
+ movelen = 1;
+ }
+
+ // If the old line has been allocated the deletion can be done in the
+ // existing line. Otherwise a new line has to be allocated.
+ bool was_alloced = ml_line_alloced(); // check if oldp was allocated
+ char_u *newp;
+ if (was_alloced) {
+ ml_add_deleted_len(curbuf->b_ml.ml_line_ptr, oldlen);
+ newp = oldp; // use same allocated memory
+ } else { // need to allocate a new line
+ newp = xmalloc((size_t)(oldlen + 1 - count));
+ memmove(newp, oldp, (size_t)col);
+ }
+ memmove(newp + col, oldp + col + count, (size_t)movelen);
+ if (!was_alloced) {
+ ml_replace(lnum, newp, false);
+ }
+
+ // mark the buffer as changed and prepare for displaying
+ changed_bytes(lnum, curwin->w_cursor.col);
+
+ return OK;
+}
+
+/// Copy the indent from ptr to the current line (and fill to size).
+/// Leaves the cursor on the first non-blank in the line.
+/// @return true if the line was changed.
+int copy_indent(int size, char_u *src)
+{
+ char_u *p = NULL;
+ char_u *line = NULL;
+ char_u *s;
+ int todo;
+ int ind_len;
+ int line_len = 0;
+ int tab_pad;
+ int ind_done;
+ int round;
+
+ // Round 1: compute the number of characters needed for the indent
+ // Round 2: copy the characters.
+ for (round = 1; round <= 2; round++) {
+ todo = size;
+ ind_len = 0;
+ ind_done = 0;
+ s = src;
+
+ // Count/copy the usable portion of the source line.
+ while (todo > 0 && ascii_iswhite(*s)) {
+ if (*s == TAB) {
+ tab_pad = (int)curbuf->b_p_ts
+ - (ind_done % (int)curbuf->b_p_ts);
+
+ // Stop if this tab will overshoot the target.
+ if (todo < tab_pad) {
+ break;
+ }
+ todo -= tab_pad;
+ ind_done += tab_pad;
+ } else {
+ todo--;
+ ind_done++;
+ }
+ ind_len++;
+
+ if (p != NULL) {
+ *p++ = *s;
+ }
+ s++;
+ }
+
+ // Fill to next tabstop with a tab, if possible.
+ tab_pad = (int)curbuf->b_p_ts - (ind_done % (int)curbuf->b_p_ts);
+
+ if ((todo >= tab_pad) && !curbuf->b_p_et) {
+ todo -= tab_pad;
+ ind_len++;
+
+ if (p != NULL) {
+ *p++ = TAB;
+ }
+ }
+
+ // Add tabs required for indent.
+ while (todo >= (int)curbuf->b_p_ts && !curbuf->b_p_et) {
+ todo -= (int)curbuf->b_p_ts;
+ ind_len++;
+
+ if (p != NULL) {
+ *p++ = TAB;
+ }
+ }
+
+ // Count/add spaces required for indent.
+ while (todo > 0) {
+ todo--;
+ ind_len++;
+
+ if (p != NULL) {
+ *p++ = ' ';
+ }
+ }
+
+ if (p == NULL) {
+ // Allocate memory for the result: the copied indent, new indent
+ // and the rest of the line.
+ line_len = (int)STRLEN(get_cursor_line_ptr()) + 1;
+ assert(ind_len + line_len >= 0);
+ size_t line_size;
+ STRICT_ADD(ind_len, line_len, &line_size, size_t);
+ line = xmalloc(line_size);
+ p = line;
+ }
+ }
+
+ // Append the original line
+ memmove(p, get_cursor_line_ptr(), (size_t)line_len);
+
+ // Replace the line
+ ml_replace(curwin->w_cursor.lnum, line, false);
+
+ // Put the cursor after the indent.
+ curwin->w_cursor.col = ind_len;
+ return true;
+}
+
+/// open_line: Add a new line below or above the current line.
+///
+/// For VREPLACE mode, we only add a new line when we get to the end of the
+/// file, otherwise we just start replacing the next line.
+///
+/// Caller must take care of undo. Since VREPLACE may affect any number of
+/// lines however, it may call u_save_cursor() again when starting to change a
+/// new line.
+/// "flags": OPENLINE_DELSPACES delete spaces after cursor
+/// OPENLINE_DO_COM format comments
+/// OPENLINE_KEEPTRAIL keep trailing spaces
+/// OPENLINE_MARKFIX adjust mark positions after the line break
+/// OPENLINE_COM_LIST format comments with list or 2nd line indent
+///
+/// "second_line_indent": indent for after ^^D in Insert mode or if flag
+/// OPENLINE_COM_LIST
+///
+/// @return true on success, false on failure
+int open_line(
+ int dir, // FORWARD or BACKWARD
+ int flags,
+ int second_line_indent
+)
+{
+ char_u *next_line = NULL; // copy of the next line
+ char_u *p_extra = NULL; // what goes to next line
+ colnr_T less_cols = 0; // less columns for mark in new line
+ colnr_T less_cols_off = 0; // columns to skip for mark adjust
+ pos_T old_cursor; // old cursor position
+ colnr_T newcol = 0; // new cursor column
+ int newindent = 0; // auto-indent of the new line
+ bool trunc_line = false; // truncate current line afterwards
+ bool retval = false; // return value
+ int extra_len = 0; // length of p_extra string
+ int lead_len; // length of comment leader
+ char_u *lead_flags; // position in 'comments' for comment leader
+ char_u *leader = NULL; // copy of comment leader
+ char_u *allocated = NULL; // allocated memory
+ char_u *p;
+ char_u saved_char = NUL; // init for GCC
+ pos_T *pos;
+ bool do_si = (!p_paste && curbuf->b_p_si && !curbuf->b_p_cin
+ && *curbuf->b_p_inde == NUL);
+ bool no_si = false; // reset did_si afterwards
+ int first_char = NUL; // init for GCC
+ int vreplace_mode;
+ bool did_append; // appended a new line
+ int saved_pi = curbuf->b_p_pi; // copy of preserveindent setting
+
+ // make a copy of the current line so we can mess with it
+ char_u *saved_line = vim_strsave(get_cursor_line_ptr());
+
+ if (State & VREPLACE_FLAG) {
+ // With VREPLACE we make a copy of the next line, which we will be
+ // starting to replace. First make the new line empty and let vim play
+ // with the indenting and comment leader to its heart's content. Then
+ // we grab what it ended up putting on the new line, put back the
+ // original line, and call ins_char() to put each new character onto
+ // the line, replacing what was there before and pushing the right
+ // stuff onto the replace stack. -- webb.
+ if (curwin->w_cursor.lnum < orig_line_count) {
+ next_line = vim_strsave(ml_get(curwin->w_cursor.lnum + 1));
+ } else {
+ next_line = vim_strsave((char_u *)"");
+ }
+
+ // In VREPLACE mode, a NL replaces the rest of the line, and starts
+ // replacing the next line, so push all of the characters left on the
+ // line onto the replace stack. We'll push any other characters that
+ // might be replaced at the start of the next line (due to autoindent
+ // etc) a bit later.
+ replace_push(NUL); // Call twice because BS over NL expects it
+ replace_push(NUL);
+ p = saved_line + curwin->w_cursor.col;
+ while (*p != NUL) {
+ p += replace_push_mb(p);
+ }
+ saved_line[curwin->w_cursor.col] = NUL;
+ }
+
+ if ((State & INSERT)
+ && !(State & VREPLACE_FLAG)
+ ) {
+ p_extra = saved_line + curwin->w_cursor.col;
+ if (do_si) { // need first char after new line break
+ p = skipwhite(p_extra);
+ first_char = *p;
+ }
+ extra_len = (int)STRLEN(p_extra);
+ saved_char = *p_extra;
+ *p_extra = NUL;
+ }
+
+ u_clearline(); // cannot do "U" command when adding lines
+ did_si = false;
+ ai_col = 0;
+
+ // If we just did an auto-indent, then we didn't type anything on
+ // the prior line, and it should be truncated. Do this even if 'ai' is not
+ // set because automatically inserting a comment leader also sets did_ai.
+ if (dir == FORWARD && did_ai) {
+ trunc_line = true;
+ }
+
+ // If 'autoindent' and/or 'smartindent' is set, try to figure out what
+ // indent to use for the new line.
+ if (curbuf->b_p_ai
+ || do_si
+ ) {
+ // count white space on current line
+ newindent = get_indent_str(saved_line, (int)curbuf->b_p_ts, false);
+ if (newindent == 0 && !(flags & OPENLINE_COM_LIST)) {
+ newindent = second_line_indent; // for ^^D command in insert mode
+ }
+
+ // Do smart indenting.
+ // In insert/replace mode (only when dir == FORWARD)
+ // we may move some text to the next line. If it starts with '{'
+ // don't add an indent. Fixes inserting a NL before '{' in line
+ // "if (condition) {"
+ if (!trunc_line && do_si && *saved_line != NUL
+ && (p_extra == NULL || first_char != '{')) {
+ char_u *ptr;
+ char_u last_char;
+
+ old_cursor = curwin->w_cursor;
+ ptr = saved_line;
+ if (flags & OPENLINE_DO_COM) {
+ lead_len = get_leader_len(ptr, NULL, false, true);
+ } else {
+ lead_len = 0;
+ }
+ if (dir == FORWARD) {
+ // Skip preprocessor directives, unless they are
+ // recognised as comments.
+ if (lead_len == 0 && ptr[0] == '#') {
+ while (ptr[0] == '#' && curwin->w_cursor.lnum > 1) {
+ ptr = ml_get(--curwin->w_cursor.lnum);
+ }
+ newindent = get_indent();
+ }
+ if (flags & OPENLINE_DO_COM) {
+ lead_len = get_leader_len(ptr, NULL, false, true);
+ } else {
+ lead_len = 0;
+ }
+ if (lead_len > 0) {
+ // This case gets the following right:
+ // \*
+ // * A comment (read '\' as '/').
+ // */
+ // #define IN_THE_WAY
+ // This should line up here;
+ p = skipwhite(ptr);
+ if (p[0] == '/' && p[1] == '*') {
+ p++;
+ }
+ if (p[0] == '*') {
+ for (p++; *p; p++) {
+ if (p[0] == '/' && p[-1] == '*') {
+ // End of C comment, indent should line up
+ // with the line containing the start of
+ // the comment
+ curwin->w_cursor.col = (colnr_T)(p - ptr);
+ if ((pos = findmatch(NULL, NUL)) != NULL) {
+ curwin->w_cursor.lnum = pos->lnum;
+ newindent = get_indent();
+ }
+ }
+ }
+ }
+ } else { // Not a comment line
+ // Find last non-blank in line
+ p = ptr + STRLEN(ptr) - 1;
+ while (p > ptr && ascii_iswhite(*p)) {
+ p--;
+ }
+ last_char = *p;
+
+ // find the character just before the '{' or ';'
+ if (last_char == '{' || last_char == ';') {
+ if (p > ptr) {
+ p--;
+ }
+ while (p > ptr && ascii_iswhite(*p)) {
+ p--;
+ }
+ }
+ // Try to catch lines that are split over multiple
+ // lines. eg:
+ // if (condition &&
+ // condition) {
+ // Should line up here!
+ // }
+ if (*p == ')') {
+ curwin->w_cursor.col = (colnr_T)(p - ptr);
+ if ((pos = findmatch(NULL, '(')) != NULL) {
+ curwin->w_cursor.lnum = pos->lnum;
+ newindent = get_indent();
+ ptr = get_cursor_line_ptr();
+ }
+ }
+ // If last character is '{' do indent, without
+ // checking for "if" and the like.
+ if (last_char == '{') {
+ did_si = true; // do indent
+ no_si = true; // don't delete it when '{' typed
+ // Look for "if" and the like, use 'cinwords'.
+ // Don't do this if the previous line ended in ';' or
+ // '}'.
+ } else if (last_char != ';' && last_char != '}'
+ && cin_is_cinword(ptr)) {
+ did_si = true;
+ }
+ }
+ } else { // dir == BACKWARD
+ // Skip preprocessor directives, unless they are
+ // recognised as comments.
+ if (lead_len == 0 && ptr[0] == '#') {
+ bool was_backslashed = false;
+
+ while ((ptr[0] == '#' || was_backslashed)
+ && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) {
+ if (*ptr && ptr[STRLEN(ptr) - 1] == '\\') {
+ was_backslashed = true;
+ } else {
+ was_backslashed = false;
+ }
+ ptr = ml_get(++curwin->w_cursor.lnum);
+ }
+ if (was_backslashed) {
+ newindent = 0; // Got to end of file
+ } else {
+ newindent = get_indent();
+ }
+ }
+ p = skipwhite(ptr);
+ if (*p == '}') { // if line starts with '}': do indent
+ did_si = true;
+ } else { // can delete indent when '{' typed
+ can_si_back = true;
+ }
+ }
+ curwin->w_cursor = old_cursor;
+ }
+ if (do_si) {
+ can_si = true;
+ }
+
+ did_ai = true;
+ }
+
+ // Find out if the current line starts with a comment leader.
+ // This may then be inserted in front of the new line.
+ end_comment_pending = NUL;
+ if (flags & OPENLINE_DO_COM) {
+ lead_len = get_leader_len(saved_line, &lead_flags, dir == BACKWARD, true);
+ } else {
+ lead_len = 0;
+ }
+ if (lead_len > 0) {
+ char_u *lead_repl = NULL; // replaces comment leader
+ int lead_repl_len = 0; // length of *lead_repl
+ char_u lead_middle[COM_MAX_LEN]; // middle-comment string
+ char_u lead_end[COM_MAX_LEN]; // end-comment string
+ char_u *comment_end = NULL; // where lead_end has been found
+ int extra_space = false; // append extra space
+ int current_flag;
+ int require_blank = false; // requires blank after middle
+ char_u *p2;
+
+ // If the comment leader has the start, middle or end flag, it may not
+ // be used or may be replaced with the middle leader.
+ for (p = lead_flags; *p && *p != ':'; p++) {
+ if (*p == COM_BLANK) {
+ require_blank = true;
+ continue;
+ }
+ if (*p == COM_START || *p == COM_MIDDLE) {
+ current_flag = *p;
+ if (*p == COM_START) {
+ // Doing "O" on a start of comment does not insert leader.
+ if (dir == BACKWARD) {
+ lead_len = 0;
+ break;
+ }
+
+ // find start of middle part
+ (void)copy_option_part(&p, lead_middle, COM_MAX_LEN, ",");
+ require_blank = false;
+ }
+
+ // Isolate the strings of the middle and end leader.
+ while (*p && p[-1] != ':') { // find end of middle flags
+ if (*p == COM_BLANK) {
+ require_blank = true;
+ }
+ p++;
+ }
+ (void)copy_option_part(&p, lead_middle, COM_MAX_LEN, ",");
+
+ while (*p && p[-1] != ':') { // find end of end flags
+ // Check whether we allow automatic ending of comments
+ if (*p == COM_AUTO_END) {
+ end_comment_pending = -1; // means we want to set it
+ }
+ p++;
+ }
+ size_t n = copy_option_part(&p, lead_end, COM_MAX_LEN, ",");
+
+ if (end_comment_pending == -1) { // we can set it now
+ end_comment_pending = lead_end[n - 1];
+ }
+
+ // If the end of the comment is in the same line, don't use
+ // the comment leader.
+ if (dir == FORWARD) {
+ for (p = saved_line + lead_len; *p; p++) {
+ if (STRNCMP(p, lead_end, n) == 0) {
+ comment_end = p;
+ lead_len = 0;
+ break;
+ }
+ }
+ }
+
+ // Doing "o" on a start of comment inserts the middle leader.
+ if (lead_len > 0) {
+ if (current_flag == COM_START) {
+ lead_repl = lead_middle;
+ lead_repl_len = (int)STRLEN(lead_middle);
+ }
+
+ // If we have hit RETURN immediately after the start
+ // comment leader, then put a space after the middle
+ // comment leader on the next line.
+ if (!ascii_iswhite(saved_line[lead_len - 1])
+ && ((p_extra != NULL
+ && (int)curwin->w_cursor.col == lead_len)
+ || (p_extra == NULL
+ && saved_line[lead_len] == NUL)
+ || require_blank)) {
+ extra_space = true;
+ }
+ }
+ break;
+ }
+ if (*p == COM_END) {
+ // Doing "o" on the end of a comment does not insert leader.
+ // Remember where the end is, might want to use it to find the
+ // start (for C-comments).
+ if (dir == FORWARD) {
+ comment_end = skipwhite(saved_line);
+ lead_len = 0;
+ break;
+ }
+
+ // Doing "O" on the end of a comment inserts the middle leader.
+ // Find the string for the middle leader, searching backwards.
+ while (p > curbuf->b_p_com && *p != ',') {
+ p--;
+ }
+ for (lead_repl = p; lead_repl > curbuf->b_p_com
+ && lead_repl[-1] != ':'; lead_repl--) {
+ }
+ lead_repl_len = (int)(p - lead_repl);
+
+ // We can probably always add an extra space when doing "O" on
+ // the comment-end
+ extra_space = true;
+
+ // Check whether we allow automatic ending of comments
+ for (p2 = p; *p2 && *p2 != ':'; p2++) {
+ if (*p2 == COM_AUTO_END) {
+ end_comment_pending = -1; // means we want to set it
+ }
+ }
+ if (end_comment_pending == -1) {
+ // Find last character in end-comment string
+ while (*p2 && *p2 != ',') {
+ p2++;
+ }
+ end_comment_pending = p2[-1];
+ }
+ break;
+ }
+ if (*p == COM_FIRST) {
+ // Comment leader for first line only: Don't repeat leader
+ // when using "O", blank out leader when using "o".
+ if (dir == BACKWARD) {
+ lead_len = 0;
+ } else {
+ lead_repl = (char_u *)"";
+ lead_repl_len = 0;
+ }
+ break;
+ }
+ }
+ if (lead_len > 0) {
+ // allocate buffer (may concatenate p_extra later)
+ int bytes = lead_len
+ + lead_repl_len
+ + extra_space
+ + extra_len
+ + (second_line_indent > 0 ? second_line_indent : 0)
+ + 1;
+ assert(bytes >= 0);
+ leader = xmalloc((size_t)bytes);
+ allocated = leader; // remember to free it later
+
+ STRLCPY(leader, saved_line, lead_len + 1);
+
+ // Replace leader with lead_repl, right or left adjusted
+ if (lead_repl != NULL) {
+ int c = 0;
+ int off = 0;
+
+ for (p = lead_flags; *p != NUL && *p != ':'; ) {
+ if (*p == COM_RIGHT || *p == COM_LEFT) {
+ c = *p++;
+ } else if (ascii_isdigit(*p) || *p == '-') {
+ off = getdigits_int(&p);
+ } else {
+ p++;
+ }
+ }
+ if (c == COM_RIGHT) { // right adjusted leader
+ // find last non-white in the leader to line up with
+ for (p = leader + lead_len - 1; p > leader
+ && ascii_iswhite(*p); p--) {
+ }
+ p++;
+
+ // Compute the length of the replaced characters in
+ // screen characters, not bytes.
+ {
+ int repl_size = vim_strnsize(lead_repl,
+ lead_repl_len);
+ int old_size = 0;
+ char_u *endp = p;
+ int l;
+
+ while (old_size < repl_size && p > leader) {
+ MB_PTR_BACK(leader, p);
+ old_size += ptr2cells(p);
+ }
+ l = lead_repl_len - (int)(endp - p);
+ if (l != 0) {
+ memmove(endp + l, endp,
+ (size_t)((leader + lead_len) - endp));
+ }
+ lead_len += l;
+ }
+ memmove(p, lead_repl, (size_t)lead_repl_len);
+ if (p + lead_repl_len > leader + lead_len) {
+ p[lead_repl_len] = NUL;
+ }
+
+ // blank-out any other chars from the old leader.
+ while (--p >= leader) {
+ int l = utf_head_off(leader, p);
+
+ if (l > 1) {
+ p -= l;
+ if (ptr2cells(p) > 1) {
+ p[1] = ' ';
+ l--;
+ }
+ memmove(p + 1, p + l + 1,
+ (size_t)((leader + lead_len) - (p + l + 1)));
+ lead_len -= l;
+ *p = ' ';
+ } else if (!ascii_iswhite(*p)) {
+ *p = ' ';
+ }
+ }
+ } else { // left adjusted leader
+ p = skipwhite(leader);
+ // Compute the length of the replaced characters in
+ // screen characters, not bytes. Move the part that is
+ // not to be overwritten.
+ {
+ int repl_size = vim_strnsize(lead_repl,
+ lead_repl_len);
+ int i;
+ int l;
+
+ for (i = 0; i < lead_len && p[i] != NUL; i += l) {
+ l = (*mb_ptr2len)(p + i);
+ if (vim_strnsize(p, i + l) > repl_size) {
+ break;
+ }
+ }
+ if (i != lead_repl_len) {
+ memmove(p + lead_repl_len, p + i,
+ (size_t)(lead_len - i - (p - leader)));
+ lead_len += lead_repl_len - i;
+ }
+ }
+ memmove(p, lead_repl, (size_t)lead_repl_len);
+
+ // Replace any remaining non-white chars in the old
+ // leader by spaces. Keep Tabs, the indent must
+ // remain the same.
+ for (p += lead_repl_len; p < leader + lead_len; p++) {
+ if (!ascii_iswhite(*p)) {
+ // Don't put a space before a TAB.
+ if (p + 1 < leader + lead_len && p[1] == TAB) {
+ lead_len--;
+ memmove(p, p + 1, (size_t)(leader + lead_len - p));
+ } else {
+ int l = (*mb_ptr2len)(p);
+
+ if (l > 1) {
+ if (ptr2cells(p) > 1) {
+ // Replace a double-wide char with
+ // two spaces
+ l--;
+ *p++ = ' ';
+ }
+ memmove(p + 1, p + l, (size_t)(leader + lead_len - p));
+ lead_len -= l - 1;
+ }
+ *p = ' ';
+ }
+ }
+ }
+ *p = NUL;
+ }
+
+ // Recompute the indent, it may have changed.
+ if (curbuf->b_p_ai
+ || do_si
+ ) {
+ newindent = get_indent_str(leader, (int)curbuf->b_p_ts, false);
+ }
+
+ // Add the indent offset
+ if (newindent + off < 0) {
+ off = -newindent;
+ newindent = 0;
+ } else {
+ newindent += off;
+ }
+
+ // Correct trailing spaces for the shift, so that
+ // alignment remains equal.
+ while (off > 0 && lead_len > 0
+ && leader[lead_len - 1] == ' ') {
+ // Don't do it when there is a tab before the space
+ if (vim_strchr(skipwhite(leader), '\t') != NULL) {
+ break;
+ }
+ lead_len--;
+ off--;
+ }
+
+ // If the leader ends in white space, don't add an
+ // extra space
+ if (lead_len > 0 && ascii_iswhite(leader[lead_len - 1])) {
+ extra_space = false;
+ }
+ leader[lead_len] = NUL;
+ }
+
+ if (extra_space) {
+ leader[lead_len++] = ' ';
+ leader[lead_len] = NUL;
+ }
+
+ newcol = lead_len;
+
+ // if a new indent will be set below, remove the indent that
+ // is in the comment leader
+ if (newindent
+ || did_si
+ ) {
+ while (lead_len && ascii_iswhite(*leader)) {
+ lead_len--;
+ newcol--;
+ leader++;
+ }
+ }
+
+ did_si = can_si = false;
+ } else if (comment_end != NULL) {
+ // We have finished a comment, so we don't use the leader.
+ // If this was a C-comment and 'ai' or 'si' is set do a normal
+ // indent to align with the line containing the start of the
+ // comment.
+ if (comment_end[0] == '*' && comment_end[1] == '/'
+ && (curbuf->b_p_ai || do_si)) {
+ old_cursor = curwin->w_cursor;
+ curwin->w_cursor.col = (colnr_T)(comment_end - saved_line);
+ if ((pos = findmatch(NULL, NUL)) != NULL) {
+ curwin->w_cursor.lnum = pos->lnum;
+ newindent = get_indent();
+ }
+ curwin->w_cursor = old_cursor;
+ }
+ }
+ }
+
+ // (State == INSERT || State == REPLACE), only when dir == FORWARD
+ if (p_extra != NULL) {
+ *p_extra = saved_char; // restore char that NUL replaced
+
+ // When 'ai' set or "flags" has OPENLINE_DELSPACES, skip to the first
+ // non-blank.
+ //
+ // When in REPLACE mode, put the deleted blanks on the replace stack,
+ // preceded by a NUL, so they can be put back when a BS is entered.
+ if (REPLACE_NORMAL(State)) {
+ replace_push(NUL); // end of extra blanks
+ }
+ if (curbuf->b_p_ai || (flags & OPENLINE_DELSPACES)) {
+ while ((*p_extra == ' ' || *p_extra == '\t')
+ && !utf_iscomposing(utf_ptr2char(p_extra + 1))) {
+ if (REPLACE_NORMAL(State)) {
+ replace_push(*p_extra);
+ }
+ p_extra++;
+ less_cols_off++;
+ }
+ }
+
+ // columns for marks adjusted for removed columns
+ less_cols = (int)(p_extra - saved_line);
+ }
+
+ if (p_extra == NULL) {
+ p_extra = (char_u *)""; // append empty line
+ }
+
+ // concatenate leader and p_extra, if there is a leader
+ if (lead_len > 0) {
+ if (flags & OPENLINE_COM_LIST && second_line_indent > 0) {
+ int i;
+ int padding = second_line_indent
+ - (newindent + (int)STRLEN(leader));
+
+ // Here whitespace is inserted after the comment char.
+ // Below, set_indent(newindent, SIN_INSERT) will insert the
+ // whitespace needed before the comment char.
+ for (i = 0; i < padding; i++) {
+ STRCAT(leader, " ");
+ less_cols--;
+ newcol++;
+ }
+ }
+ STRCAT(leader, p_extra);
+ p_extra = leader;
+ did_ai = true; // So truncating blanks works with comments
+ less_cols -= lead_len;
+ } else {
+ end_comment_pending = NUL; // turns out there was no leader
+ }
+
+ old_cursor = curwin->w_cursor;
+ if (dir == BACKWARD) {
+ curwin->w_cursor.lnum--;
+ }
+ if (!(State & VREPLACE_FLAG) || old_cursor.lnum >= orig_line_count) {
+ if (ml_append(curwin->w_cursor.lnum, p_extra, (colnr_T)0, false) == FAIL) {
+ goto theend;
+ }
+ // Postpone calling changed_lines(), because it would mess up folding
+ // with markers.
+ // Skip mark_adjust when adding a line after the last one, there can't
+ // be marks there. But still needed in diff mode.
+ if (curwin->w_cursor.lnum + 1 < curbuf->b_ml.ml_line_count
+ || curwin->w_p_diff) {
+ mark_adjust(curwin->w_cursor.lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, false);
+ }
+ did_append = true;
+ } else {
+ // In VREPLACE mode we are starting to replace the next line.
+ curwin->w_cursor.lnum++;
+ if (curwin->w_cursor.lnum >= Insstart.lnum + vr_lines_changed) {
+ // In case we NL to a new line, BS to the previous one, and NL
+ // again, we don't want to save the new line for undo twice.
+ (void)u_save_cursor(); // errors are ignored!
+ vr_lines_changed++;
+ }
+ ml_replace(curwin->w_cursor.lnum, p_extra, true);
+ changed_bytes(curwin->w_cursor.lnum, 0);
+ curwin->w_cursor.lnum--;
+ did_append = false;
+ }
+
+ inhibit_delete_count++;
+ if (newindent
+ || did_si
+ ) {
+ curwin->w_cursor.lnum++;
+ if (did_si) {
+ int sw = get_sw_value(curbuf);
+
+ if (p_sr) {
+ newindent -= newindent % sw;
+ }
+ newindent += sw;
+ }
+ // Copy the indent
+ if (curbuf->b_p_ci) {
+ (void)copy_indent(newindent, saved_line);
+
+ // Set the 'preserveindent' option so that any further screwing
+ // with the line doesn't entirely destroy our efforts to preserve
+ // it. It gets restored at the function end.
+ curbuf->b_p_pi = true;
+ } else {
+ (void)set_indent(newindent, SIN_INSERT);
+ }
+ less_cols -= curwin->w_cursor.col;
+
+ ai_col = curwin->w_cursor.col;
+
+ // In REPLACE mode, for each character in the new indent, there must
+ // be a NUL on the replace stack, for when it is deleted with BS
+ if (REPLACE_NORMAL(State)) {
+ for (colnr_T n = 0; n < curwin->w_cursor.col; n++) {
+ replace_push(NUL);
+ }
+ }
+ newcol += curwin->w_cursor.col;
+ if (no_si) {
+ did_si = false;
+ }
+ }
+ inhibit_delete_count--;
+
+ // In REPLACE mode, for each character in the extra leader, there must be
+ // a NUL on the replace stack, for when it is deleted with BS.
+ if (REPLACE_NORMAL(State)) {
+ while (lead_len-- > 0) {
+ replace_push(NUL);
+ }
+ }
+
+ curwin->w_cursor = old_cursor;
+
+ if (dir == FORWARD) {
+ if (trunc_line || (State & INSERT)) {
+ // truncate current line at cursor
+ saved_line[curwin->w_cursor.col] = NUL;
+ // Remove trailing white space, unless OPENLINE_KEEPTRAIL used.
+ if (trunc_line && !(flags & OPENLINE_KEEPTRAIL)) {
+ truncate_spaces(saved_line);
+ }
+ ml_replace(curwin->w_cursor.lnum, saved_line, false);
+ saved_line = NULL;
+ if (did_append) {
+ changed_lines(curwin->w_cursor.lnum, curwin->w_cursor.col,
+ curwin->w_cursor.lnum + 1, 1L, true);
+ did_append = false;
+
+ // Move marks after the line break to the new line.
+ if (flags & OPENLINE_MARKFIX) {
+ mark_col_adjust(curwin->w_cursor.lnum,
+ curwin->w_cursor.col + less_cols_off,
+ 1L, (long)-less_cols, 0);
+ }
+ } else {
+ changed_bytes(curwin->w_cursor.lnum, curwin->w_cursor.col);
+ }
+ }
+
+ // Put the cursor on the new line. Careful: the scrollup() above may
+ // have moved w_cursor, we must use old_cursor.
+ curwin->w_cursor.lnum = old_cursor.lnum + 1;
+ }
+ if (did_append) {
+ changed_lines(curwin->w_cursor.lnum, 0, curwin->w_cursor.lnum, 1L, true);
+ }
+
+ curwin->w_cursor.col = newcol;
+ curwin->w_cursor.coladd = 0;
+
+ // In VREPLACE mode, we are handling the replace stack ourselves, so stop
+ // fixthisline() from doing it (via change_indent()) by telling it we're in
+ // normal INSERT mode.
+ if (State & VREPLACE_FLAG) {
+ vreplace_mode = State; // So we know to put things right later
+ State = INSERT;
+ } else {
+ vreplace_mode = 0;
+ }
+ // May do lisp indenting.
+ if (!p_paste
+ && leader == NULL
+ && curbuf->b_p_lisp
+ && curbuf->b_p_ai) {
+ fixthisline(get_lisp_indent);
+ ai_col = (colnr_T)getwhitecols_curline();
+ }
+ // May do indenting after opening a new line.
+ if (!p_paste
+ && (curbuf->b_p_cin
+ || *curbuf->b_p_inde != NUL
+ )
+ && in_cinkeys(dir == FORWARD
+ ? KEY_OPEN_FORW
+ : KEY_OPEN_BACK, ' ', linewhite(curwin->w_cursor.lnum))) {
+ do_c_expr_indent();
+ ai_col = (colnr_T)getwhitecols_curline();
+ }
+ if (vreplace_mode != 0) {
+ State = vreplace_mode;
+ }
+
+ // Finally, VREPLACE gets the stuff on the new line, then puts back the
+ // original line, and inserts the new stuff char by char, pushing old stuff
+ // onto the replace stack (via ins_char()).
+ if (State & VREPLACE_FLAG) {
+ // Put new line in p_extra
+ p_extra = vim_strsave(get_cursor_line_ptr());
+
+ // Put back original line
+ ml_replace(curwin->w_cursor.lnum, next_line, false);
+
+ // Insert new stuff into line again
+ curwin->w_cursor.col = 0;
+ curwin->w_cursor.coladd = 0;
+ ins_bytes(p_extra); // will call changed_bytes()
+ xfree(p_extra);
+ next_line = NULL;
+ }
+
+ retval = true; // success!
+theend:
+ curbuf->b_p_pi = saved_pi;
+ xfree(saved_line);
+ xfree(next_line);
+ xfree(allocated);
+ return retval;
+} // NOLINT(readability/fn_size)
+
+/// Delete from cursor to end of line.
+/// Caller must have prepared for undo.
+/// If "fixpos" is true fix the cursor position when done.
+void truncate_line(int fixpos)
+{
+ char_u *newp;
+ linenr_T lnum = curwin->w_cursor.lnum;
+ colnr_T col = curwin->w_cursor.col;
+
+ if (col == 0) {
+ newp = vim_strsave((char_u *)"");
+ } else {
+ newp = vim_strnsave(ml_get(lnum), (size_t)col);
+ }
+ ml_replace(lnum, newp, false);
+
+ // mark the buffer as changed and prepare for displaying
+ changed_bytes(lnum, curwin->w_cursor.col);
+
+ // If "fixpos" is true we don't want to end up positioned at the NUL.
+ if (fixpos && curwin->w_cursor.col > 0) {
+ curwin->w_cursor.col--;
+ }
+}
+
+/// Delete "nlines" lines at the cursor.
+/// Saves the lines for undo first if "undo" is true.
+void del_lines(long nlines, int undo)
+{
+ long n;
+ linenr_T first = curwin->w_cursor.lnum;
+
+ if (nlines <= 0) {
+ return;
+ }
+
+ // save the deleted lines for undo
+ if (undo && u_savedel(first, nlines) == FAIL) {
+ return;
+ }
+
+ for (n = 0; n < nlines; ) {
+ if (curbuf->b_ml.ml_flags & ML_EMPTY) { // nothing to delete
+ break;
+ }
+
+ ml_delete(first, true);
+ n++;
+
+ // If we delete the last line in the file, stop
+ if (first > curbuf->b_ml.ml_line_count) {
+ break;
+ }
+ }
+
+ // Correct the cursor position before calling deleted_lines_mark(), it may
+ // trigger a callback to display the cursor.
+ curwin->w_cursor.col = 0;
+ check_cursor_lnum();
+
+ // adjust marks, mark the buffer as changed and prepare for displaying
+ deleted_lines_mark(first, n);
+}
diff --git a/src/nvim/change.h b/src/nvim/change.h
new file mode 100644
index 0000000000..e1a1bfba17
--- /dev/null
+++ b/src/nvim/change.h
@@ -0,0 +1,11 @@
+#ifndef NVIM_CHANGE_H
+#define NVIM_CHANGE_H
+
+#include "nvim/buffer_defs.h" // for buf_T
+#include "nvim/pos.h" // for linenr_T
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "change.h.generated.h"
+#endif
+
+#endif // NVIM_CHANGE_H
diff --git a/src/nvim/channel.c b/src/nvim/channel.c
index 8b8d27affd..104c79efd9 100644
--- a/src/nvim/channel.c
+++ b/src/nvim/channel.c
@@ -22,19 +22,10 @@ PMap(uint64_t) *channels = NULL;
/// 2 is reserved for stderr channel
static uint64_t next_chan_id = CHAN_STDERR+1;
-
-typedef struct {
- Channel *chan;
- Callback *callback;
- const char *type;
- // if reader is set, status is ignored.
- CallbackReader *reader;
- int status;
-} ChannelEvent;
-
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "channel.c.generated.h"
#endif
+
/// Teardown the module
void channel_teardown(void)
{
@@ -179,6 +170,7 @@ static Channel *channel_alloc(ChannelStreamType type)
}
chan->events = multiqueue_new_child(main_loop.events);
chan->refcount = 1;
+ chan->exit_status = -1;
chan->streamtype = type;
pmap_put(uint64_t)(channels, chan->id, chan);
return chan;
@@ -234,9 +226,10 @@ void callback_reader_free(CallbackReader *reader)
ga_clear(&reader->buffer);
}
-void callback_reader_start(CallbackReader *reader)
+void callback_reader_start(CallbackReader *reader, const char *type)
{
ga_init(&reader->buffer, sizeof(char *), 32);
+ reader->type = type;
}
static void free_channel_event(void **argv)
@@ -246,7 +239,7 @@ static void free_channel_event(void **argv)
rpc_free(chan);
}
- callback_reader_free(&chan->on_stdout);
+ callback_reader_free(&chan->on_data);
callback_reader_free(&chan->on_stderr);
callback_free(&chan->on_exit);
@@ -286,7 +279,7 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout,
assert(cwd == NULL || os_isdir_executable(cwd));
Channel *chan = channel_alloc(kChannelStreamProc);
- chan->on_stdout = on_stdout;
+ chan->on_data = on_stdout;
chan->on_stderr = on_stderr;
chan->on_exit = on_exit;
@@ -326,7 +319,7 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout,
has_out = true;
has_err = false;
} else {
- has_out = rpc || callback_reader_set(chan->on_stdout);
+ has_out = rpc || callback_reader_set(chan->on_data);
has_err = callback_reader_set(chan->on_stderr);
}
int status = process_spawn(proc, true, has_out, has_err);
@@ -352,13 +345,13 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout,
rpc_start(chan);
} else {
if (has_out) {
- callback_reader_start(&chan->on_stdout);
- rstream_start(&proc->out, on_job_stdout, chan);
+ callback_reader_start(&chan->on_data, "stdout");
+ rstream_start(&proc->out, on_channel_data, chan);
}
}
if (has_err) {
- callback_reader_start(&chan->on_stderr);
+ callback_reader_start(&chan->on_stderr, "stderr");
rstream_init(&proc->err, 0);
rstream_start(&proc->err, on_job_stderr, chan);
}
@@ -402,9 +395,9 @@ uint64_t channel_connect(bool tcp, const char *address,
if (rpc) {
rpc_start(channel);
} else {
- channel->on_stdout = on_output;
- callback_reader_start(&channel->on_stdout);
- rstream_start(&channel->stream.socket, on_socket_output, channel);
+ channel->on_data = on_output;
+ callback_reader_start(&channel->on_data, "data");
+ rstream_start(&channel->stream.socket, on_channel_data, channel);
}
end:
@@ -452,9 +445,9 @@ uint64_t channel_from_stdio(bool rpc, CallbackReader on_output,
if (rpc) {
rpc_start(channel);
} else {
- channel->on_stdout = on_output;
- callback_reader_start(&channel->on_stdout);
- rstream_start(&channel->stream.stdio.in, on_stdio_input, channel);
+ channel->on_data = on_output;
+ callback_reader_start(&channel->on_data, "stdin");
+ rstream_start(&channel->stream.stdio.in, on_channel_data, channel);
}
return channel->id;
@@ -519,55 +512,22 @@ static inline list_T *buffer_to_tv_list(const char *const buf, const size_t len)
return l;
}
-// vimscript job callbacks must be executed on Nvim main loop
-static inline void process_channel_event(Channel *chan, Callback *callback,
- const char *type,
- CallbackReader *reader, int status)
-{
- assert(callback);
- ChannelEvent *event_data = xmalloc(sizeof(*event_data));
- event_data->reader = reader;
- event_data->status = status;
- channel_incref(chan); // Hold on ref to callback
- event_data->chan = chan;
- event_data->callback = callback;
- event_data->type = type;
-
- multiqueue_put(chan->events, on_channel_event, 1, event_data);
-}
-
-void on_job_stdout(Stream *stream, RBuffer *buf, size_t count,
- void *data, bool eof)
+void on_channel_data(Stream *stream, RBuffer *buf, size_t count,
+ void *data, bool eof)
{
Channel *chan = data;
- on_channel_output(stream, chan, buf, count, eof, &chan->on_stdout, "stdout");
+ on_channel_output(stream, chan, buf, count, eof, &chan->on_data);
}
void on_job_stderr(Stream *stream, RBuffer *buf, size_t count,
void *data, bool eof)
{
Channel *chan = data;
- on_channel_output(stream, chan, buf, count, eof, &chan->on_stderr, "stderr");
-}
-
-static void on_socket_output(Stream *stream, RBuffer *buf, size_t count,
- void *data, bool eof)
-{
- Channel *chan = data;
- on_channel_output(stream, chan, buf, count, eof, &chan->on_stdout, "data");
-}
-
-static void on_stdio_input(Stream *stream, RBuffer *buf, size_t count,
- void *data, bool eof)
-{
- Channel *chan = data;
- on_channel_output(stream, chan, buf, count, eof, &chan->on_stdout, "stdin");
+ on_channel_output(stream, chan, buf, count, eof, &chan->on_stderr);
}
-/// @param type must have static lifetime
static void on_channel_output(Stream *stream, Channel *chan, RBuffer *buf,
- size_t count, bool eof, CallbackReader *reader,
- const char *type)
+ size_t count, bool eof, CallbackReader *reader)
{
// stub variable, to keep reading consistent with the order of events, only
// consider the count parameter.
@@ -575,57 +535,93 @@ static void on_channel_output(Stream *stream, Channel *chan, RBuffer *buf,
char *ptr = rbuffer_read_ptr(buf, &r);
if (eof) {
- if (reader->buffered) {
- if (reader->cb.type != kCallbackNone) {
- process_channel_event(chan, &reader->cb, type, reader, 0);
- } else if (reader->self) {
- if (tv_dict_find(reader->self, type, -1) == NULL) {
- list_T *data = buffer_to_tv_list(reader->buffer.ga_data,
- (size_t)reader->buffer.ga_len);
- tv_dict_add_list(reader->self, type, strlen(type), data);
- } else {
- // can't display error message now, defer it.
- channel_incref(chan);
- multiqueue_put(chan->events, on_buffered_error, 2, chan, type);
- }
- ga_clear(&reader->buffer);
- } else {
- abort();
- }
- } else if (reader->cb.type != kCallbackNone) {
- process_channel_event(chan, &reader->cb, type, reader, 0);
+ reader->eof = true;
+ } else {
+ if (chan->term) {
+ terminal_receive(chan->term, ptr, count);
+ terminal_flush_output(chan->term);
}
- return;
- }
- // The order here matters, the terminal must receive the data first because
- // process_channel_event will modify the read buffer(convert NULs into NLs)
- if (chan->term) {
- terminal_receive(chan->term, ptr, count);
- terminal_flush_output(chan->term);
+ rbuffer_consumed(buf, count);
+
+ if (callback_reader_set(*reader)) {
+ ga_concat_len(&reader->buffer, ptr, count);
+ }
}
- rbuffer_consumed(buf, count);
+ if (callback_reader_set(*reader)) {
+ schedule_channel_event(chan);
+ }
+}
- if (callback_reader_set(*reader) || reader->buffered) {
- // if buffer wasn't consumed, a pending callback is stalled. Aggregate the
- // received data and avoid a "burst" of multiple callbacks.
- bool buffer_set = reader->buffer.ga_len > 0;
- ga_concat_len(&reader->buffer, ptr, count);
- if (callback_reader_set(*reader) && !reader->buffered && !buffer_set) {
- process_channel_event(chan, &reader->cb, type, reader, 0);
+/// schedule the necessary callbacks to be invoked as a deferred event
+static void schedule_channel_event(Channel *chan)
+{
+ if (!chan->callback_scheduled) {
+ if (!chan->callback_busy) {
+ multiqueue_put(chan->events, on_channel_event, 1, chan);
+ channel_incref(chan);
}
+ chan->callback_scheduled = true;
}
}
-static void on_buffered_error(void **args)
+static void on_channel_event(void **args)
{
Channel *chan = (Channel *)args[0];
- const char *stream = (const char *)args[1];
- EMSG3(_(e_streamkey), stream, chan->id);
+
+ chan->callback_busy = true;
+ chan->callback_scheduled = false;
+
+ int exit_status = chan->exit_status;
+ channel_reader_callbacks(chan, &chan->on_data);
+ channel_reader_callbacks(chan, &chan->on_stderr);
+ if (exit_status > -1) {
+ channel_callback_call(chan, NULL);
+ chan->exit_status = -1;
+ }
+
+ chan->callback_busy = false;
+ if (chan->callback_scheduled) {
+ // further callback was deferred to avoid recursion.
+ multiqueue_put(chan->events, on_channel_event, 1, chan);
+ channel_incref(chan);
+ }
+
channel_decref(chan);
}
+void channel_reader_callbacks(Channel *chan, CallbackReader *reader)
+{
+ if (reader->buffered) {
+ if (reader->eof) {
+ if (reader->self) {
+ if (tv_dict_find(reader->self, reader->type, -1) == NULL) {
+ list_T *data = buffer_to_tv_list(reader->buffer.ga_data,
+ (size_t)reader->buffer.ga_len);
+ tv_dict_add_list(reader->self, reader->type, strlen(reader->type),
+ data);
+ } else {
+ EMSG3(_(e_streamkey), reader->type, chan->id);
+ }
+ } else {
+ channel_callback_call(chan, reader);
+ }
+ reader->eof = false;
+ }
+ } else {
+ bool is_eof = reader->eof;
+ if (reader->buffer.ga_len > 0) {
+ channel_callback_call(chan, reader);
+ }
+ // if the stream reached eof, invoke extra callback with no data
+ if (is_eof) {
+ channel_callback_call(chan, reader);
+ reader->eof = false;
+ }
+ }
+}
+
static void channel_process_exit_cb(Process *proc, int status, void *data)
{
Channel *chan = data;
@@ -637,45 +633,46 @@ static void channel_process_exit_cb(Process *proc, int status, void *data)
// If process did not exit, we only closed the handle of a detached process.
bool exited = (status >= 0);
- if (exited) {
- process_channel_event(chan, &chan->on_exit, "exit", NULL, status);
+ if (exited && chan->on_exit.type != kCallbackNone) {
+ schedule_channel_event(chan);
+ chan->exit_status = status;
}
channel_decref(chan);
}
-static void on_channel_event(void **args)
+static void channel_callback_call(Channel *chan, CallbackReader *reader)
{
- ChannelEvent *ev = (ChannelEvent *)args[0];
-
+ Callback *cb;
typval_T argv[4];
argv[0].v_type = VAR_NUMBER;
argv[0].v_lock = VAR_UNLOCKED;
- argv[0].vval.v_number = (varnumber_T)ev->chan->id;
+ argv[0].vval.v_number = (varnumber_T)chan->id;
- if (ev->reader) {
+ if (reader) {
argv[1].v_type = VAR_LIST;
argv[1].v_lock = VAR_UNLOCKED;
- argv[1].vval.v_list = buffer_to_tv_list(ev->reader->buffer.ga_data,
- (size_t)ev->reader->buffer.ga_len);
+ argv[1].vval.v_list = buffer_to_tv_list(reader->buffer.ga_data,
+ (size_t)reader->buffer.ga_len);
tv_list_ref(argv[1].vval.v_list);
- ga_clear(&ev->reader->buffer);
+ ga_clear(&reader->buffer);
+ cb = &reader->cb;
+ argv[2].vval.v_string = (char_u *)reader->type;
} else {
argv[1].v_type = VAR_NUMBER;
argv[1].v_lock = VAR_UNLOCKED;
- argv[1].vval.v_number = ev->status;
+ argv[1].vval.v_number = chan->exit_status;
+ cb = &chan->on_exit;
+ argv[2].vval.v_string = (char_u *)"exit";
}
argv[2].v_type = VAR_STRING;
argv[2].v_lock = VAR_UNLOCKED;
- argv[2].vval.v_string = (uint8_t *)ev->type;
typval_T rettv = TV_INITIAL_VALUE;
- callback_call(ev->callback, 3, argv, &rettv);
+ callback_call(cb, 3, argv, &rettv);
tv_clear(&rettv);
- channel_decref(ev->chan);
- xfree(ev);
}
@@ -765,6 +762,14 @@ static void set_info_event(void **argv)
channel_decref(chan);
}
+bool channel_job_running(uint64_t id)
+{
+ Channel *chan = find_channel(id);
+ return (chan
+ && chan->streamtype == kChannelStreamProc
+ && !process_is_stopped(&chan->stream.proc));
+}
+
Dictionary channel_info(uint64_t id)
{
Channel *chan = find_channel(id);
diff --git a/src/nvim/channel.h b/src/nvim/channel.h
index b856d197f1..c733e276be 100644
--- a/src/nvim/channel.h
+++ b/src/nvim/channel.h
@@ -42,13 +42,16 @@ typedef struct {
Callback cb;
dict_T *self;
garray_T buffer;
+ bool eof;
bool buffered;
+ const char *type;
} CallbackReader;
#define CALLBACK_READER_INIT ((CallbackReader){ .cb = CALLBACK_NONE, \
.self = NULL, \
.buffer = GA_EMPTY_INIT_VALUE, \
- .buffered = false })
+ .buffered = false, \
+ .type = NULL })
static inline bool callback_reader_set(CallbackReader reader)
{
return reader.cb.type != kCallbackNone || reader.self;
@@ -73,9 +76,13 @@ struct Channel {
RpcState rpc;
Terminal *term;
- CallbackReader on_stdout;
+ CallbackReader on_data;
CallbackReader on_stderr;
Callback on_exit;
+ int exit_status;
+
+ bool callback_busy;
+ bool callback_scheduled;
};
EXTERN PMap(uint64_t) *channels;
diff --git a/src/nvim/context.c b/src/nvim/context.c
new file mode 100644
index 0000000000..b2a2fd3fd9
--- /dev/null
+++ b/src/nvim/context.c
@@ -0,0 +1,383 @@
+// 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
+
+// Context: snapshot of the entire editor state as one big object/map
+
+#include "nvim/context.h"
+#include "nvim/eval/encode.h"
+#include "nvim/ex_docmd.h"
+#include "nvim/option.h"
+#include "nvim/shada.h"
+#include "nvim/api/vim.h"
+#include "nvim/api/private/helpers.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "context.c.generated.h"
+#endif
+
+int kCtxAll = (kCtxRegs | kCtxJumps | kCtxBuflist | kCtxGVars | kCtxSFuncs
+ | kCtxFuncs);
+
+static ContextVec ctx_stack = KV_INITIAL_VALUE;
+
+/// Clears and frees the context stack
+void ctx_free_all(void)
+{
+ for (size_t i = 0; i < kv_size(ctx_stack); i++) {
+ ctx_free(&kv_A(ctx_stack, i));
+ }
+ kv_destroy(ctx_stack);
+}
+
+/// Returns the size of the context stack.
+size_t ctx_size(void)
+{
+ return kv_size(ctx_stack);
+}
+
+/// Returns pointer to Context object with given zero-based index from the top
+/// of context stack or NULL if index is out of bounds.
+Context *ctx_get(size_t index)
+{
+ if (index < kv_size(ctx_stack)) {
+ return &kv_Z(ctx_stack, index);
+ }
+ return NULL;
+}
+
+/// Free resources used by Context object.
+///
+/// param[in] ctx pointer to Context object to free.
+void ctx_free(Context *ctx)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (ctx->regs.data) {
+ msgpack_sbuffer_destroy(&ctx->regs);
+ }
+ if (ctx->jumps.data) {
+ msgpack_sbuffer_destroy(&ctx->jumps);
+ }
+ if (ctx->buflist.data) {
+ msgpack_sbuffer_destroy(&ctx->buflist);
+ }
+ if (ctx->gvars.data) {
+ msgpack_sbuffer_destroy(&ctx->gvars);
+ }
+ if (ctx->funcs.items) {
+ api_free_array(ctx->funcs);
+ }
+}
+
+/// Saves the editor state to a context.
+///
+/// If "context" is NULL, pushes context on context stack.
+/// Use "flags" to select particular types of context.
+///
+/// @param ctx Save to this context, or push on context stack if NULL.
+/// @param flags Flags, see ContextTypeFlags enum.
+void ctx_save(Context *ctx, const int flags)
+{
+ if (ctx == NULL) {
+ kv_push(ctx_stack, CONTEXT_INIT);
+ ctx = &kv_last(ctx_stack);
+ }
+
+ if (flags & kCtxRegs) {
+ ctx_save_regs(ctx);
+ }
+
+ if (flags & kCtxJumps) {
+ ctx_save_jumps(ctx);
+ }
+
+ if (flags & kCtxBuflist) {
+ ctx_save_buflist(ctx);
+ }
+
+ if (flags & kCtxGVars) {
+ ctx_save_gvars(ctx);
+ }
+
+ if (flags & kCtxFuncs) {
+ ctx_save_funcs(ctx, false);
+ } else if (flags & kCtxSFuncs) {
+ ctx_save_funcs(ctx, true);
+ }
+}
+
+/// Restores the editor state from a context.
+///
+/// If "context" is NULL, pops context from context stack.
+/// Use "flags" to select particular types of context.
+///
+/// @param ctx Restore from this context. Pop from context stack if NULL.
+/// @param flags Flags, see ContextTypeFlags enum.
+///
+/// @return true on success, false otherwise (i.e.: empty context stack).
+bool ctx_restore(Context *ctx, const int flags)
+{
+ bool free_ctx = false;
+ if (ctx == NULL) {
+ if (ctx_stack.size == 0) {
+ return false;
+ }
+ ctx = &kv_pop(ctx_stack);
+ free_ctx = true;
+ }
+
+ char_u *op_shada;
+ get_option_value((char_u *)"shada", NULL, &op_shada, OPT_GLOBAL);
+ set_option_value("shada", 0L, "!,'100,%", OPT_GLOBAL);
+
+ if (flags & kCtxRegs) {
+ ctx_restore_regs(ctx);
+ }
+
+ if (flags & kCtxJumps) {
+ ctx_restore_jumps(ctx);
+ }
+
+ if (flags & kCtxBuflist) {
+ ctx_restore_buflist(ctx);
+ }
+
+ if (flags & kCtxGVars) {
+ ctx_restore_gvars(ctx);
+ }
+
+ if (flags & kCtxFuncs) {
+ ctx_restore_funcs(ctx);
+ }
+
+ if (free_ctx) {
+ ctx_free(ctx);
+ }
+
+ set_option_value("shada", 0L, (char *)op_shada, OPT_GLOBAL);
+ xfree(op_shada);
+
+ return true;
+}
+
+/// Saves the global registers to a context.
+///
+/// @param ctx Save to this context.
+static inline void ctx_save_regs(Context *ctx)
+ FUNC_ATTR_NONNULL_ALL
+{
+ msgpack_sbuffer_init(&ctx->regs);
+ shada_encode_regs(&ctx->regs);
+}
+
+/// Restores the global registers from a context.
+///
+/// @param ctx Restore from this context.
+static inline void ctx_restore_regs(Context *ctx)
+ FUNC_ATTR_NONNULL_ALL
+{
+ shada_read_sbuf(&ctx->regs, kShaDaWantInfo | kShaDaForceit);
+}
+
+/// Saves the jumplist to a context.
+///
+/// @param ctx Save to this context.
+static inline void ctx_save_jumps(Context *ctx)
+ FUNC_ATTR_NONNULL_ALL
+{
+ msgpack_sbuffer_init(&ctx->jumps);
+ shada_encode_jumps(&ctx->jumps);
+}
+
+/// Restores the jumplist from a context.
+///
+/// @param ctx Restore from this context.
+static inline void ctx_restore_jumps(Context *ctx)
+ FUNC_ATTR_NONNULL_ALL
+{
+ shada_read_sbuf(&ctx->jumps, kShaDaWantInfo | kShaDaForceit);
+}
+
+/// Saves the buffer list to a context.
+///
+/// @param ctx Save to this context.
+static inline void ctx_save_buflist(Context *ctx)
+ FUNC_ATTR_NONNULL_ALL
+{
+ msgpack_sbuffer_init(&ctx->buflist);
+ shada_encode_buflist(&ctx->buflist);
+}
+
+/// Restores the buffer list from a context.
+///
+/// @param ctx Restore from this context.
+static inline void ctx_restore_buflist(Context *ctx)
+ FUNC_ATTR_NONNULL_ALL
+{
+ shada_read_sbuf(&ctx->buflist, kShaDaWantInfo | kShaDaForceit);
+}
+
+/// Saves global variables to a context.
+///
+/// @param ctx Save to this context.
+static inline void ctx_save_gvars(Context *ctx)
+ FUNC_ATTR_NONNULL_ALL
+{
+ msgpack_sbuffer_init(&ctx->gvars);
+ shada_encode_gvars(&ctx->gvars);
+}
+
+/// Restores global variables from a context.
+///
+/// @param ctx Restore from this context.
+static inline void ctx_restore_gvars(Context *ctx)
+ FUNC_ATTR_NONNULL_ALL
+{
+ shada_read_sbuf(&ctx->gvars, kShaDaWantInfo | kShaDaForceit);
+}
+
+/// Saves functions to a context.
+///
+/// @param ctx Save to this context.
+/// @param scriptonly Save script-local (s:) functions only.
+static inline void ctx_save_funcs(Context *ctx, bool scriptonly)
+ FUNC_ATTR_NONNULL_ALL
+{
+ ctx->funcs = (Array)ARRAY_DICT_INIT;
+ Error err = ERROR_INIT;
+
+ HASHTAB_ITER(&func_hashtab, hi, {
+ const char_u *const name = hi->hi_key;
+ bool islambda = (STRNCMP(name, "<lambda>", 8) == 0);
+ bool isscript = (name[0] == K_SPECIAL);
+
+ if (!islambda && (!scriptonly || isscript)) {
+ size_t cmd_len = sizeof("func! ") + STRLEN(name);
+ char *cmd = xmalloc(cmd_len);
+ snprintf(cmd, cmd_len, "func! %s", name);
+ String func_body = nvim_command_output(cstr_as_string(cmd), &err);
+ xfree(cmd);
+ if (!ERROR_SET(&err)) {
+ ADD(ctx->funcs, STRING_OBJ(func_body));
+ }
+ api_clear_error(&err);
+ }
+ });
+}
+
+/// Restores functions from a context.
+///
+/// @param ctx Restore from this context.
+static inline void ctx_restore_funcs(Context *ctx)
+ FUNC_ATTR_NONNULL_ALL
+{
+ for (size_t i = 0; i < ctx->funcs.size; i++) {
+ do_cmdline_cmd(ctx->funcs.items[i].data.string.data);
+ }
+}
+
+/// Convert msgpack_sbuffer to readfile()-style array.
+///
+/// @param[in] sbuf msgpack_sbuffer to convert.
+///
+/// @return readfile()-style array representation of "sbuf".
+static inline Array sbuf_to_array(msgpack_sbuffer sbuf)
+{
+ list_T *const list = tv_list_alloc(kListLenMayKnow);
+ tv_list_append_string(list, "", 0);
+ if (sbuf.size > 0) {
+ encode_list_write(list, sbuf.data, sbuf.size);
+ }
+
+ typval_T list_tv = (typval_T) {
+ .v_lock = VAR_UNLOCKED,
+ .v_type = VAR_LIST,
+ .vval.v_list = list
+ };
+
+ Array array = vim_to_object(&list_tv).data.array;
+ tv_clear(&list_tv);
+ return array;
+}
+
+/// Convert readfile()-style array to msgpack_sbuffer.
+///
+/// @param[in] array readfile()-style array to convert.
+///
+/// @return msgpack_sbuffer with conversion result.
+static inline msgpack_sbuffer array_to_sbuf(Array array)
+{
+ msgpack_sbuffer sbuf;
+ msgpack_sbuffer_init(&sbuf);
+
+ typval_T list_tv;
+ Error err = ERROR_INIT;
+ object_to_vim(ARRAY_OBJ(array), &list_tv, &err);
+
+ if (!encode_vim_list_to_buf(list_tv.vval.v_list, &sbuf.size, &sbuf.data)) {
+ EMSG(_("E474: Failed to convert list to msgpack string buffer"));
+ }
+ sbuf.alloc = sbuf.size;
+
+ tv_clear(&list_tv);
+ api_clear_error(&err);
+ return sbuf;
+}
+
+/// Converts Context to Dictionary representation.
+///
+/// @param[in] ctx Context to convert.
+///
+/// @return Dictionary representing "ctx".
+Dictionary ctx_to_dict(Context *ctx)
+ FUNC_ATTR_NONNULL_ALL
+{
+ assert(ctx != NULL);
+
+ Dictionary rv = ARRAY_DICT_INIT;
+
+ PUT(rv, "regs", ARRAY_OBJ(sbuf_to_array(ctx->regs)));
+ PUT(rv, "jumps", ARRAY_OBJ(sbuf_to_array(ctx->jumps)));
+ PUT(rv, "buflist", ARRAY_OBJ(sbuf_to_array(ctx->buflist)));
+ PUT(rv, "gvars", ARRAY_OBJ(sbuf_to_array(ctx->gvars)));
+ PUT(rv, "funcs", ARRAY_OBJ(copy_array(ctx->funcs)));
+
+ return rv;
+}
+
+/// Converts Dictionary representation of Context back to Context object.
+///
+/// @param[in] dict Context Dictionary representation.
+/// @param[out] ctx Context object to store conversion result into.
+///
+/// @return types of included context items.
+int ctx_from_dict(Dictionary dict, Context *ctx)
+ FUNC_ATTR_NONNULL_ALL
+{
+ assert(ctx != NULL);
+
+ int types = 0;
+ for (size_t i = 0; i < dict.size; i++) {
+ KeyValuePair item = dict.items[i];
+ if (item.value.type != kObjectTypeArray) {
+ continue;
+ }
+ if (strequal(item.key.data, "regs")) {
+ types |= kCtxRegs;
+ ctx->regs = array_to_sbuf(item.value.data.array);
+ } else if (strequal(item.key.data, "jumps")) {
+ types |= kCtxJumps;
+ ctx->jumps = array_to_sbuf(item.value.data.array);
+ } else if (strequal(item.key.data, "buflist")) {
+ types |= kCtxBuflist;
+ ctx->buflist = array_to_sbuf(item.value.data.array);
+ } else if (strequal(item.key.data, "gvars")) {
+ types |= kCtxGVars;
+ ctx->gvars = array_to_sbuf(item.value.data.array);
+ } else if (strequal(item.key.data, "funcs")) {
+ types |= kCtxFuncs;
+ ctx->funcs = copy_object(item.value).data.array;
+ }
+ }
+
+ return types;
+}
diff --git a/src/nvim/context.h b/src/nvim/context.h
new file mode 100644
index 0000000000..328e12c6a6
--- /dev/null
+++ b/src/nvim/context.h
@@ -0,0 +1,46 @@
+#ifndef NVIM_CONTEXT_H
+#define NVIM_CONTEXT_H
+
+#include <msgpack.h>
+#include "nvim/api/private/defs.h"
+#include "nvim/lib/kvec.h"
+
+typedef struct {
+ msgpack_sbuffer regs; ///< Registers.
+ msgpack_sbuffer jumps; ///< Jumplist.
+ msgpack_sbuffer buflist; ///< Buffer list.
+ msgpack_sbuffer gvars; ///< Global variables.
+ Array funcs; ///< Functions.
+} Context;
+typedef kvec_t(Context) ContextVec;
+
+#define MSGPACK_SBUFFER_INIT (msgpack_sbuffer) { \
+ .size = 0, \
+ .data = NULL, \
+ .alloc = 0, \
+}
+
+#define CONTEXT_INIT (Context) { \
+ .regs = MSGPACK_SBUFFER_INIT, \
+ .jumps = MSGPACK_SBUFFER_INIT, \
+ .buflist = MSGPACK_SBUFFER_INIT, \
+ .gvars = MSGPACK_SBUFFER_INIT, \
+ .funcs = ARRAY_DICT_INIT, \
+}
+
+typedef enum {
+ kCtxRegs = 1, ///< Registers
+ kCtxJumps = 2, ///< Jumplist
+ kCtxBuflist = 4, ///< Buffer list
+ kCtxGVars = 8, ///< Global variables
+ kCtxSFuncs = 16, ///< Script functions
+ kCtxFuncs = 32, ///< Functions
+} ContextTypeFlags;
+
+extern int kCtxAll;
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "context.h.generated.h"
+#endif
+
+#endif // NVIM_CONTEXT_H
diff --git a/src/nvim/cursor.c b/src/nvim/cursor.c
index bc14761877..f2b3cfe690 100644
--- a/src/nvim/cursor.c
+++ b/src/nvim/cursor.c
@@ -5,6 +5,7 @@
#include <inttypes.h>
#include "nvim/assert.h"
+#include "nvim/change.h"
#include "nvim/cursor.h"
#include "nvim/charset.h"
#include "nvim/fold.h"
diff --git a/src/nvim/diff.c b/src/nvim/diff.c
index f720e702a4..7328b88a40 100644
--- a/src/nvim/diff.c
+++ b/src/nvim/diff.c
@@ -18,6 +18,7 @@
#include "nvim/ascii.h"
#include "nvim/diff.h"
#include "nvim/buffer.h"
+#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/eval.h"
@@ -951,7 +952,7 @@ static int check_external_diff(diffio_T *diffio)
TriState ok = kFalse;
for (;;) {
ok = kFalse;
- FILE *fd = mch_fopen((char *)diffio->dio_orig.din_fname, "w");
+ FILE *fd = os_fopen((char *)diffio->dio_orig.din_fname, "w");
if (fd == NULL) {
io_error = true;
@@ -960,7 +961,7 @@ static int check_external_diff(diffio_T *diffio)
io_error = true;
}
fclose(fd);
- fd = mch_fopen((char *)diffio->dio_new.din_fname, "w");
+ fd = os_fopen((char *)diffio->dio_new.din_fname, "w");
if (fd == NULL) {
io_error = true;
@@ -971,7 +972,7 @@ static int check_external_diff(diffio_T *diffio)
fclose(fd);
fd = NULL;
if (diff_file(diffio) == OK) {
- fd = mch_fopen((char *)diffio->dio_diff.dout_fname, "r");
+ fd = os_fopen((char *)diffio->dio_diff.dout_fname, "r");
}
if (fd == NULL) {
@@ -1505,7 +1506,7 @@ static void diff_read(int idx_orig, int idx_new, diffout_T *dout)
if (dout->dout_fname == NULL) {
diffstyle = DIFF_UNIFIED;
} else {
- fd = mch_fopen((char *)dout->dout_fname, "r");
+ fd = os_fopen((char *)dout->dout_fname, "r");
if (fd == NULL) {
EMSG(_("E98: Cannot read diff output"));
return;
@@ -2190,7 +2191,7 @@ int diffopt_changed(void)
}
diff_flags = diff_flags_new;
- diff_context = diff_context_new;
+ diff_context = diff_context_new == 0 ? 1 : diff_context_new;
diff_foldcolumn = diff_foldcolumn_new;
diff_algorithm = diff_algorithm_new;
diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c
index 3329290634..d47ad3f1c0 100644
--- a/src/nvim/digraph.c
+++ b/src/nvim/digraph.c
@@ -18,6 +18,7 @@
#include "nvim/ex_docmd.h"
#include "nvim/ex_getln.h"
#include "nvim/getchar.h"
+#include "nvim/misc1.h"
#include "nvim/mbyte.h"
#include "nvim/message.h"
#include "nvim/memory.h"
@@ -1496,8 +1497,8 @@ int get_digraph(int cmdline)
}
if (cmdline) {
- if ((char2cells(c) == 1) && (cmdline_star == 0)) {
- putcmdline(c, TRUE);
+ if ((char2cells(c) == 1) && c < 128 && (cmdline_star == 0)) {
+ putcmdline((char)c, true);
}
} else {
add_to_showcmd(c);
@@ -1663,13 +1664,13 @@ void listdigraphs(void)
printdigraph(&tmp);
}
dp++;
- os_breakcheck();
+ fast_breakcheck();
}
dp = (digr_T *)user_digraphs.ga_data;
for (int i = 0; i < user_digraphs.ga_len && !got_int; ++i) {
printdigraph(dp);
- os_breakcheck();
+ fast_breakcheck();
dp++;
}
}
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index 9af003f140..2ac429cf9e 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -14,6 +14,7 @@
#include "nvim/ascii.h"
#include "nvim/edit.h"
#include "nvim/buffer.h"
+#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/digraph.h"
@@ -64,6 +65,7 @@
#define CTRL_X_WANT_IDENT 0x100
+#define CTRL_X_NORMAL 0 ///< CTRL-N CTRL-P completion, default
#define CTRL_X_NOT_DEFINED_YET 1
#define CTRL_X_SCROLL 2
#define CTRL_X_WHOLE_LINE 3
@@ -78,11 +80,12 @@
#define CTRL_X_FUNCTION 12
#define CTRL_X_OMNI 13
#define CTRL_X_SPELL 14
-#define CTRL_X_LOCAL_MSG 15 /* only used in "ctrl_x_msgs" */
+#define CTRL_X_LOCAL_MSG 15 ///< only used in "ctrl_x_msgs"
#define CTRL_X_EVAL 16 ///< for builtin function complete()
#define CTRL_X_MSG(i) ctrl_x_msgs[(i) & ~CTRL_X_WANT_IDENT]
-#define CTRL_X_MODE_LINE_OR_EVAL(m) (m == CTRL_X_WHOLE_LINE || m == CTRL_X_EVAL)
+#define CTRL_X_MODE_LINE_OR_EVAL(m) \
+ ((m) == CTRL_X_WHOLE_LINE || (m) == CTRL_X_EVAL)
// Message for CTRL-X mode, index is ctrl_x_mode.
static char *ctrl_x_msgs[] =
@@ -192,6 +195,9 @@ static int compl_restarting = FALSE; /* don't insert match */
* FALSE the word to be completed must be located. */
static int compl_started = FALSE;
+// Which Ctrl-X mode are we in?
+static int ctrl_x_mode = CTRL_X_NORMAL;
+
static int compl_matches = 0;
static char_u *compl_pattern = NULL;
static int compl_direction = FORWARD;
@@ -276,7 +282,7 @@ static int ins_need_undo; /* call u_save() before inserting a
static bool did_add_space = false; // auto_format() added an extra space
// under the cursor
static TriState dont_sync_undo = kFalse; // CTRL-G U prevents syncing undo
- // for the next left/right cursor
+ // for the next left/right cursor key
static linenr_T o_lnum = 0;
@@ -509,7 +515,7 @@ static int insert_check(VimState *state)
// If typed something may trigger CursorHoldI again.
if (s->c != K_EVENT
// but not in CTRL-X mode, a script can't restore the state
- && ctrl_x_mode == 0) {
+ && ctrl_x_mode == CTRL_X_NORMAL) {
did_cursorhold = false;
}
@@ -518,7 +524,10 @@ static int insert_check(VimState *state)
s->inserted_space = false;
}
- if (can_cindent && cindent_on() && ctrl_x_mode == 0 && !compl_started) {
+ if (can_cindent
+ && cindent_on()
+ && ctrl_x_mode == CTRL_X_NORMAL
+ && !compl_started) {
insert_do_cindent(s);
}
@@ -641,7 +650,9 @@ static int insert_execute(VimState *state, int key)
s->c = key;
// Don't want K_EVENT with cursorhold for the second key, e.g., after CTRL-V.
- did_cursorhold = true;
+ if (key != K_EVENT) {
+ did_cursorhold = true;
+ }
if (p_hkmap && KeyTyped) {
s->c = hkmap(s->c); // Hebrew mode mapping
@@ -1037,7 +1048,7 @@ check_pum:
if (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL)) {
ins_s_left();
} else {
- ins_left(dont_sync_undo == kFalse);
+ ins_left();
}
break;
@@ -1050,7 +1061,7 @@ check_pum:
if (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL)) {
ins_s_right();
} else {
- ins_right(dont_sync_undo == kFalse);
+ ins_right();
}
break;
@@ -1204,7 +1215,8 @@ check_pum:
// if 'complete' is empty then plain ^P is no longer special,
// but it is under other ^X modes
if (*curbuf->b_p_cpt == NUL
- && ctrl_x_mode != 0
+ && (ctrl_x_mode == CTRL_X_NORMAL
+ || ctrl_x_mode == CTRL_X_WHOLE_LINE)
&& !(compl_cont_status & CONT_LOCAL)) {
goto normalchar;
}
@@ -1921,6 +1933,19 @@ static void ins_ctrl_x(void)
}
}
+// Whether other than default completion has been selected.
+bool ctrl_x_mode_not_default(void)
+{
+ return ctrl_x_mode != CTRL_X_NORMAL;
+}
+
+// Whether CTRL-X was typed without a following character.
+bool ctrl_x_mode_not_defined_yet(void)
+{
+ return ctrl_x_mode == CTRL_X_NOT_DEFINED_YET;
+}
+
+
/// Check that the "dict" or "tsr" option can be used.
///
/// @param dict_opt check "dict" when true, "tsr" when false.
@@ -1929,7 +1954,7 @@ static bool check_compl_option(bool dict_opt)
if (dict_opt
? (*curbuf->b_p_dict == NUL && *p_dict == NUL && !curwin->w_p_spell)
: (*curbuf->b_p_tsr == NUL && *p_tsr == NUL)) {
- ctrl_x_mode = 0;
+ ctrl_x_mode = CTRL_X_NORMAL;
edit_submode = NULL;
msg_attr((dict_opt
? _("'dictionary' option is empty")
@@ -2460,7 +2485,7 @@ void completeopt_was_set(void)
void set_completion(colnr_T startcol, list_T *list)
{
// If already doing completions stop it.
- if (ctrl_x_mode != 0) {
+ if (ctrl_x_mode != CTRL_X_NORMAL) {
ins_compl_prep(' ');
}
ins_compl_clear();
@@ -2556,10 +2581,35 @@ static bool pum_enough_matches(void)
return i >= 2;
}
-/*
- * Show the popup menu for the list of matches.
- * Also adjusts "compl_shown_match" to an entry that is actually displayed.
- */
+static void trigger_complete_changed_event(int cur)
+{
+ static bool recursive = false;
+
+ if (recursive) {
+ return;
+ }
+
+ dict_T *v_event = get_vim_var_dict(VV_EVENT);
+ if (cur < 0) {
+ tv_dict_add_dict(v_event, S_LEN("completed_item"), tv_dict_alloc());
+ } else {
+ dict_T *item = ins_compl_dict_alloc(compl_curr_match);
+ tv_dict_add_dict(v_event, S_LEN("completed_item"), item);
+ }
+ pum_set_event_info(v_event);
+ tv_dict_set_keys_readonly(v_event);
+
+ recursive = true;
+ textlock++;
+ apply_autocmds(EVENT_COMPLETECHANGED, NULL, NULL, false, curbuf);
+ textlock--;
+ recursive = false;
+
+ tv_dict_clear(v_event);
+}
+
+/// Show the popup menu for the list of matches.
+/// Also adjusts "compl_shown_match" to an entry that is actually displayed.
void ins_compl_show_pum(void)
{
compl_T *compl;
@@ -2691,22 +2741,9 @@ void ins_compl_show_pum(void)
pum_display(compl_match_array, compl_match_arraysize, cur, array_changed, 0);
curwin->w_cursor.col = col;
- if (!has_event(EVENT_COMPLETECHANGED)) {
- return;
- }
- dict_T *dict = get_vim_var_dict(VV_EVENT);
- if (cur < 0) {
- tv_dict_add_dict(dict, S_LEN("completed_item"), tv_dict_alloc());
- } else {
- dict_T *item = ins_compl_dict_alloc(compl_curr_match);
- tv_dict_add_dict(dict, S_LEN("completed_item"), item);
+ if (has_event(EVENT_COMPLETECHANGED)) {
+ trigger_complete_changed_event(cur);
}
- pum_set_boundings(dict);
- tv_dict_set_keys_readonly(dict);
- textlock++;
- apply_autocmds(EVENT_COMPLETECHANGED, NULL, NULL, false, curbuf);
- textlock--;
- tv_dict_clear(dict);
}
#define DICT_FIRST (1) /* use just first element in "dict" */
@@ -2820,7 +2857,7 @@ static void ins_compl_files(int count, char_u **files, int thesaurus, int flags,
int add_r;
for (i = 0; i < count && !got_int && !compl_interrupted; i++) {
- fp = mch_fopen((char *)files[i], "r"); /* open dictionary file */
+ fp = os_fopen((char *)files[i], "r"); // open dictionary file
if (flags != DICT_EXACT) {
vim_snprintf((char *)IObuff, IOSIZE,
_("Scanning dictionary: %s"), (char *)files[i]);
@@ -3086,6 +3123,7 @@ void get_complete_info(list_T *what_list, dict_T *retdict)
? compl_curr_match->cp_number - 1 : -1);
}
+ (void)ret;
// TODO(vim):
// if (ret == OK && (what_flag & CI_WHAT_INSERTED))
}
@@ -3331,7 +3369,7 @@ static bool ins_compl_prep(int c)
/* Set "compl_get_longest" when finding the first matches. */
if (ctrl_x_mode == CTRL_X_NOT_DEFINED_YET
- || (ctrl_x_mode == 0 && !compl_started)) {
+ || (ctrl_x_mode == CTRL_X_NORMAL && !compl_started)) {
compl_get_longest = (strstr((char *)p_cot, "longest") != NULL);
compl_used_match = TRUE;
@@ -3426,18 +3464,19 @@ static bool ins_compl_prep(int c)
else
compl_cont_mode = CTRL_X_NOT_DEFINED_YET;
}
- ctrl_x_mode = 0;
+ ctrl_x_mode = CTRL_X_NORMAL;
edit_submode = NULL;
showmode();
break;
}
- } else if (ctrl_x_mode != 0) {
- /* We're already in CTRL-X mode, do we stay in it? */
+ } else if (ctrl_x_mode != CTRL_X_NORMAL) {
+ // We're already in CTRL-X mode, do we stay in it?
if (!vim_is_ctrl_x_key(c)) {
- if (ctrl_x_mode == CTRL_X_SCROLL)
- ctrl_x_mode = 0;
- else
+ if (ctrl_x_mode == CTRL_X_SCROLL) {
+ ctrl_x_mode = CTRL_X_NORMAL;
+ } else {
ctrl_x_mode = CTRL_X_FINISHED;
+ }
edit_submode = NULL;
}
showmode();
@@ -3448,7 +3487,10 @@ static bool ins_compl_prep(int c)
* 'Pattern not found') until another key is hit, then go back to
* showing what mode we are in. */
showmode();
- if ((ctrl_x_mode == 0 && c != Ctrl_N && c != Ctrl_P && c != Ctrl_R
+ if ((ctrl_x_mode == CTRL_X_NORMAL
+ && c != Ctrl_N
+ && c != Ctrl_P
+ && c != Ctrl_R
&& !ins_compl_pum_key(c))
|| ctrl_x_mode == CTRL_X_FINISHED) {
/* Get here when we have finished typing a sequence of ^N and
@@ -3526,8 +3568,8 @@ static bool ins_compl_prep(int c)
if (!shortmess(SHM_COMPLETIONMENU)) {
msg_clr_cmdline(); // necessary for "noshowmode"
}
- ctrl_x_mode = 0;
- compl_enter_selects = FALSE;
+ ctrl_x_mode = CTRL_X_NORMAL;
+ compl_enter_selects = false;
if (edit_submode != NULL) {
edit_submode = NULL;
showmode();
@@ -3654,14 +3696,19 @@ expand_by_function (
return;
// Call 'completefunc' to obtain the list of matches.
- const char_u *const args[2] = { (char_u *)"0", base };
+ typval_T args[3];
+ args[0].v_type = VAR_NUMBER;
+ args[1].v_type = VAR_STRING;
+ args[2].v_type = VAR_UNKNOWN;
+ args[0].vval.v_number = 0;
+ args[1].vval.v_string = base != NULL ? base : (char_u *)"";
pos = curwin->w_cursor;
curwin_save = curwin;
curbuf_save = curbuf;
- /* Call a function, which returns a list or dict. */
- if (call_vim_function(funcname, 2, args, FALSE, FALSE, &rettv) == OK) {
+ // Call a function, which returns a list or dict.
+ if (call_vim_function(funcname, 2, args, &rettv, false) == OK) {
switch (rettv.v_type) {
case VAR_LIST:
matchlist = rettv.vval.v_list;
@@ -3838,11 +3885,14 @@ static int ins_compl_get_exp(pos_T *ini)
e_cpt = (compl_cont_status & CONT_LOCAL)
? (char_u *)"." : curbuf->b_p_cpt;
last_match_pos = first_match_pos = *ini;
+ } else if (ins_buf != curbuf && !buf_valid(ins_buf)) {
+ ins_buf = curbuf; // In case the buffer was wiped out.
}
compl_old_match = compl_curr_match; // remember the last current match
pos = (compl_direction == FORWARD) ? &last_match_pos : &first_match_pos;
- /* For ^N/^P loop over all the flags/windows/buffers in 'complete' */
+
+ // For ^N/^P loop over all the flags/windows/buffers in 'complete'
for (;; ) {
found_new_match = FAIL;
set_match_pos = FALSE;
@@ -3852,7 +3902,8 @@ static int ins_compl_get_exp(pos_T *ini)
/* For ^N/^P pick a new entry from e_cpt if compl_started is off,
* or if found_all says this entry is done. For ^X^L only use the
* entries from 'complete' that look in loaded buffers. */
- if ((l_ctrl_x_mode == 0 || CTRL_X_MODE_LINE_OR_EVAL(l_ctrl_x_mode))
+ if ((l_ctrl_x_mode == CTRL_X_NORMAL
+ || CTRL_X_MODE_LINE_OR_EVAL(l_ctrl_x_mode))
&& (!compl_started || found_all)) {
found_all = FALSE;
while (*e_cpt == ',' || *e_cpt == ' ')
@@ -3862,7 +3913,7 @@ static int ins_compl_get_exp(pos_T *ini)
first_match_pos = *ini;
// Move the cursor back one character so that ^N can match the
// word immediately after the cursor.
- if (ctrl_x_mode == 0 && dec(&first_match_pos) < 0) {
+ if (ctrl_x_mode == CTRL_X_NORMAL && dec(&first_match_pos) < 0) {
// Move the cursor to after the last character in the
// buffer, so that word at start of buffer is found
// correctly.
@@ -3980,9 +4031,9 @@ static int ins_compl_get_exp(pos_T *ini)
/* Find up to TAG_MANY matches. Avoids that an enormous number
* of matches is found when compl_pattern is empty */
if (find_tags(compl_pattern, &num_matches, &matches,
- TAG_REGEXP | TAG_NAMES | TAG_NOIC |
- TAG_INS_COMP | (l_ctrl_x_mode ? TAG_VERBOSE : 0),
- TAG_MANY, curbuf->b_ffname) == OK && num_matches > 0) {
+ TAG_REGEXP | TAG_NAMES | TAG_NOIC | TAG_INS_COMP
+ | (l_ctrl_x_mode != CTRL_X_NORMAL ? TAG_VERBOSE : 0),
+ TAG_MANY, curbuf->b_ffname) == OK && num_matches > 0) {
ins_compl_add_matches(num_matches, matches, p_ic);
}
p_ic = save_p_ic;
@@ -4156,9 +4207,10 @@ static int ins_compl_get_exp(pos_T *ini)
found_new_match = OK;
}
- /* break the loop for specialized modes (use 'complete' just for the
- * generic l_ctrl_x_mode == 0) or when we've found a new match */
- if ((l_ctrl_x_mode != 0 && !CTRL_X_MODE_LINE_OR_EVAL(l_ctrl_x_mode))
+ // break the loop for specialized modes (use 'complete' just for the
+ // generic l_ctrl_x_mode == CTRL_X_NORMAL) or when we've found a new match
+ if ((l_ctrl_x_mode != CTRL_X_NORMAL
+ && !CTRL_X_MODE_LINE_OR_EVAL(l_ctrl_x_mode))
|| found_new_match != FAIL) {
if (got_int)
break;
@@ -4166,7 +4218,8 @@ static int ins_compl_get_exp(pos_T *ini)
if (type != -1)
ins_compl_check_keys(0, false);
- if ((l_ctrl_x_mode != 0 && !CTRL_X_MODE_LINE_OR_EVAL(l_ctrl_x_mode))
+ if ((l_ctrl_x_mode != CTRL_X_NORMAL
+ && !CTRL_X_MODE_LINE_OR_EVAL(l_ctrl_x_mode))
|| compl_interrupted) {
break;
}
@@ -4183,14 +4236,16 @@ static int ins_compl_get_exp(pos_T *ini)
}
compl_started = TRUE;
- if ((l_ctrl_x_mode == 0 || CTRL_X_MODE_LINE_OR_EVAL(l_ctrl_x_mode))
+ if ((l_ctrl_x_mode == CTRL_X_NORMAL
+ || CTRL_X_MODE_LINE_OR_EVAL(l_ctrl_x_mode))
&& *e_cpt == NUL) { // Got to end of 'complete'
found_new_match = FAIL;
}
i = -1; /* total of matches, unknown */
if (found_new_match == FAIL
- || (l_ctrl_x_mode != 0 && !CTRL_X_MODE_LINE_OR_EVAL(l_ctrl_x_mode))) {
+ || (l_ctrl_x_mode != CTRL_X_NORMAL
+ && !CTRL_X_MODE_LINE_OR_EVAL(l_ctrl_x_mode))) {
i = ins_compl_make_cyclic();
}
@@ -4505,12 +4560,12 @@ void ins_compl_check_keys(int frequency, int in_compl_func)
{
static int count = 0;
- int c;
-
- /* Don't check when reading keys from a script. That would break the test
- * scripts */
- if (using_script())
+ // Don't check when reading keys from a script, :normal or feedkeys().
+ // That would break the test scripts. But do check for keys when called
+ // from complete_check().
+ if (!in_compl_func && (using_script() || ex_normal_busy)) {
return;
+ }
/* Only do this at regular intervals */
if (++count < frequency)
@@ -4519,7 +4574,7 @@ void ins_compl_check_keys(int frequency, int in_compl_func)
/* Check for a typed key. Do use mappings, otherwise vim_is_ctrl_x_key()
* can't do its work correctly. */
- c = vpeekc_any();
+ 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 */
@@ -4668,8 +4723,9 @@ static int ins_complete(int c, bool enable_pum)
/*
* it is a continued search
*/
- compl_cont_status &= ~CONT_INTRPT; /* remove INTRPT */
- if (ctrl_x_mode == 0 || ctrl_x_mode == CTRL_X_PATH_PATTERNS
+ compl_cont_status &= ~CONT_INTRPT; // remove INTRPT
+ if (ctrl_x_mode == CTRL_X_NORMAL
+ || ctrl_x_mode == CTRL_X_PATH_PATTERNS
|| ctrl_x_mode == CTRL_X_PATH_DEFINES) {
if (compl_startpos.lnum != curwin->w_cursor.lnum) {
/* line (probably) wrapped, set compl_startpos to the
@@ -4713,16 +4769,18 @@ static int ins_complete(int c, bool enable_pum)
if (!(compl_cont_status & CONT_ADDING)) { /* normal expansion */
compl_cont_mode = ctrl_x_mode;
- if (ctrl_x_mode != 0) /* Remove LOCAL if ctrl_x_mode != 0 */
+ if (ctrl_x_mode != CTRL_X_NORMAL) {
+ // Remove LOCAL if ctrl_x_mode != CTRL_X_NORMAL
compl_cont_status = 0;
+ }
compl_cont_status |= CONT_N_ADDS;
compl_startpos = curwin->w_cursor;
startcol = (int)curs_col;
compl_col = 0;
}
- /* Work out completion pattern and original text -- webb */
- if (ctrl_x_mode == 0 || (ctrl_x_mode & CTRL_X_WANT_IDENT)) {
+ // Work out completion pattern and original text -- webb
+ if (ctrl_x_mode == CTRL_X_NORMAL || (ctrl_x_mode & CTRL_X_WANT_IDENT)) {
if ((compl_cont_status & CONT_SOL)
|| ctrl_x_mode == CTRL_X_PATH_DEFINES) {
if (!(compl_cont_status & CONT_ADDING)) {
@@ -4853,7 +4911,13 @@ static int ins_complete(int c, bool enable_pum)
return FAIL;
}
- const char_u *const args[2] = { (char_u *)"1", NULL };
+ typval_T args[3];
+ args[0].v_type = VAR_NUMBER;
+ args[1].v_type = VAR_STRING;
+ args[2].v_type = VAR_UNKNOWN;
+ args[0].vval.v_number = 1;
+ args[1].vval.v_string = (char_u *)"";
+
pos = curwin->w_cursor;
curwin_save = curwin;
curbuf_save = curbuf;
@@ -4877,7 +4941,7 @@ static int ins_complete(int c, bool enable_pum)
if (col == -2)
return FAIL;
if (col == -3) {
- ctrl_x_mode = 0;
+ ctrl_x_mode = CTRL_X_NORMAL;
edit_submode = NULL;
if (!shortmess(SHM_COMPLETIONMENU)) {
msg_clr_cmdline();
@@ -5008,12 +5072,13 @@ static int ins_complete(int c, bool enable_pum)
* because we couldn't expand anything at first place, but if we used
* ^P, ^N, ^X^I or ^X^D we might want to add-expand a single-char-word
* (such as M in M'exico) if not tried already. -- Acevedo */
- if ( compl_length > 1
- || (compl_cont_status & CONT_ADDING)
- || (ctrl_x_mode != 0
- && ctrl_x_mode != CTRL_X_PATH_PATTERNS
- && ctrl_x_mode != CTRL_X_PATH_DEFINES))
+ if (compl_length > 1
+ || (compl_cont_status & CONT_ADDING)
+ || (ctrl_x_mode != CTRL_X_NORMAL
+ && ctrl_x_mode != CTRL_X_PATH_PATTERNS
+ && ctrl_x_mode != CTRL_X_PATH_DEFINES)) {
compl_cont_status &= ~CONT_N_ADDS;
+ }
}
if (compl_curr_match->cp_flags & CONT_S_IPOS)
@@ -8100,9 +8165,10 @@ static void ins_mousescroll(int dir)
-static void ins_left(bool end_change)
+static void ins_left(void)
{
pos_T tpos;
+ const bool end_change = dont_sync_undo == kFalse; // end undoable change
if ((fdo_flags & FDO_HOR) && KeyTyped)
foldOpenCursor();
@@ -8164,23 +8230,31 @@ static void ins_end(int c)
static void ins_s_left(void)
{
- if ((fdo_flags & FDO_HOR) && KeyTyped)
+ const bool end_change = dont_sync_undo == kFalse; // end undoable change
+ if ((fdo_flags & FDO_HOR) && KeyTyped) {
foldOpenCursor();
+ }
undisplay_dollar();
if (curwin->w_cursor.lnum > 1 || curwin->w_cursor.col > 0) {
- start_arrow(&curwin->w_cursor);
+ start_arrow_with_change(&curwin->w_cursor, end_change);
+ if (!end_change) {
+ AppendCharToRedobuff(K_S_LEFT);
+ }
(void)bck_word(1L, false, false);
curwin->w_set_curswant = true;
} else {
vim_beep(BO_CRSR);
}
+ dont_sync_undo = kFalse;
}
/// @param end_change end undoable change
-static void ins_right(bool end_change)
+static void ins_right(void)
{
- if ((fdo_flags & FDO_HOR) && KeyTyped)
+ const bool end_change = dont_sync_undo == kFalse; // end undoable change
+ if ((fdo_flags & FDO_HOR) && KeyTyped) {
foldOpenCursor();
+ }
undisplay_dollar();
if (gchar_cursor() != NUL || virtual_active()) {
start_arrow_with_change(&curwin->w_cursor, end_change);
@@ -8217,17 +8291,23 @@ static void ins_right(bool end_change)
static void ins_s_right(void)
{
- if ((fdo_flags & FDO_HOR) && KeyTyped)
+ const bool end_change = dont_sync_undo == kFalse; // end undoable change
+ if ((fdo_flags & FDO_HOR) && KeyTyped) {
foldOpenCursor();
+ }
undisplay_dollar();
if (curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count
|| gchar_cursor() != NUL) {
- start_arrow(&curwin->w_cursor);
+ start_arrow_with_change(&curwin->w_cursor, end_change);
+ if (!end_change) {
+ AppendCharToRedobuff(K_S_RIGHT);
+ }
(void)fwd_word(1L, false, 0);
curwin->w_set_curswant = true;
} else {
vim_beep(BO_CRSR);
}
+ dont_sync_undo = kFalse;
}
static void
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 942f8d5e09..a3a66a5764 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -24,8 +24,10 @@
#endif
#include "nvim/eval.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"
@@ -176,6 +178,7 @@ 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\"");
@@ -302,15 +305,6 @@ typedef struct {
list_T *fi_list; /* list being used */
} forinfo_T;
-/*
- * enum used by var_flavour()
- */
-typedef enum {
- VAR_FLAVOUR_DEFAULT, /* doesn't start with uppercase */
- VAR_FLAVOUR_SESSION, /* starts with uppercase, some lower */
- VAR_FLAVOUR_SHADA /* all uppercase */
-} var_flavour_T;
-
/* values for vv_flags: */
#define VV_COMPAT 1 /* compatible, also used without "v:" */
#define VV_RO 2 /* read-only */
@@ -533,6 +527,35 @@ const list_T *eval_msgpack_type_lists[] = {
[kMPExt] = NULL,
};
+// Return "n1" divided by "n2", taking care of dividing by zero.
+varnumber_T num_divide(varnumber_T n1, varnumber_T n2)
+ FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ varnumber_T result;
+
+ if (n2 == 0) { // give an error message?
+ if (n1 == 0) {
+ result = VARNUMBER_MIN; // similar to NaN
+ } else if (n1 < 0) {
+ result = -VARNUMBER_MAX;
+ } else {
+ result = VARNUMBER_MAX;
+ }
+ } else {
+ result = n1 / n2;
+ }
+
+ return result;
+}
+
+// Return "n1" modulus "n2", taking care of dividing by zero.
+varnumber_T num_modulus(varnumber_T n1, varnumber_T n2)
+ FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ // Give an error when n2 is 0?
+ return (n2 == 0) ? 0 : (n1 % n2);
+}
+
/*
* Initialize the global and v: variables.
*/
@@ -776,10 +799,11 @@ var_redir_start(
did_emsg = FALSE;
tv.v_type = VAR_STRING;
tv.vval.v_string = (char_u *)"";
- if (append)
- set_var_lval(redir_lval, redir_endp, &tv, TRUE, (char_u *)".");
- else
- set_var_lval(redir_lval, redir_endp, &tv, TRUE, (char_u *)"=");
+ if (append) {
+ set_var_lval(redir_lval, redir_endp, &tv, true, false, (char_u *)".");
+ } else {
+ set_var_lval(redir_lval, redir_endp, &tv, true, false, (char_u *)"=");
+ }
clear_lval(redir_lval);
err = did_emsg;
did_emsg |= save_emsg;
@@ -837,7 +861,7 @@ void var_redir_stop(void)
redir_endp = (char_u *)get_lval(redir_varname, NULL, redir_lval,
false, false, 0, FNE_CHECK_START);
if (redir_endp != NULL && redir_lval->ll_name != NULL) {
- set_var_lval(redir_lval, redir_endp, &tv, false, (char_u *)".");
+ set_var_lval(redir_lval, redir_endp, &tv, false, false, (char_u *)".");
}
clear_lval(redir_lval);
}
@@ -955,6 +979,89 @@ eval_to_bool(
return retval;
}
+// Call eval1() and give an error message if not done at a lower level.
+static int eval1_emsg(char_u **arg, typval_T *rettv, bool evaluate)
+ FUNC_ATTR_NONNULL_ARG(1, 2)
+{
+ const char_u *const start = *arg;
+ const int did_emsg_before = did_emsg;
+ const int called_emsg_before = called_emsg;
+
+ const int ret = eval1(arg, rettv, evaluate);
+ if (ret == FAIL) {
+ // Report the invalid expression unless the expression evaluation has
+ // been cancelled due to an aborting error, an interrupt, or an
+ // exception, or we already gave a more specific error.
+ // Also check called_emsg for when using assert_fails().
+ if (!aborting()
+ && did_emsg == did_emsg_before
+ && called_emsg == called_emsg_before) {
+ emsgf(_(e_invexpr2), start);
+ }
+ }
+ return ret;
+}
+
+static 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;
+
+ if (expr->v_type == VAR_FUNC) {
+ const char_u *const s = expr->vval.v_string;
+ if (s == NULL || *s == NUL) {
+ return FAIL;
+ }
+ if (call_func(s, (int)STRLEN(s), rettv, argc, argv, NULL,
+ 0L, 0L, &dummy, true, NULL, NULL) == FAIL) {
+ return FAIL;
+ }
+ } else if (expr->v_type == VAR_PARTIAL) {
+ partial_T *const partial = expr->vval.v_partial;
+ const char_u *const s = partial_name(partial);
+ if (s == NULL || *s == NUL) {
+ return FAIL;
+ }
+ if (call_func(s, (int)STRLEN(s), rettv, argc, argv, NULL,
+ 0L, 0L, &dummy, true, partial, NULL) == FAIL) {
+ return FAIL;
+ }
+ } else {
+ char buf[NUMBUFLEN];
+ char_u *s = (char_u *)tv_get_string_buf_chk(expr, buf);
+ if (s == NULL) {
+ return FAIL;
+ }
+ s = skipwhite(s);
+ if (eval1_emsg(&s, rettv, true) == FAIL) {
+ return FAIL;
+ }
+ if (*s != NUL) { // check for trailing chars after expr
+ tv_clear(rettv);
+ emsgf(_(e_invexpr2), s);
+ return FAIL;
+ }
+ }
+ return OK;
+}
+
+/// 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)
+ FUNC_ATTR_NONNULL_ARG(1, 2)
+{
+ typval_T argv, rettv;
+
+ if (eval_expr_typval(expr, &argv, 0, &rettv) == FAIL) {
+ *error = true;
+ return false;
+ }
+ const bool res = (tv_get_number_chk(&rettv, error) != 0);
+ tv_clear(&rettv);
+ return res;
+}
+
/// Top level evaluation function, returning a string
///
/// @param[in] arg String to evaluate.
@@ -1190,68 +1297,35 @@ int get_spellword(list_T *const list, const char **ret_word)
// Call some vim script function and return the result in "*rettv".
-// Uses argv[argc] for the function arguments. Only Number and String
-// arguments are currently supported.
+// Uses argv[0] to argv[argc-1] for the function arguments. argv[argc]
+// should have type VAR_UNKNOWN.
//
// Return OK or FAIL.
int call_vim_function(
const char_u *func,
int argc,
- const char_u *const *const argv,
- bool safe, // use the sandbox
- int str_arg_only, // all arguments are strings
- typval_T *rettv
+ typval_T *argv,
+ typval_T *rettv,
+ bool safe // use the sandbox
)
{
- varnumber_T n;
- int len;
int doesrange;
void *save_funccalp = NULL;
int ret;
- typval_T *argvars = xmalloc((argc + 1) * sizeof(typval_T));
-
- for (int i = 0; i < argc; i++) {
- // Pass a NULL or empty argument as an empty string
- if (argv[i] == NULL || *argv[i] == NUL) {
- argvars[i].v_type = VAR_STRING;
- argvars[i].vval.v_string = (char_u *)"";
- continue;
- }
-
- if (str_arg_only) {
- len = 0;
- } else {
- // Recognize a number argument, the others must be strings. A dash
- // is a string too.
- vim_str2nr(argv[i], NULL, &len, STR2NR_ALL, &n, NULL, 0);
- if (len == 1 && *argv[i] == '-') {
- len = 0;
- }
- }
- if (len != 0 && len == (int)STRLEN(argv[i])) {
- argvars[i].v_type = VAR_NUMBER;
- argvars[i].vval.v_number = n;
- } else {
- argvars[i].v_type = VAR_STRING;
- argvars[i].vval.v_string = (char_u *)argv[i];
- }
- }
-
if (safe) {
save_funccalp = save_funccal();
++sandbox;
}
rettv->v_type = VAR_UNKNOWN; // tv_clear() uses this.
- ret = call_func(func, (int)STRLEN(func), rettv, argc, argvars, NULL,
+ ret = call_func(func, (int)STRLEN(func), rettv, argc, argv, NULL,
curwin->w_cursor.lnum, curwin->w_cursor.lnum,
&doesrange, true, NULL, NULL);
if (safe) {
--sandbox;
restore_funccal(save_funccalp);
}
- xfree(argvars);
if (ret == FAIL) {
tv_clear(rettv);
@@ -1259,47 +1333,44 @@ int call_vim_function(
return ret;
}
-
/// Call Vim script function and return the result as a number
///
/// @param[in] func Function name.
/// @param[in] argc Number of arguments.
-/// @param[in] argv Array with string arguments.
+/// @param[in] argv Array with typval_T arguments.
/// @param[in] safe Use with sandbox.
///
/// @return -1 when calling function fails, result of function otherwise.
varnumber_T call_func_retnr(char_u *func, int argc,
- const char_u *const *const argv, int safe)
+ typval_T *argv, int safe)
{
typval_T rettv;
varnumber_T retval;
- /* All arguments are passed as strings, no conversion to number. */
- if (call_vim_function(func, argc, argv, safe, TRUE, &rettv) == FAIL)
+ if (call_vim_function(func, argc, argv, &rettv, safe) == FAIL) {
return -1;
-
+ }
retval = tv_get_number_chk(&rettv, NULL);
tv_clear(&rettv);
return retval;
}
-
/// Call Vim script function and return the result as a string
///
/// @param[in] func Function name.
/// @param[in] argc Number of arguments.
-/// @param[in] argv Array with string arguments.
+/// @param[in] argv Array with typval_T arguments.
/// @param[in] safe Use the sandbox.
///
/// @return [allocated] NULL when calling function fails, allocated string
/// otherwise.
char *call_func_retstr(const char *const func, int argc,
- const char_u *const *argv,
+ typval_T *argv,
bool safe)
FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_MALLOC
{
typval_T rettv;
// All arguments are passed as strings, no conversion to number.
- if (call_vim_function((const char_u *)func, argc, argv, safe, true, &rettv)
+ if (call_vim_function((const char_u *)func, argc, argv, &rettv, safe)
== FAIL) {
return NULL;
}
@@ -1308,24 +1379,24 @@ char *call_func_retstr(const char *const func, int argc,
tv_clear(&rettv);
return retval;
}
-
/// Call Vim script function and return the result as a List
///
/// @param[in] func Function name.
/// @param[in] argc Number of arguments.
-/// @param[in] argv Array with string arguments.
+/// @param[in] argv Array with typval_T arguments.
/// @param[in] safe Use the sandbox.
///
/// @return [allocated] NULL when calling function fails or return tv is not a
/// List, allocated List otherwise.
-void *call_func_retlist(char_u *func, int argc, const char_u *const *argv,
+void *call_func_retlist(char_u *func, int argc, typval_T *argv,
bool safe)
{
typval_T rettv;
- /* All arguments are passed as strings, no conversion to number. */
- if (call_vim_function(func, argc, argv, safe, TRUE, &rettv) == FAIL)
+ // All arguments are passed as strings, no conversion to number.
+ if (call_vim_function(func, argc, argv, &rettv, safe) == FAIL) {
return NULL;
+ }
if (rettv.v_type != VAR_LIST) {
tv_clear(&rettv);
@@ -1436,21 +1507,33 @@ int eval_foldexpr(char_u *arg, int *cp)
return (int)retval;
}
-/*
- * ":let" list all variable values
- * ":let var1 var2" list variable values
- * ":let var = expr" assignment command.
- * ":let var += expr" assignment command.
- * ":let var -= expr" assignment command.
- * ":let var *= expr" assignment command.
- * ":let var /= expr" assignment command.
- * ":let var %= expr" assignment command.
- * ":let var .= expr" assignment command.
- * ":let var ..= expr" assignment command.
- * ":let [var1, var2] = expr" unpack list.
- */
+// ":cons[t] var = expr1" define constant
+// ":cons[t] [name1, name2, ...] = expr1" define constants unpacking list
+// ":cons[t] [name, ..., ; lastname] = expr" define constants unpacking list
+void ex_const(exarg_T *eap)
+{
+ ex_let_const(eap, true);
+}
+
+// ":let" list all variable values
+// ":let var1 var2" list variable values
+// ":let var = expr" assignment command.
+// ":let var += expr" assignment command.
+// ":let var -= expr" assignment command.
+// ":let var *= expr" assignment command.
+// ":let var /= expr" assignment command.
+// ":let var %= expr" assignment command.
+// ":let var .= expr" assignment command.
+// ":let var ..= expr" assignment command.
+// ":let [var1, var2] = expr" unpack list.
+// ":let [name, ..., ; lastname] = expr" unpack list.
void ex_let(exarg_T *eap)
{
+ ex_let_const(eap, false);
+}
+
+static void ex_let_const(exarg_T *eap, const bool is_const)
+{
char_u *arg = eap->arg;
char_u *expr = NULL;
typval_T rettv;
@@ -1512,7 +1595,8 @@ void ex_let(exarg_T *eap)
}
emsg_skip--;
} else if (i != FAIL) {
- (void)ex_let_vars(eap->arg, &rettv, false, semicolon, var_count, op);
+ (void)ex_let_vars(eap->arg, &rettv, false, semicolon, var_count,
+ is_const, op);
tv_clear(&rettv);
}
}
@@ -1530,9 +1614,10 @@ static int
ex_let_vars(
char_u *arg_start,
typval_T *tv,
- int copy, /* copy values from "tv", don't move */
- int semicolon, /* from skip_var_list() */
- int var_count, /* from skip_var_list() */
+ int copy, // copy values from "tv", don't move
+ int semicolon, // from skip_var_list()
+ int var_count, // from skip_var_list()
+ int is_const, // lock variables for :const
char_u *nextchars
)
{
@@ -1543,8 +1628,9 @@ ex_let_vars(
/*
* ":let var = expr" or ":for var in list"
*/
- if (ex_let_one(arg, tv, copy, nextchars, nextchars) == NULL)
+ if (ex_let_one(arg, tv, copy, is_const, nextchars, nextchars) == NULL) {
return FAIL;
+ }
return OK;
}
@@ -1572,8 +1658,8 @@ ex_let_vars(
size_t rest_len = tv_list_len(l);
while (*arg != ']') {
arg = skipwhite(arg + 1);
- arg = ex_let_one(arg, TV_LIST_ITEM_TV(item), true, (const char_u *)",;]",
- nextchars);
+ arg = ex_let_one(arg, TV_LIST_ITEM_TV(item), true, is_const,
+ (const char_u *)",;]", nextchars);
if (arg == NULL) {
return FAIL;
}
@@ -1595,7 +1681,7 @@ ex_let_vars(
ltv.vval.v_list = rest_list;
tv_list_ref(rest_list);
- arg = ex_let_one(skipwhite(arg + 1), &ltv, false,
+ arg = ex_let_one(skipwhite(arg + 1), &ltv, false, is_const,
(char_u *)"]", nextchars);
tv_clear(&ltv);
if (arg == NULL) {
@@ -1683,6 +1769,15 @@ static void list_hashtable_vars(hashtab_T *ht, const char *prefix, int empty,
if (!HASHITEM_EMPTY(hi)) {
todo--;
di = TV_DICT_HI2DI(hi);
+ char buf[IOSIZE];
+
+ // apply :filter /pat/ to variable name
+ xstrlcpy(buf, prefix, IOSIZE - 1);
+ xstrlcat(buf, (char *)di->di_key, IOSIZE);
+ if (message_filtered((char_u *)buf)) {
+ continue;
+ }
+
if (empty || di->di_tv.v_type != VAR_STRING
|| di->di_tv.vval.v_string != NULL) {
list_one_var(di, prefix, first);
@@ -1851,8 +1946,8 @@ static const char *list_arg_vars(exarg_T *eap, const char *arg, int *first)
/// @return a pointer to the char just after the var name or NULL in case of
/// error.
static char_u *ex_let_one(char_u *arg, typval_T *const tv,
- const bool copy, const char_u *const endchars,
- const char_u *const op)
+ const bool copy, const bool is_const,
+ const char_u *const endchars, const char_u *const op)
FUNC_ATTR_NONNULL_ARG(1, 2) FUNC_ATTR_WARN_UNUSED_RESULT
{
char_u *arg_end = NULL;
@@ -1864,6 +1959,10 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv,
* ":let $VAR = expr": Set environment variable.
*/
if (*arg == '$') {
+ if (is_const) {
+ EMSG(_("E996: Cannot lock an environment variable"));
+ return NULL;
+ }
// Find the end of the name.
arg++;
char *name = (char *)arg;
@@ -1909,6 +2008,10 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv,
// ":let &l:option = expr": Set local option value.
// ":let &g:option = expr": Set global option value.
} else if (*arg == '&') {
+ if (is_const) {
+ EMSG(_("E996: Cannot lock an option"));
+ return NULL;
+ }
// Find the end of the name.
char *const p = (char *)find_option_end((const char **)&arg, &opt_flags);
if (p == NULL
@@ -1938,8 +2041,8 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv,
case '+': n = numval + n; break;
case '-': n = numval - n; break;
case '*': n = numval * n; break;
- case '/': n = numval / n; break;
- case '%': n = numval % n; break;
+ case '/': n = num_divide(numval, n); break;
+ case '%': n = num_modulus(numval, n); break;
}
} else if (opt_type == 0 && stringval != NULL) { // string
char *const oldstringval = stringval;
@@ -1959,6 +2062,10 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv,
}
// ":let @r = expr": Set register contents.
} else if (*arg == '@') {
+ if (is_const) {
+ EMSG(_("E996: Cannot lock a register"));
+ return NULL;
+ }
arg++;
if (op != NULL && vim_strchr((char_u *)"+-*/%", *op) != NULL) {
emsgf(_(e_letwrong), op);
@@ -1998,7 +2105,7 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv,
if (endchars != NULL && vim_strchr(endchars, *skipwhite(p)) == NULL) {
EMSG(_(e_letunexp));
} else {
- set_var_lval(&lv, p, tv, copy, op);
+ set_var_lval(&lv, p, tv, copy, is_const, op);
arg_end = p;
}
}
@@ -2249,6 +2356,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv,
/* Can't add "v:" variable. */
if (lp->ll_dict == &vimvardict) {
EMSG2(_(e_illvar), name);
+ tv_clear(&var1);
return NULL;
}
@@ -2363,7 +2471,7 @@ static void clear_lval(lval_T *lp)
* "%" for "%=", "." for ".=" or "=" for "=".
*/
static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv,
- int copy, const char_u *op)
+ int copy, const bool is_const, const char_u *op)
{
int cc;
listitem_T *ri;
@@ -2375,6 +2483,12 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv,
if (op != NULL && *op != '=') {
typval_T tv;
+ if (is_const) {
+ EMSG(_(e_cannot_mod));
+ *endp = cc;
+ return;
+ }
+
// handle +=, -=, *=, /=, %= and .=
di = NULL;
if (get_var_tv((const char *)lp->ll_name, (int)STRLEN(lp->ll_name),
@@ -2390,7 +2504,7 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv,
tv_clear(&tv);
}
} else {
- set_var(lp->ll_name, lp->ll_name_len, rettv, copy);
+ set_var_const(lp->ll_name, lp->ll_name_len, rettv, copy, is_const);
}
*endp = cc;
} else if (tv_check_lock(lp->ll_newkey == NULL
@@ -2401,6 +2515,11 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv,
listitem_T *ll_li = lp->ll_li;
int ll_n1 = lp->ll_n1;
+ if (is_const) {
+ EMSG(_("E996: Cannot lock a range"));
+ return;
+ }
+
// Check whether any of the list items is locked
for (ri = tv_list_first(rettv->vval.v_list);
ri != NULL && ll_li != NULL; ) {
@@ -2456,6 +2575,11 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv,
dict_T *dict = lp->ll_dict;
bool watched = tv_dict_is_watched(dict);
+ if (is_const) {
+ EMSG(_("E996: Cannot lock a list or dict"));
+ return;
+ }
+
// Assign to a List or Dictionary item.
if (lp->ll_newkey != NULL) {
if (op != NULL && *op != '=') {
@@ -2579,7 +2703,7 @@ bool next_for_item(void *fi_void, char_u *arg)
} else {
fi->fi_lw.lw_item = TV_LIST_ITEM_NEXT(fi->fi_list, item);
return (ex_let_vars(arg, TV_LIST_ITEM_TV(item), true,
- fi->fi_semicolon, fi->fi_varcount, NULL) == OK);
+ fi->fi_semicolon, fi->fi_varcount, false, NULL) == OK);
}
}
@@ -4048,22 +4172,9 @@ static int eval6(char_u **arg, typval_T *rettv, int evaluate, int want_string)
if (op == '*') {
n1 = n1 * n2;
} else if (op == '/') {
- if (n2 == 0) { // give an error message?
- if (n1 == 0) {
- n1 = VARNUMBER_MIN; // similar to NaN
- } else if (n1 < 0) {
- n1 = -VARNUMBER_MAX;
- } else {
- n1 = VARNUMBER_MAX;
- }
- } else {
- n1 = n1 / n2;
- }
+ n1 = num_divide(n1, n2);
} else {
- if (n2 == 0) /* give an error message? */
- n1 = 0;
- else
- n1 = n1 % n2;
+ n1 = num_modulus(n1, n2);
}
rettv->v_type = VAR_NUMBER;
rettv->vval.v_number = n1;
@@ -4199,7 +4310,7 @@ static int eval7(
// Dictionary: {key: val, key: val}
case '{': ret = get_lambda_tv(arg, rettv, evaluate);
if (ret == NOTDONE) {
- ret = get_dict_tv(arg, rettv, evaluate);
+ ret = dict_get_tv(arg, rettv, evaluate);
}
break;
@@ -5123,7 +5234,7 @@ bool garbage_collect(bool testing)
yankreg_T reg;
char name = NUL;
bool is_unnamed = false;
- reg_iter = op_register_iter(reg_iter, &name, &reg, &is_unnamed);
+ reg_iter = op_global_reg_iter(reg_iter, &name, &reg, &is_unnamed);
if (name != NUL) {
ABORTING(set_ref_dict)(reg.additional_data, copyID);
}
@@ -5165,7 +5276,7 @@ bool garbage_collect(bool testing)
{
Channel *data;
map_foreach_value(channels, data, {
- set_ref_in_callback_reader(&data->on_stdout, copyID, NULL, NULL);
+ set_ref_in_callback_reader(&data->on_data, copyID, NULL, NULL);
set_ref_in_callback_reader(&data->on_stderr, copyID, NULL, NULL);
set_ref_in_callback(&data->on_exit, copyID, NULL, NULL);
})
@@ -5583,7 +5694,7 @@ static bool set_ref_in_funccal(funccall_T *fc, int copyID)
* Allocate a variable for a Dictionary and fill it from "*arg".
* Return OK or FAIL. Returns NOTDONE for {expr}.
*/
-static int get_dict_tv(char_u **arg, typval_T *rettv, int evaluate)
+static int dict_get_tv(char_u **arg, typval_T *rettv, int evaluate)
{
dict_T *d = NULL;
typval_T tvkey;
@@ -5871,10 +5982,6 @@ static int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate)
fp->uf_scoped = NULL;
}
- fp->uf_tml_count = NULL;
- fp->uf_tml_total = NULL;
- fp->uf_tml_self = NULL;
- fp->uf_profiling = false;
if (prof_def_func()) {
func_do_profile(fp);
}
@@ -6312,6 +6419,7 @@ call_func(
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;
@@ -6545,6 +6653,10 @@ static void float_op_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr)
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;
@@ -6969,10 +7081,14 @@ 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 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);
@@ -6994,6 +7110,7 @@ static void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
+ trylevel = save_trylevel;
called_emsg = false;
suppress_errthrow = false;
emsg_silent = false;
@@ -7161,6 +7278,14 @@ static buf_T *find_buffer(typval_T *avar)
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
*/
@@ -7180,6 +7305,21 @@ static void f_buflisted(typval_T *argvars, typval_T *rettv, FunPtr fptr)
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
*/
@@ -7856,6 +7996,116 @@ static void f_cscope_connection(typval_T *argvars, typval_T *rettv, FunPtr fptr)
(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, "buflist")) {
+ types |= kCtxBuflist;
+ } 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)"
///
@@ -8246,6 +8496,25 @@ static void f_empty(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->vval.v_number = n;
}
+/// "environ()" function
+static void f_environ(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ tv_dict_alloc_ret(rettv);
+
+ for (int i = 0; ; i++) {
+ // TODO(justinmk): use os_copyfullenv from #7202 ?
+ char *envname = os_getenvname_at_index((size_t)i);
+ if (envname == NULL) {
+ break;
+ }
+ const char *value = os_getenv(envname);
+ tv_dict_add_str(rettv->vval.v_dict,
+ (char *)envname, STRLEN((char *)envname),
+ value == NULL ? "" : value);
+ xfree(envname);
+ }
+}
+
/*
* "escape({string}, {chars})" function
*/
@@ -8259,6 +8528,20 @@ static void f_escape(typval_T *argvars, typval_T *rettv, FunPtr fptr)
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_number = kSpecialVarNull;
+ return;
+ }
+ rettv->vval.v_string = p;
+ rettv->v_type = VAR_STRING;
+}
+
/*
* "eval()" function
*/
@@ -8298,10 +8581,7 @@ 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((const char_u *)name, NULL, true)
- || (gettail_dir(name) != name
- && os_can_exe((const char_u *)name, NULL, false)));
+ rettv->vval.v_number = os_can_exe(name, NULL, true);
}
typedef struct {
@@ -8406,12 +8686,12 @@ static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr)
static void f_exepath(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
const char *arg = tv_get_string(&argvars[0]);
- char_u *path = NULL;
+ char *path = NULL;
- (void)os_can_exe((const char_u *)arg, &path, true);
+ (void)os_can_exe(arg, &path, true);
rettv->v_type = VAR_STRING;
- rettv->vval.v_string = path;
+ rettv->vval.v_string = (char_u *)path;
}
/// Find a window: When using a Window ID in any tab page, when using a number
@@ -8438,7 +8718,7 @@ static void f_exists(typval_T *argvars, typval_T *rettv, FunPtr fptr)
const char *p = tv_get_string(&argvars[0]);
if (*p == '$') { // Environment variable.
// First try "normal" environment variables (fast).
- if (os_getenv(p + 1) != NULL) {
+ if (os_env_exists(p + 1)) {
n = true;
} else {
// Try expanding things like $VIM and ${HOME}.
@@ -8834,6 +9114,7 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map)
}
hash_unlock(ht);
} else {
+ assert(argvars[0].v_type == VAR_LIST);
vimvars[VV_KEY].vv_type = VAR_NUMBER;
for (listitem_T *li = tv_list_first(l); li != NULL;) {
@@ -8864,44 +9145,17 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map)
}
static int filter_map_one(typval_T *tv, typval_T *expr, int map, int *remp)
+ FUNC_ATTR_NONNULL_ARG(1, 2)
{
typval_T rettv;
typval_T argv[3];
int retval = FAIL;
- int dummy;
tv_copy(tv, &vimvars[VV_VAL].vv_tv);
argv[0] = vimvars[VV_KEY].vv_tv;
argv[1] = vimvars[VV_VAL].vv_tv;
- if (expr->v_type == VAR_FUNC) {
- const char_u *const s = expr->vval.v_string;
- if (call_func(s, (int)STRLEN(s), &rettv, 2, argv, NULL,
- 0L, 0L, &dummy, true, NULL, NULL) == FAIL) {
- goto theend;
- }
- } else if (expr->v_type == VAR_PARTIAL) {
- partial_T *partial = expr->vval.v_partial;
-
- const char_u *const s = partial_name(partial);
- if (call_func(s, (int)STRLEN(s), &rettv, 2, argv, NULL,
- 0L, 0L, &dummy, true, partial, NULL) == FAIL) {
- goto theend;
- }
- } else {
- char buf[NUMBUFLEN];
- const char *s = tv_get_string_buf_chk(expr, buf);
- if (s == NULL) {
- goto theend;
- }
- s = (const char *)skipwhite((const char_u *)s);
- if (eval1((char_u **)&s, &rettv, true) == FAIL) {
- goto theend;
- }
-
- if (*s != NUL) { // check for trailing chars after expr
- emsgf(_(e_invexpr2), s);
- goto theend;
- }
+ if (eval_expr_typval(expr, argv, 2, &rettv) == FAIL) {
+ goto theend;
}
if (map) {
// map(): replace the list item value.
@@ -9360,6 +9614,7 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr)
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) {
@@ -9401,7 +9656,10 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr)
func_ref(rettv->vval.v_string);
}
} else if (strcmp(what, "dict") == 0) {
- tv_dict_set_ret(rettv, pt->pt_dict);
+ 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) {
@@ -9412,7 +9670,12 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr)
} else {
EMSG2(_(e_invarg2), what);
}
- return;
+
+ // When {what} == "dict" and pt->pt_dict == NULL, evaluate the
+ // third argument
+ if (!what_is_dict) {
+ return;
+ }
}
} else {
EMSG2(_(e_listdictarg), "get()");
@@ -9886,13 +10149,13 @@ static void f_getcompletion(typval_T *argvars, typval_T *rettv, FunPtr fptr)
if (strcmp(tv_get_string(&argvars[1]), "cmdline") == 0) {
set_one_cmd_context(&xpc, tv_get_string(&argvars[0]));
- xpc.xp_pattern_len = (int)STRLEN(xpc.xp_pattern);
+ 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 = (int)STRLEN(xpc.xp_pattern);
+ 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) {
@@ -9902,17 +10165,17 @@ static void f_getcompletion(typval_T *argvars, typval_T *rettv, FunPtr fptr)
if (xpc.xp_context == EXPAND_MENUS) {
set_context_in_menu_cmd(&xpc, (char_u *)"menu", xpc.xp_pattern, false);
- xpc.xp_pattern_len = (int)STRLEN(xpc.xp_pattern);
+ 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 = (int)STRLEN(xpc.xp_pattern);
+ 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 = (int)STRLEN(xpc.xp_pattern);
+ xpc.xp_pattern_len = STRLEN(xpc.xp_pattern);
}
theend:
@@ -10619,6 +10882,7 @@ static dict_T *get_win_info(win_T *wp, int16_t tpnr, int16_t winnr)
tv_dict_add_nr(dict, S_LEN("bufnr"), wp->w_buffer->b_fnum);
tv_dict_add_nr(dict, S_LEN("wincol"), wp->w_wincol);
+ tv_dict_add_nr(dict, S_LEN("terminal"), bt_terminal(wp->w_buffer));
tv_dict_add_nr(dict, S_LEN("quickfix"), bt_quickfix(wp->w_buffer));
tv_dict_add_nr(dict, S_LEN("loclist"),
(bt_quickfix(wp->w_buffer) && wp->w_llist_ref != NULL));
@@ -10996,7 +11260,7 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr)
"fork",
#endif
"gettext",
-#if defined(HAVE_ICONV_H) && defined(USE_ICONV)
+#if defined(HAVE_ICONV)
"iconv",
#endif
"insert_expand",
@@ -11109,10 +11373,6 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr)
n = stdout_isatty;
} else if (STRICMP(name, "multi_byte_encoding") == 0) {
n = has_mbyte != 0;
-#if defined(USE_ICONV) && defined(DYNAMIC_ICONV)
- } else if (STRICMP(name, "iconv") == 0) {
- n = iconv_enabled(false);
-#endif
} else if (STRICMP(name, "syntax_items") == 0) {
n = syntax_present(curwin);
#ifdef UNIX
@@ -11935,9 +12195,18 @@ static void f_jobresize(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->vval.v_number = 1;
}
+/// Builds a process argument vector from a VimL object (typval_T).
+///
+/// @param[in] cmd_tv VimL object
+/// @param[out] cmd Returns the command or executable name.
+/// @param[out] executable Returns `false` if argv[0] is not executable.
+///
+/// @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)
{
- if (cmd_tv->v_type == VAR_STRING) {
+ if (cmd_tv->v_type == VAR_STRING) { // String => "shell semantics".
const char *cmd_str = tv_get_string(cmd_tv);
if (cmd) {
*cmd = cmd_str;
@@ -11957,16 +12226,17 @@ static char **tv_to_argv(typval_T *cmd_tv, const char **cmd, bool *executable)
return NULL;
}
- const char *exe = tv_get_string_chk(TV_LIST_ITEM_TV(tv_list_first(argl)));
- if (!exe || !os_can_exe((const char_u *)exe, NULL, true)) {
- if (exe && executable) {
+ const char *arg0 = tv_get_string_chk(TV_LIST_ITEM_TV(tv_list_first(argl)));
+ char *exe_resolved = NULL;
+ if (!arg0 || !os_can_exe(arg0, &exe_resolved, true)) {
+ if (arg0 && executable) {
*executable = false;
}
return NULL;
}
if (cmd) {
- *cmd = exe;
+ *cmd = exe_resolved;
}
// Build the argument vector
@@ -11977,10 +12247,15 @@ static char **tv_to_argv(typval_T *cmd_tv, const char **cmd, bool *executable)
if (!a) {
// Did emsg in tv_get_string_chk; just deallocate argv.
shell_free_argv(argv);
+ xfree(exe_resolved);
return NULL;
}
argv[i++] = xstrdup(a);
});
+ // Replace argv[0] with absolute path. The only reason for this is to make
+ // $PATHEXT work on Windows with jobstart([…]). #9569
+ xfree(argv[0]);
+ argv[0] = exe_resolved;
return argv;
}
@@ -12080,14 +12355,21 @@ static void f_jobstop(typval_T *argvars, typval_T *rettv, FunPtr fptr)
return;
}
-
Channel *data = find_job(argvars[0].vval.v_number, true);
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
@@ -13454,7 +13736,7 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr)
// 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 = mch_fopen(fname, READBIN)) == NULL) {
+ if (*fname == NUL || (fd = os_fopen(fname, READBIN)) == NULL) {
EMSG2(_(e_notopen), *fname == NUL ? _("<empty>") : fname);
return;
}
@@ -13686,11 +13968,7 @@ static void f_reltime(typval_T *argvars, typval_T *rettv, FunPtr fptr)
tv_list_append_number(rettv->vval.v_list, u.split.low);
}
-/// f_reltimestr - return a string that represents the value of {time}
-///
-/// @return The string representation of the argument, the format is the
-/// number of seconds followed by a dot, followed by the number
-/// of microseconds.
+/// "reltimestr()" function
static void f_reltimestr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
FUNC_ATTR_NONNULL_ALL
{
@@ -13699,7 +13977,7 @@ static void f_reltimestr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
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));
+ rettv->vval.v_string = (char_u *)xstrdup(profile_msg(tm));
}
}
@@ -14501,10 +14779,10 @@ static int searchpair_cmn(typval_T *argvars, pos_T *match_pos)
long lnum_stop = 0;
long time_limit = 0;
- // Get the three pattern arguments: start, middle, end.
+ // Get the three pattern arguments: start, middle, end. Will result in an
+ // error if not a valid argument.
char nbuf1[NUMBUFLEN];
char nbuf2[NUMBUFLEN];
- char nbuf3[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);
@@ -14532,23 +14810,28 @@ static int searchpair_cmn(typval_T *argvars, pos_T *match_pos)
}
// Optional fifth argument: skip expression.
- const char *skip;
+ const typval_T *skip;
if (argvars[3].v_type == VAR_UNKNOWN
|| argvars[4].v_type == VAR_UNKNOWN) {
- skip = "";
+ skip = NULL;
} else {
- skip = tv_get_string_buf_chk(&argvars[4], nbuf3);
- if (skip == NULL) {
+ 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;
}
}
@@ -14556,7 +14839,7 @@ static int searchpair_cmn(typval_T *argvars, pos_T *match_pos)
}
retval = do_searchpair(
- (char_u *)spat, (char_u *)mpat, (char_u *)epat, dir, (char_u *)skip,
+ (char_u *)spat, (char_u *)mpat, (char_u *)epat, dir, skip,
flags, match_pos, lnum_stop, time_limit);
theend:
@@ -14604,7 +14887,7 @@ do_searchpair(
char_u *mpat, // middle pattern
char_u *epat, // end pattern
int dir, // BACKWARD or FORWARD
- char_u *skip, // skip expression
+ 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
@@ -14620,8 +14903,8 @@ do_searchpair(
pos_T save_cursor;
pos_T save_pos;
int n;
- int r;
int nest = 1;
+ bool use_skip = false;
int options = SEARCH_KEEP;
proftime_T tm;
size_t pat2_len;
@@ -14651,6 +14934,13 @@ do_searchpair(
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);
@@ -14680,12 +14970,12 @@ do_searchpair(
/* clear the start flag to avoid getting stuck here */
options &= ~SEARCH_START;
- /* If the skip pattern matches, ignore this match. */
- if (*skip != NUL) {
+ // If the skip pattern matches, ignore this match.
+ if (use_skip) {
save_pos = curwin->w_cursor;
curwin->w_cursor = pos;
- bool err;
- r = eval_to_bool(skip, &err, NULL, false);
+ 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. */
@@ -14846,6 +15136,123 @@ static void f_serverstop(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
+/// Set line or list of lines in buffer "buf".
+static void set_buffer_lines(buf_T *buf, linenr_T lnum, typval_T *lines,
+ typval_T *rettv)
+{
+ list_T *l = NULL;
+ listitem_T *li = NULL;
+ long added = 0;
+ linenr_T lcount;
+ buf_T *curbuf_save = NULL;
+ win_T *curwin_save = NULL;
+ int is_curbuf = buf == curbuf;
+
+ // When using the current buffer ml_mfp will be set if needed. Useful when
+ // setline() is used on startup. For other buffers the buffer must be
+ // loaded.
+ if (buf == NULL || (!is_curbuf && buf->b_ml.ml_mfp == NULL) || lnum < 1) {
+ rettv->vval.v_number = 1; // FAIL
+ return;
+ }
+
+ if (!is_curbuf) {
+ wininfo_T *wip;
+
+ curbuf_save = curbuf;
+ curwin_save = curwin;
+ curbuf = buf;
+ for (wip = buf->b_wininfo; wip != NULL; wip = wip->wi_next) {
+ if (wip->wi_win != NULL) {
+ curwin = wip->wi_win;
+ break;
+ }
+ }
+ }
+
+ lcount = curbuf->b_ml.ml_line_count;
+
+ const char *line = NULL;
+
+ if (lines->v_type == VAR_LIST) {
+ l = lines->vval.v_list;
+ li = tv_list_first(l);
+ } else {
+ line = tv_get_string_chk(lines);
+ }
+
+ // Default result is zero == OK.
+ for (;; ) {
+ if (lines->v_type == VAR_LIST) {
+ // List argument, get next string.
+ if (li == NULL) {
+ break;
+ }
+ line = tv_get_string_chk(TV_LIST_ITEM_TV(li));
+ li = TV_LIST_ITEM_NEXT(l, li);
+ }
+
+ rettv->vval.v_number = 1; // FAIL
+ if (line == NULL || lnum > curbuf->b_ml.ml_line_count + 1) {
+ break;
+ }
+
+ // 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 (lnum <= curbuf->b_ml.ml_line_count) {
+ // Existing line, replace it.
+ if (u_savesub(lnum) == OK
+ && ml_replace(lnum, (char_u *)line, true) == OK) {
+ changed_bytes(lnum, 0);
+ if (is_curbuf && lnum == curwin->w_cursor.lnum) {
+ check_cursor_col();
+ }
+ rettv->vval.v_number = 0; // OK
+ }
+ } else if (added > 0 || u_save(lnum - 1, lnum) == OK) {
+ // lnum is one past the last line, append the line.
+ added++;
+ if (ml_append(lnum - 1, (char_u *)line, 0, false) == OK) {
+ rettv->vval.v_number = 0; // OK
+ }
+ }
+
+ if (l == NULL) /* only one string argument */
+ break;
+ ++lnum;
+ }
+
+ if (added > 0) {
+ appended_lines_mark(lcount, added);
+ }
+
+ if (!is_curbuf) {
+ curbuf = curbuf_save;
+ curwin = curwin_save;
+ }
+}
+
+/// "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, &argvars[2], rettv);
+ }
+}
+
/*
* "setbufvar()" function
*/
@@ -14941,6 +15348,20 @@ static void f_setcmdpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
+/// "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_number == 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)
@@ -14978,67 +15399,8 @@ static void f_setfperm(typval_T *argvars, typval_T *rettv, FunPtr fptr)
*/
static void f_setline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- list_T *l = NULL;
- listitem_T *li = NULL;
- long added = 0;
- linenr_T lcount = curbuf->b_ml.ml_line_count;
-
linenr_T lnum = tv_get_lnum(&argvars[0]);
- const char *line = NULL;
- if (argvars[1].v_type == VAR_LIST) {
- l = argvars[1].vval.v_list;
- li = tv_list_first(l);
- } else {
- line = tv_get_string_chk(&argvars[1]);
- }
-
- // Default result is zero == OK.
- for (;; ) {
- if (argvars[1].v_type == VAR_LIST) {
- // List argument, get next string.
- if (li == NULL) {
- break;
- }
- line = tv_get_string_chk(TV_LIST_ITEM_TV(li));
- li = TV_LIST_ITEM_NEXT(l, li);
- }
-
- rettv->vval.v_number = 1; // FAIL
- if (line == NULL || lnum < 1 || lnum > curbuf->b_ml.ml_line_count + 1) {
- break;
- }
-
- /* 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 (lnum <= curbuf->b_ml.ml_line_count) {
- // Existing line, replace it.
- if (u_savesub(lnum) == OK
- && ml_replace(lnum, (char_u *)line, true) == OK) {
- changed_bytes(lnum, 0);
- if (lnum == curwin->w_cursor.lnum)
- check_cursor_col();
- rettv->vval.v_number = 0; /* OK */
- }
- } else if (added > 0 || u_save(lnum - 1, lnum) == OK) {
- // lnum is one past the last line, append the line.
- added++;
- if (ml_append(lnum - 1, (char_u *)line, 0, false) == OK) {
- rettv->vval.v_number = 0; // OK
- }
- }
-
- if (l == NULL) /* only one string argument */
- break;
- ++lnum;
- }
-
- if (added > 0)
- appended_lines_mark(lcount, added);
+ set_buffer_lines(curbuf, lnum, &argvars[1], rettv);
}
/// Create quickfix/location list from VimL values
@@ -15390,7 +15752,7 @@ free_lstval:
if (set_unnamed) {
// Discard the result. We already handle the error case.
- if (op_register_set_previous(regname)) { }
+ if (op_reg_set_previous(regname)) { }
}
}
@@ -15503,7 +15865,7 @@ static void f_setwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
static void setwinvar(typval_T *argvars, typval_T *rettv, int off)
{
- if (check_restricted() || check_secure()) {
+ if (check_secure()) {
return;
}
@@ -15681,6 +16043,7 @@ static void f_sign_getplaced(typval_T *argvars, typval_T *rettv, FunPtr fptr)
if (notanum) {
return;
}
+ (void)lnum;
lnum = tv_get_lnum(&di->di_tv);
}
if ((di = tv_dict_find(dict, "id", -1)) != NULL) {
@@ -15735,9 +16098,6 @@ static void f_sign_jump(typval_T *argvars, typval_T *rettv, FunPtr fptr)
sign_group = NULL; // global sign group
} else {
sign_group = xstrdup(sign_group_chk);
- if (sign_group == NULL) {
- return;
- }
}
// Buffer to place the sign
@@ -15786,9 +16146,6 @@ static void f_sign_place(typval_T *argvars, typval_T *rettv, FunPtr fptr)
group = NULL; // global sign group
} else {
group = vim_strsave((const char_u *)group_chk);
- if (group == NULL) {
- return;
- }
}
// Sign name
@@ -15816,6 +16173,7 @@ static void f_sign_place(typval_T *argvars, typval_T *rettv, FunPtr fptr)
if (notanum) {
goto cleanup;
}
+ (void)lnum;
lnum = tv_get_lnum(&di->di_tv);
}
if ((di = tv_dict_find(dict, "priority", -1)) != NULL) {
@@ -15881,9 +16239,6 @@ static void f_sign_unplace(typval_T *argvars, typval_T *rettv, FunPtr fptr)
group = NULL; // global sign group
} else {
group = vim_strsave((const char_u *)group_chk);
- if (group == NULL) {
- return;
- }
}
if (argvars[1].v_type != VAR_UNKNOWN) {
@@ -16352,9 +16707,7 @@ 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
{
@@ -16363,7 +16716,7 @@ static void f_reltimefloat(typval_T *argvars , typval_T *rettv, FunPtr fptr)
rettv->v_type = VAR_FLOAT;
rettv->vval.v_float = 0;
if (list2proftime(&argvars[0], &tm) == OK) {
- rettv->vval.v_float = ((float_T)tm) / 1000000000;
+ rettv->vval.v_float = (float_T)profile_signed(tm) / 1000000000.0;
}
}
@@ -16406,6 +16759,8 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr)
break;
}
str += len;
+ capcol -= len;
+ len = 0;
}
}
}
@@ -17776,7 +18131,6 @@ static void add_timer_info(typval_T *rettv, timer_T *timer)
di->di_tv.v_type = VAR_FUNC;
di->di_tv.vval.v_string = vim_strsave(timer->callback.data.funcref);
}
- di->di_tv.v_lock = 0;
}
static void add_timer_info_all(typval_T *rettv)
@@ -19033,8 +19387,11 @@ static int get_name_len(const char **const arg,
}
len += get_id_len(arg);
- if (len == 0 && verbose)
+ // Only give an error when there is something, otherwise it will be
+ // reported at a higher level.
+ if (len == 0 && verbose && **arg != NUL) {
EMSG2(_(e_invexpr2), *arg);
+ }
return len;
}
@@ -20047,6 +20404,24 @@ static 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);
+}
+
+/// Set variable to the given value
+///
+/// If the variable already exists, the value is updated. Otherwise the variable
+/// is created.
+///
+/// @param[in] name Variable name to set.
+/// @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.
+/// @param[in] is_const True if value in tv is to be locked.
+static void set_var_const(const char *name, const size_t name_len,
+ typval_T *const tv, const bool copy,
+ const bool is_const)
+ FUNC_ATTR_NONNULL_ALL
+{
dictitem_T *v;
hashtab_T *ht;
dict_T *dict;
@@ -20072,6 +20447,11 @@ static void set_var(const char *name, const size_t name_len, typval_T *const tv,
typval_T oldtv = TV_INITIAL_VALUE;
if (v != NULL) {
+ if (is_const) {
+ EMSG(_(e_cannot_mod));
+ return;
+ }
+
// existing variable, need to clear the value
if (var_check_ro(v->di_flags, name, name_len)
|| tv_check_lock(v->di_tv.v_lock, name, name_len)) {
@@ -20138,6 +20518,9 @@ static void set_var(const char *name, const size_t name_len, typval_T *const tv,
return;
}
v->di_flags = DI_FLAGS_ALLOC;
+ if (is_const) {
+ v->di_flags |= DI_FLAGS_LOCK;
+ }
}
if (copy || tv->v_type == VAR_NUMBER || tv->v_type == VAR_FLOAT) {
@@ -20156,6 +20539,10 @@ static void set_var(const char *name, const size_t name_len, typval_T *const tv,
tv_clear(&oldtv);
}
}
+
+ if (is_const) {
+ v->di_tv.v_lock |= VAR_LOCKED;
+ }
}
/// Check whether variable is read-only (DI_FLAGS_RO, DI_FLAGS_RO_SBX)
@@ -20480,29 +20867,19 @@ void ex_echohl(exarg_T *eap)
*/
void ex_execute(exarg_T *eap)
{
- char_u *arg = eap->arg;
+ char_u *arg = eap->arg;
typval_T rettv;
int ret = OK;
- char_u *p;
garray_T ga;
- int save_did_emsg = did_emsg;
+ int save_did_emsg;
ga_init(&ga, 1, 80);
if (eap->skip)
++emsg_skip;
while (*arg != NUL && *arg != '|' && *arg != '\n') {
- p = arg;
- if (eval1(&arg, &rettv, !eap->skip) == FAIL) {
- /*
- * Report the invalid expression unless the expression evaluation
- * has been cancelled due to an aborting error, an interrupt, or an
- * exception.
- */
- if (!aborting() && did_emsg == save_did_emsg) {
- EMSG2(_(e_invexpr2), p);
- }
- ret = FAIL;
+ ret = eval1_emsg(&arg, &rettv, !eap->skip);
+ if (ret == FAIL) {
break;
}
@@ -20632,8 +21009,11 @@ void ex_function(exarg_T *eap)
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);
+ list_func_head(fp, false, false);
}
}
}
@@ -20664,7 +21044,7 @@ void ex_function(exarg_T *eap)
fp = HI2UF(hi);
if (!isdigit(*fp->uf_name)
&& vim_regexec(&regmatch, fp->uf_name, 0))
- list_func_head(fp, FALSE);
+ list_func_head(fp, false, false);
}
}
vim_regfree(regmatch.regprog);
@@ -20714,9 +21094,12 @@ void ex_function(exarg_T *eap)
saved_did_emsg = did_emsg;
did_emsg = FALSE;
- /*
- * ":function func" with only function name: list function.
- */
+ //
+ // ":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));
@@ -20728,17 +21111,20 @@ void ex_function(exarg_T *eap)
if (!eap->skip && !got_int) {
fp = find_func(name);
if (fp != NULL) {
- list_func_head(fp, TRUE);
- for (int j = 0; j < fp->uf_lines.ga_len && !got_int; ++j) {
- if (FUNCLINE(fp, j) == 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');
- msg_outnum((long)j + 1);
- if (j < 9) {
- msg_putchar(' ');
}
- if (j < 99) {
- msg_putchar(' ');
+ 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
@@ -20746,7 +21132,7 @@ void ex_function(exarg_T *eap)
}
if (!got_int) {
msg_putchar('\n');
- msg_puts(" endfunction");
+ msg_puts(eap->forceit ? "endfunction" : " endfunction");
}
} else
emsg_funcname(N_("E123: Undefined function: %s"), name);
@@ -20895,7 +21281,8 @@ void ex_function(exarg_T *eap)
goto erret;
}
if (show_block) {
- ui_ext_cmdline_block_append(indent, (const char *)theline);
+ assert(indent >= 0);
+ ui_ext_cmdline_block_append((size_t)indent, (const char *)theline);
}
/* Detect line continuation: sourcing_lnum increased more than one. */
@@ -21064,9 +21451,10 @@ void ex_function(exarg_T *eap)
overwrite = true;
} else {
// redefine existing function
- ga_clear_strings(&(fp->uf_args));
- ga_clear_strings(&(fp->uf_lines));
XFREE_CLEAR(name);
+ func_clear_items(fp);
+ fp->uf_profiling = false;
+ fp->uf_prof_initialized = false;
}
}
} else {
@@ -21137,7 +21525,6 @@ void ex_function(exarg_T *eap)
tv_clear(&fudi.fd_di->di_tv);
}
fudi.fd_di->di_tv.v_type = VAR_FUNC;
- fudi.fd_di->di_tv.v_lock = 0;
fudi.fd_di->di_tv.vval.v_string = vim_strsave(name);
/* behave like "dict" was used */
@@ -21162,12 +21549,9 @@ void ex_function(exarg_T *eap)
} else {
fp->uf_scoped = NULL;
}
- fp->uf_tml_count = NULL;
- fp->uf_tml_total = NULL;
- fp->uf_tml_self = NULL;
- fp->uf_profiling = FALSE;
- if (prof_def_func())
+ if (prof_def_func()) {
func_do_profile(fp);
+ }
fp->uf_varargs = varargs;
if (sandbox) {
flags |= FC_SANDBOX;
@@ -21438,15 +21822,17 @@ static inline bool eval_fname_sid(const char *const name)
return *name == 's' || TOUPPER_ASC(name[2]) == 'I';
}
-/*
- * List the head of the function: "name(arg1, arg2)".
- */
-static void list_func_head(ufunc_T *fp, int indent)
+/// 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("function ");
+ 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);
@@ -21628,25 +22014,29 @@ static void func_do_profile(ufunc_T *fp)
{
int len = fp->uf_lines.ga_len;
- if (len == 0)
- len = 1; /* avoid getting error for allocating zero bytes */
- fp->uf_tm_count = 0;
- fp->uf_tm_self = profile_zero();
- fp->uf_tm_total = profile_zero();
+ if (!fp->uf_prof_initialized) {
+ if (len == 0) {
+ len = 1; // avoid getting error for allocating zero bytes
+ }
+ fp->uf_tm_count = 0;
+ fp->uf_tm_self = profile_zero();
+ fp->uf_tm_total = profile_zero();
- if (fp->uf_tml_count == NULL) {
- fp->uf_tml_count = xcalloc(len, sizeof(int));
- }
+ if (fp->uf_tml_count == NULL) {
+ fp->uf_tml_count = xcalloc(len, sizeof(int));
+ }
- if (fp->uf_tml_total == NULL) {
- fp->uf_tml_total = xcalloc(len, sizeof(proftime_T));
- }
+ if (fp->uf_tml_total == NULL) {
+ fp->uf_tml_total = xcalloc(len, sizeof(proftime_T));
+ }
- if (fp->uf_tml_self == NULL) {
- fp->uf_tml_self = xcalloc(len, sizeof(proftime_T));
- }
+ if (fp->uf_tml_self == NULL) {
+ fp->uf_tml_self = xcalloc(len, sizeof(proftime_T));
+ }
- fp->uf_tml_idx = -1;
+ fp->uf_tml_idx = -1;
+ fp->uf_prof_initialized = true;
+ }
fp->uf_profiling = TRUE;
}
@@ -21672,7 +22062,7 @@ void func_dump_profile(FILE *fd)
if (!HASHITEM_EMPTY(hi)) {
--todo;
fp = HI2UF(hi);
- if (fp->uf_profiling) {
+ if (fp->uf_prof_initialized) {
sorttab[st_len++] = fp;
if (fp->uf_name[0] == K_SPECIAL)
@@ -21832,6 +22222,7 @@ static bool script_autoload(const char *const name, const size_t name_len,
}
/// 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.
@@ -22015,6 +22406,16 @@ static bool func_remove(ufunc_T *fp)
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.
///
@@ -22027,11 +22428,7 @@ static void func_clear(ufunc_T *fp, bool force)
fp->uf_cleared = true;
// clear this function
- ga_clear_strings(&(fp->uf_args));
- ga_clear_strings(&(fp->uf_lines));
- xfree(fp->uf_tml_count);
- xfree(fp->uf_tml_total);
- xfree(fp->uf_tml_self);
+ func_clear_items(fp);
funccal_unref(fp->uf_scoped, fp, force);
}
@@ -22174,6 +22571,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,
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;
@@ -22393,8 +22791,10 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,
do_profiling_yes
&& !fp->uf_profiling && has_profiling(false, fp->uf_name, NULL);
- if (func_not_yet_profiling_but_should)
+ if (func_not_yet_profiling_but_should) {
+ started_profiling = true;
func_do_profile(fp);
+ }
bool func_or_func_caller_profiling =
do_profiling_yes
@@ -22442,6 +22842,10 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,
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 */
@@ -23012,7 +23416,7 @@ dictitem_T *find_var_in_scoped_ht(const char *name, const size_t namelen,
/// @return Pointer that needs to be passed to next `var_shada_iter` invocation
/// or NULL to indicate that iteration is over.
const void *var_shada_iter(const void *const iter, const char **const name,
- typval_T *rettv)
+ typval_T *rettv, var_flavour_T flavour)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(2, 3)
{
const hashitem_T *hi;
@@ -23023,7 +23427,7 @@ const void *var_shada_iter(const void *const iter, const char **const name,
hi = globvarht.ht_array;
while ((size_t) (hi - hifirst) < hinum
&& (HASHITEM_EMPTY(hi)
- || var_flavour(hi->hi_key) != VAR_FLAVOUR_SHADA)) {
+ || !(var_flavour(hi->hi_key) & flavour))) {
hi++;
}
if ((size_t) (hi - hifirst) == hinum) {
@@ -23035,7 +23439,7 @@ const void *var_shada_iter(const void *const iter, const char **const name,
*name = (char *)TV_DICT_HI2DI(hi)->di_key;
tv_copy(&TV_DICT_HI2DI(hi)->di_tv, rettv);
while ((size_t)(++hi - hifirst) < hinum) {
- if (!HASHITEM_EMPTY(hi) && var_flavour(hi->hi_key) == VAR_FLAVOUR_SHADA) {
+ if (!HASHITEM_EMPTY(hi) && (var_flavour(hi->hi_key) & flavour)) {
return hi;
}
}
@@ -23609,50 +24013,57 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments)
return rettv;
}
+/// Checks if a named provider is enabled.
bool eval_has_provider(const char *name)
{
-#define CHECK_PROVIDER(name) \
- if (has_##name == -1) { \
- has_##name = !!find_func((char_u *)"provider#" #name "#Call"); \
- if (!has_##name) { \
- script_autoload("provider#" #name "#Call", \
- sizeof("provider#" #name "#Call") - 1, \
- false); \
- has_##name = !!find_func((char_u *)"provider#" #name "#Call"); \
- } \
- }
-
- static int has_clipboard = -1;
- static int has_python = -1;
- static int has_python3 = -1;
- static int has_ruby = -1;
-
- if (strequal(name, "clipboard")) {
- CHECK_PROVIDER(clipboard);
- return has_clipboard;
- } else if (strequal(name, "python3")) {
- CHECK_PROVIDER(python3);
- return has_python3;
- } else if (strequal(name, "python")) {
- CHECK_PROVIDER(python);
- return has_python;
- } else if (strequal(name, "ruby")) {
- bool need_check_ruby = (has_ruby == -1);
- CHECK_PROVIDER(ruby);
- if (need_check_ruby && has_ruby == 1) {
- char *rubyhost = call_func_retstr("provider#ruby#Detect", 0, NULL, true);
- if (rubyhost) {
- if (*rubyhost == NUL) {
- // Invalid rubyhost executable. Gem is probably not installed.
- has_ruby = 0;
- }
- xfree(rubyhost);
+ if (!strequal(name, "clipboard")
+ && !strequal(name, "python")
+ && !strequal(name, "python3")
+ && !strequal(name, "ruby")
+ && !strequal(name, "node")) {
+ // Avoid autoload for non-provider has() features.
+ return false;
+ }
+
+ char buf[256];
+ int len;
+ typval_T tv;
+
+ // Get the g:loaded_xx_provider variable.
+ len = snprintf(buf, sizeof(buf), "g:loaded_%s_provider", name);
+ if (get_var_tv(buf, len, &tv, NULL, false, true) == FAIL) {
+ // Trigger autoload once.
+ len = snprintf(buf, sizeof(buf), "provider#%s#bogus", name);
+ script_autoload(buf, len, false);
+
+ // Retry the (non-autoload-style) variable.
+ len = snprintf(buf, sizeof(buf), "g:loaded_%s_provider", name);
+ if (get_var_tv(buf, len, &tv, NULL, false, true) == FAIL) {
+ // Show a hint if Call() is defined but g:loaded_xx_provider is missing.
+ snprintf(buf, sizeof(buf), "provider#%s#Call", name);
+ if (!!find_func((char_u *)buf) && p_lpl) {
+ emsgf("provider: %s: missing required variable g:loaded_%s_provider",
+ name, name);
}
+ return false;
}
- return has_ruby;
}
- return false;
+ bool ok = (tv.v_type == VAR_NUMBER)
+ ? 2 == tv.vval.v_number // Value of 2 means "loaded and working".
+ : false;
+
+ if (ok) {
+ // Call() must be defined if provider claims to be working.
+ snprintf(buf, sizeof(buf), "provider#%s#Call", name);
+ if (!find_func((char_u *)buf)) {
+ emsgf("provider: %s: g:loaded_%s_provider=2 but %s is not defined",
+ name, name, buf);
+ ok = false;
+ }
+ }
+
+ return ok;
}
/// Writes "<sourcing_name>:<sourcing_lnum>" to `buf[bufsize]`.
diff --git a/src/nvim/eval.h b/src/nvim/eval.h
index 149dae688e..abe032a96e 100644
--- a/src/nvim/eval.h
+++ b/src/nvim/eval.h
@@ -24,6 +24,13 @@ EXTERN ufunc_T dumuf;
#define HIKEY2UF(p) ((ufunc_T *)(p - offsetof(ufunc_T, uf_name)))
#define HI2UF(hi) HIKEY2UF((hi)->hi_key)
+/// enum used by var_flavour()
+typedef enum {
+ VAR_FLAVOUR_DEFAULT = 1, // doesn't start with uppercase
+ VAR_FLAVOUR_SESSION = 2, // starts with uppercase, some lower
+ VAR_FLAVOUR_SHADA = 4 // all uppercase
+} var_flavour_T;
+
/// Defines for Vim variables
typedef enum {
VV_COUNT,
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index f36a7ea6c0..db45409e77 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -20,10 +20,10 @@ return {
['and']={args=2},
api_info={},
append={args=2},
- argc={},
+ argc={args={0, 1}},
argidx={},
arglistid={args={0, 2}},
- argv={args={0, 1}},
+ argv={args={0, 2}},
asin={args=1, func="float_op_wrapper", data="&asin"}, -- WJMc
assert_beeps={args={1, 2}},
assert_equal={args={2, 3}},
@@ -40,11 +40,13 @@ return {
atan2={args=2},
browse={args=4},
browsedir={args=2},
+ bufadd={args=1},
bufexists={args=1},
buffer_exists={args=1, func='f_bufexists'}, -- obsolete
buffer_name={args=1, func='f_bufname'}, -- obsolete
buffer_number={args=1, func='f_bufnr'}, -- obsolete
buflisted={args=1},
+ bufload={args=1},
bufloaded={args=1},
bufname={args=1},
bufnr={args={1, 2}},
@@ -72,6 +74,11 @@ return {
cosh={args=1, func="float_op_wrapper", data="&cosh"},
count={args={2, 4}},
cscope_connection={args={0, 3}},
+ ctxget={args={0, 1}},
+ ctxpop={},
+ ctxpush={args={0, 1}},
+ ctxset={args={1, 2}},
+ ctxsize={},
cursor={args={1, 3}},
deepcopy={args={1, 2}},
delete={args={1,2}},
@@ -82,6 +89,7 @@ return {
diff_filler={args=1},
diff_hlID={args=2},
empty={args=1},
+ environ={},
escape={args=2},
eval={args=1},
eventhandler={},
@@ -128,6 +136,7 @@ return {
getcompletion={args={2, 3}},
getcurpos={},
getcwd={args={0,2}},
+ getenv={args={1}},
getfontname={args={0, 1}},
getfperm={args=1},
getfsize={args=1},
@@ -263,9 +272,11 @@ return {
serverlist={},
serverstart={args={0, 1}},
serverstop={args=1},
+ setbufline={args=3},
setbufvar={args=3},
setcharsearch={args=1},
setcmdpos={args=1},
+ setenv={args=2},
setfperm={args=2},
setline={args=2},
setloclist={args={2, 4}},
diff --git a/src/nvim/eval/executor.c b/src/nvim/eval/executor.c
index e972c506dd..8cd21f8d62 100644
--- a/src/nvim/eval/executor.c
+++ b/src/nvim/eval/executor.c
@@ -74,8 +74,8 @@ int eexe_mod_op(typval_T *const tv1, const typval_T *const tv2,
case '+': n += tv_get_number(tv2); break;
case '-': n -= tv_get_number(tv2); break;
case '*': n *= tv_get_number(tv2); break;
- case '/': n /= tv_get_number(tv2); break;
- case '%': n %= tv_get_number(tv2); break;
+ case '/': n = num_divide(n, tv_get_number(tv2)); break;
+ case '%': n = num_modulus(n, tv_get_number(tv2)); break;
}
tv_clear(tv1);
tv1->v_type = VAR_NUMBER;
diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c
index ffb46abfea..91a1d083c7 100644
--- a/src/nvim/eval/typval.c
+++ b/src/nvim/eval/typval.c
@@ -1221,7 +1221,8 @@ void tv_dict_watcher_notify(dict_T *const dict, const char *const key,
/// Allocate a dictionary item
///
-/// @note that the value of the item (->di_tv) still needs to be initialized.
+/// @note that the type and value of the item (->di_tv) still needs to
+/// be initialized.
///
/// @param[in] key Key, is copied to the new item.
/// @param[in] key_len Key length.
@@ -1235,12 +1236,14 @@ dictitem_T *tv_dict_item_alloc_len(const char *const key, const size_t key_len)
memcpy(di->di_key, key, key_len);
di->di_key[key_len] = NUL;
di->di_flags = DI_FLAGS_ALLOC;
+ di->di_tv.v_lock = VAR_UNLOCKED;
return di;
}
/// Allocate a dictionary item
///
-/// @note that the value of the item (->di_tv) still needs to be initialized.
+/// @note that the type and value of the item (->di_tv) still needs to
+/// be initialized.
///
/// @param[in] key Key, is copied to the new item.
///
@@ -1572,7 +1575,6 @@ int tv_dict_add_list(dict_T *const d, const char *const key,
{
dictitem_T *const item = tv_dict_item_alloc_len(key, key_len);
- item->di_tv.v_lock = VAR_UNLOCKED;
item->di_tv.v_type = VAR_LIST;
item->di_tv.vval.v_list = list;
tv_list_ref(list);
@@ -1597,7 +1599,6 @@ int tv_dict_add_dict(dict_T *const d, const char *const key,
{
dictitem_T *const item = tv_dict_item_alloc_len(key, key_len);
- item->di_tv.v_lock = VAR_UNLOCKED;
item->di_tv.v_type = VAR_DICT;
item->di_tv.vval.v_dict = dict;
dict->dv_refcount++;
@@ -1621,7 +1622,6 @@ int tv_dict_add_nr(dict_T *const d, const char *const key,
{
dictitem_T *const item = tv_dict_item_alloc_len(key, key_len);
- item->di_tv.v_lock = VAR_UNLOCKED;
item->di_tv.v_type = VAR_NUMBER;
item->di_tv.vval.v_number = nr;
if (tv_dict_add(d, item) == FAIL) {
@@ -1644,7 +1644,6 @@ int tv_dict_add_special(dict_T *const d, const char *const key,
{
dictitem_T *const item = tv_dict_item_alloc_len(key, key_len);
- item->di_tv.v_lock = VAR_UNLOCKED;
item->di_tv.v_type = VAR_SPECIAL;
item->di_tv.vval.v_special = val;
if (tv_dict_add(d, item) == FAIL) {
@@ -1706,7 +1705,6 @@ int tv_dict_add_allocated_str(dict_T *const d,
{
dictitem_T *const item = tv_dict_item_alloc_len(key, key_len);
- item->di_tv.v_lock = VAR_UNLOCKED;
item->di_tv.v_type = VAR_STRING;
item->di_tv.vval.v_string = (char_u *)val;
if (tv_dict_add(d, item) == FAIL) {
diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h
index e99289c430..5c0f872b38 100644
--- a/src/nvim/eval/typval.h
+++ b/src/nvim/eval/typval.h
@@ -260,6 +260,7 @@ struct ufunc {
garray_T uf_args; ///< arguments
garray_T uf_lines; ///< function lines
int uf_profiling; ///< true when func is being profiled
+ int uf_prof_initialized;
// Profiling the function as a whole.
int uf_tm_count; ///< nr of calls
proftime_T uf_tm_total; ///< time spent in function + children
diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h
index 623bdfc93b..289c3ee99c 100644
--- a/src/nvim/eval/typval_encode.c.h
+++ b/src/nvim/eval/typval_encode.c.h
@@ -382,7 +382,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE(
case VAR_SPECIAL: {
switch (tv->vval.v_special) {
case kSpecialVarNull: {
- TYPVAL_ENCODE_CONV_NIL(tv);
+ TYPVAL_ENCODE_CONV_NIL(tv); // -V1037
break;
}
case kSpecialVarTrue:
diff --git a/src/nvim/event/libuv_process.c b/src/nvim/event/libuv_process.c
index ffe2db9b76..63efee59a8 100644
--- a/src/nvim/event/libuv_process.c
+++ b/src/nvim/event/libuv_process.c
@@ -101,6 +101,10 @@ static void close_cb(uv_handle_t *handle)
static void exit_cb(uv_process_t *handle, int64_t status, int term_signal)
{
Process *proc = handle->data;
- proc->status = (int)status;
+#if defined(WIN32)
+ // Use stored/expected signal.
+ term_signal = proc->exit_signal;
+#endif
+ proc->status = term_signal ? 128 + term_signal : (int)status;
proc->internal_exit_cb(proc);
}
diff --git a/src/nvim/event/process.c b/src/nvim/event/process.c
index 7a8a39dbcf..c31ecdaddf 100644
--- a/src/nvim/event/process.c
+++ b/src/nvim/event/process.c
@@ -26,6 +26,11 @@
// For PTY processes SIGTERM is sent first (in case SIGHUP was not enough).
#define KILL_TIMEOUT_MS 2000
+/// Externally defined with gcov.
+#ifdef USE_GCOV
+void __gcov_flush(void);
+#endif
+
static bool process_is_tearing_down = false;
/// @returns zero on success, or negative error code
@@ -50,6 +55,11 @@ int process_spawn(Process *proc, bool in, bool out, bool err)
proc->err.closed = true;
}
+#ifdef USE_GCOV
+ // Flush coverage data before forking, to avoid "Merge mismatch" errors.
+ __gcov_flush();
+#endif
+
int status;
switch (proc->type) {
case kProcessTypeUv:
@@ -149,7 +159,7 @@ void process_close_streams(Process *proc) FUNC_ATTR_NONNULL_ALL
/// 0 for no wait. -1 to wait until the process quits.
/// @return Exit code of the process. proc->status will have the same value.
/// -1 if the timeout expired while the process is still running.
-/// -2 if the user interruped the wait.
+/// -2 if the user interrupted the wait.
int process_wait(Process *proc, int ms, MultiQueue *events)
FUNC_ATTR_NONNULL_ARG(1)
{
@@ -210,13 +220,10 @@ void process_stop(Process *proc) FUNC_ATTR_NONNULL_ALL
return;
}
proc->stopped_time = os_hrtime();
+ proc->exit_signal = SIGTERM;
switch (proc->type) {
case kProcessTypeUv:
- // Close the process's stdin. If the process doesn't close its own
- // stdout/stderr, they will be closed when it exits(possibly due to being
- // terminated after a timeout)
- stream_may_close(&proc->in);
os_proc_tree_kill(proc->pid, SIGTERM);
break;
case kProcessTypePty:
@@ -247,8 +254,10 @@ static void children_kill_cb(uv_timer_t *handle)
}
uint64_t term_sent = UINT64_MAX == proc->stopped_time;
if (kProcessTypePty != proc->type || term_sent) {
+ proc->exit_signal = SIGKILL;
os_proc_tree_kill(proc->pid, SIGKILL);
} else {
+ proc->exit_signal = SIGTERM;
os_proc_tree_kill(proc->pid, SIGTERM);
proc->stopped_time = UINT64_MAX; // Flag: SIGTERM was sent.
// Restart timer.
@@ -397,4 +406,3 @@ static void on_process_stream_close(Stream *stream, void *data)
Process *proc = data;
decref(proc);
}
-
diff --git a/src/nvim/event/process.h b/src/nvim/event/process.h
index ba2c2a6a11..ef9d953ab7 100644
--- a/src/nvim/event/process.h
+++ b/src/nvim/event/process.h
@@ -19,6 +19,7 @@ struct process {
Loop *loop;
void *data;
int pid, status, refcount;
+ uint8_t exit_signal; // Signal used when killing (on Windows).
uint64_t stopped_time; // process_stop() timestamp
const char *cwd;
char **argv;
@@ -56,7 +57,8 @@ static inline Process process_init(Loop *loop, ProcessType type, void *data)
static inline bool process_is_stopped(Process *proc)
{
- return proc->stopped_time != 0;
+ bool exited = (proc->status >= 0);
+ return exited || (proc->stopped_time != 0);
}
#ifdef INCLUDE_GENERATED_DECLARATIONS
diff --git a/src/nvim/event/stream.c b/src/nvim/event/stream.c
index 7aaac0b03b..7c8014dead 100644
--- a/src/nvim/event/stream.c
+++ b/src/nvim/event/stream.c
@@ -113,6 +113,11 @@ void stream_close_handle(Stream *stream)
FUNC_ATTR_NONNULL_ALL
{
if (stream->uvstream) {
+ if (uv_stream_get_write_queue_size(stream->uvstream) > 0) {
+ WLOG("closed Stream (%p) with %zu unwritten bytes",
+ (void *)stream,
+ uv_stream_get_write_queue_size(stream->uvstream));
+ }
uv_close((uv_handle_t *)stream->uvstream, close_cb);
} else {
uv_close((uv_handle_t *)&stream->uv.idle, close_cb);
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index abed909008..a0fbde008b 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -20,6 +20,7 @@
#include "nvim/ascii.h"
#include "nvim/ex_cmds.h"
#include "nvim/buffer.h"
+#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/diff.h"
@@ -335,7 +336,7 @@ static int linelen(int *has_tab)
len = linetabsize(line);
// Check for embedded TAB.
if (has_tab != NULL) {
- *has_tab = STRRCHR(first, TAB) != NULL;
+ *has_tab = vim_strchr(first, TAB) != NULL;
}
*last = save;
@@ -1230,7 +1231,7 @@ static void do_filter(
/* Create the shell command in allocated memory. */
cmd_buf = make_filter_cmd(cmd, itmp, otmp);
- ui_cursor_goto((int)Rows - 1, 0);
+ ui_cursor_goto(Rows - 1, 0);
if (do_out) {
if (u_save((linenr_T)(line2), (linenr_T)(line2 + 1)) == FAIL) {
@@ -1931,11 +1932,12 @@ void do_wqall(exarg_T *eap)
int error = 0;
int save_forceit = eap->forceit;
- if (eap->cmdidx == CMD_xall || eap->cmdidx == CMD_wqall)
- exiting = TRUE;
+ if (eap->cmdidx == CMD_xall || eap->cmdidx == CMD_wqall) {
+ exiting = true;
+ }
FOR_ALL_BUFFERS(buf) {
- if (!bufIsChanged(buf)) {
+ if (!bufIsChanged(buf) || bt_dontwrite(buf)) {
continue;
}
/*
@@ -2073,7 +2075,7 @@ int getfile(int fnum, char_u *ffname, char_u *sfname, int setpm, linenr_T lnum,
}
if (curbufIsChanged()) {
no_wait_return--;
- EMSG(_(e_nowrtmsg));
+ no_write_message();
retval = GETFILE_NOT_WRITTEN; // File has been changed.
goto theend;
}
@@ -4553,7 +4555,7 @@ void ex_help(exarg_T *eap)
} else {
// There is no help window yet.
// Try to open the file specified by the "helpfile" option.
- if ((helpfd = mch_fopen((char *)p_hf, READBIN)) == NULL) {
+ if ((helpfd = os_fopen((char *)p_hf, READBIN)) == NULL) {
smsg(_("Sorry, help file \"%s\" not found"), p_hf);
goto erret;
}
@@ -5083,7 +5085,7 @@ void fix_help_buffer(void)
continue;
}
- FILE *const fd = mch_fopen((char *)fnames[fi], "r");
+ FILE *const fd = os_fopen((char *)fnames[fi], "r");
if (fd == NULL) {
continue;
}
@@ -5219,17 +5221,15 @@ static void helptags_one(char_u *const dir, const char_u *const ext,
return;
}
- FILE *const fd_tags = mch_fopen((char *)NameBuff, "w");
+ FILE *const fd_tags = os_fopen((char *)NameBuff, "w");
if (fd_tags == NULL) {
EMSG2(_("E152: Cannot open %s for writing"), NameBuff);
FreeWild(filecount, files);
return;
}
- /*
- * If using the "++t" argument or generating tags for "$VIMRUNTIME/doc"
- * add the "help-tags" tag.
- */
+ // If using the "++t" argument or generating tags for "$VIMRUNTIME/doc"
+ // add the "help-tags" tag.
ga_init(&ga, (int)sizeof(char_u *), 100);
if (add_help_tags
|| path_full_compare((char_u *)"$VIMRUNTIME/doc",
@@ -5239,11 +5239,9 @@ static void helptags_one(char_u *const dir, const char_u *const ext,
GA_APPEND(char_u *, &ga, s);
}
- /*
- * Go over all the files and extract the tags.
- */
+ // Go over all the files and extract the tags.
for (int fi = 0; fi < filecount && !got_int; fi++) {
- FILE *const fd = mch_fopen((char *)files[fi], "r");
+ FILE *const fd = os_fopen((char *)files[fi], "r");
if (fd == NULL) {
EMSG2(_("E153: Unable to open %s for reading"), files[fi]);
continue;
@@ -5281,21 +5279,19 @@ static void helptags_one(char_u *const dir, const char_u *const ext,
}
firstline = false;
}
- p1 = vim_strchr(IObuff, '*'); /* find first '*' */
+ p1 = vim_strchr(IObuff, '*'); // find first '*'
while (p1 != NULL) {
p2 = (char_u *)strchr((const char *)p1 + 1, '*'); // Find second '*'.
- if (p2 != NULL && p2 > p1 + 1) { // Skip "*" and "**".
+ if (p2 != NULL && p2 > p1 + 1) { // Skip "*" and "**".
for (s = p1 + 1; s < p2; s++) {
if (*s == ' ' || *s == '\t' || *s == '|') {
break;
}
}
- /*
- * Only accept a *tag* when it consists of valid
- * characters, there is white space before it and is
- * followed by a white character or end-of-line.
- */
+ // Only accept a *tag* when it consists of valid
+ // characters, there is white space before it and is
+ // followed by a white character or end-of-line.
if (s == p2
&& (p1 == IObuff || p1[-1] == ' ' || p1[-1] == '\t')
&& (vim_strchr((char_u *)" \t\n\r", s[1]) != NULL
@@ -5306,7 +5302,7 @@ static void helptags_one(char_u *const dir, const char_u *const ext,
GA_APPEND(char_u *, &ga, s);
sprintf((char *)s, "%s\t%s", p1, fname);
- /* find next '*' */
+ // find next '*'
p2 = vim_strchr(p2 + 1, '*');
}
}
@@ -5320,18 +5316,12 @@ static void helptags_one(char_u *const dir, const char_u *const ext,
FreeWild(filecount, files);
- if (!got_int) {
- /*
- * Sort the tags.
- */
- if (ga.ga_data != NULL) {
- sort_strings((char_u **)ga.ga_data, ga.ga_len);
- }
+ if (!got_int && ga.ga_data != NULL) {
+ // Sort the tags.
+ sort_strings((char_u **)ga.ga_data, ga.ga_len);
- /*
- * Check for duplicates.
- */
- for (int i = 1; i < ga.ga_len; ++i) {
+ // Check for duplicates.
+ for (int i = 1; i < ga.ga_len; i++) {
p1 = ((char_u **)ga.ga_data)[i - 1];
p2 = ((char_u **)ga.ga_data)[i];
while (*p1 == *p2) {
@@ -5353,31 +5343,31 @@ static void helptags_one(char_u *const dir, const char_u *const ext,
fprintf(fd_tags, "!_TAG_FILE_ENCODING\tutf-8\t//\n");
}
- /*
- * Write the tags into the file.
- */
- for (int i = 0; i < ga.ga_len; ++i) {
+ // Write the tags into the file.
+ for (int i = 0; i < ga.ga_len; i++) {
s = ((char_u **)ga.ga_data)[i];
- if (STRNCMP(s, "help-tags\t", 10) == 0)
- /* help-tags entry was added in formatted form */
+ if (STRNCMP(s, "help-tags\t", 10) == 0) {
+ // help-tags entry was added in formatted form
fputs((char *)s, fd_tags);
- else {
- fprintf(fd_tags, "%s\t/*", s);
- for (p1 = s; *p1 != '\t'; ++p1) {
- /* insert backslash before '\\' and '/' */
- if (*p1 == '\\' || *p1 == '/')
+ } else {
+ fprintf(fd_tags, "%s\t/" "*", s);
+ for (p1 = s; *p1 != '\t'; p1++) {
+ // insert backslash before '\\' and '/'
+ if (*p1 == '\\' || *p1 == '/') {
putc('\\', fd_tags);
+ }
putc(*p1, fd_tags);
}
fprintf(fd_tags, "*\n");
}
}
}
- if (mix)
- got_int = FALSE; /* continue with other languages */
+ if (mix) {
+ got_int = false; // continue with other languages
+ }
GA_DEEP_CLEAR_PTR(&ga);
- fclose(fd_tags); /* there is no check for an error... */
+ fclose(fd_tags); // there is no check for an error...
}
/// Generate tags in one help directory, taking care of translations.
diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua
index 0f69d476f9..8c0d22809f 100644
--- a/src/nvim/ex_cmds.lua
+++ b/src/nvim/ex_cmds.lua
@@ -601,6 +601,12 @@ return {
func='ex_wrongmodifier',
},
{
+ command='const',
+ flags=bit.bor(NEEDARG, EXTRA, NOTRLCOM, CMDWIN),
+ addr_type=ADDR_LINES,
+ func='ex_const',
+ },
+ {
command='copen',
flags=bit.bor(RANGE, NOTADR, COUNT, TRLBAR),
addr_type=ADDR_LINES,
@@ -2145,6 +2151,12 @@ return {
func='ex_redrawstatus',
},
{
+ command='redrawtabline',
+ flags=bit.bor(TRLBAR, CMDWIN),
+ addr_type=ADDR_LINES,
+ func='ex_redrawtabline',
+ },
+ {
command='registers',
flags=bit.bor(EXTRA, NOTRLCOM, TRLBAR, CMDWIN),
addr_type=ADDR_LINES,
@@ -2793,6 +2805,12 @@ return {
func='ex_tag',
},
{
+ command='tmenu',
+ flags=bit.bor(RANGE, NOTADR, ZEROR, EXTRA, TRLBAR, NOTRLCOM, USECTRLV, CMDWIN),
+ addr_type=ADDR_LINES,
+ func='ex_menu',
+ },
+ {
command='tmap',
flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, USECTRLV, CMDWIN),
addr_type=ADDR_LINES,
@@ -2805,12 +2823,6 @@ return {
func='ex_mapclear',
},
{
- command='tmenu',
- flags=bit.bor(RANGE, NOTADR, ZEROR, EXTRA, TRLBAR, NOTRLCOM, USECTRLV, CMDWIN),
- addr_type=ADDR_LINES,
- func='ex_menu',
- },
- {
command='tnext',
flags=bit.bor(RANGE, NOTADR, BANG, TRLBAR, ZEROR),
addr_type=ADDR_LINES,
@@ -2853,16 +2865,16 @@ return {
func='ex_tag',
},
{
- command='tunmap',
+ command='tunmenu',
flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, USECTRLV, CMDWIN),
addr_type=ADDR_LINES,
- func='ex_unmap',
+ func='ex_menu',
},
{
- command='tunmenu',
+ command='tunmap',
flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, USECTRLV, CMDWIN),
addr_type=ADDR_LINES,
- func='ex_menu',
+ func='ex_unmap',
},
{
command='undo',
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c
index 5ad285c387..408c6dce79 100644
--- a/src/nvim/ex_cmds2.c
+++ b/src/nvim/ex_cmds2.c
@@ -18,6 +18,7 @@
#endif
#include "nvim/ex_cmds2.h"
#include "nvim/buffer.h"
+#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/eval.h"
#include "nvim/ex_cmds.h"
@@ -43,6 +44,7 @@
#include "nvim/screen.h"
#include "nvim/strings.h"
#include "nvim/undo.h"
+#include "nvim/version.h"
#include "nvim/window.h"
#include "nvim/profile.h"
#include "nvim/os/os.h"
@@ -611,7 +613,7 @@ static int dbg_parsearg(char_u *arg, garray_T *gap)
return OK;
}
-/// ":breakadd".
+/// ":breakadd". Also used for ":profile".
void ex_breakadd(exarg_T *eap)
{
struct debuggy *bp;
@@ -989,7 +991,7 @@ void profile_dump(void)
FILE *fd;
if (profile_fname != NULL) {
- fd = mch_fopen((char *)profile_fname, "w");
+ fd = os_fopen((char *)profile_fname, "w");
if (fd == NULL) {
EMSG2(_(e_notopen), profile_fname);
} else {
@@ -1138,7 +1140,7 @@ static void script_dump_profile(FILE *fd)
fprintf(fd, "\n");
fprintf(fd, "count total (s) self (s)\n");
- sfd = mch_fopen((char *)si->sn_name, "r");
+ sfd = os_fopen((char *)si->sn_name, "r");
if (sfd == NULL) {
fprintf(fd, "Cannot open file!\n");
} else {
@@ -1274,9 +1276,9 @@ bool check_changed(buf_T *buf, int flags)
return bufIsChanged(buf);
}
if (flags & CCGD_EXCMD) {
- EMSG(_(e_nowrtmsg));
+ no_write_message();
} else {
- EMSG(_(e_nowrtmsg_nobang));
+ no_write_message_nobang();
}
return true;
}
@@ -1791,19 +1793,15 @@ void ex_args(exarg_T *eap)
} else if (eap->cmdidx == CMD_args) {
// ":args": list arguments.
if (ARGCOUNT > 0) {
+ char_u **items = xmalloc(sizeof(char_u *) * (size_t)ARGCOUNT);
// Overwrite the command, for a short list there is no scrolling
// required and no wait_return().
gotocmdline(true);
for (int i = 0; i < ARGCOUNT; i++) {
- if (i == curwin->w_arg_idx) {
- msg_putchar('[');
- }
- msg_outtrans(alist_name(&ARGLIST[i]));
- if (i == curwin->w_arg_idx) {
- msg_putchar(']');
- }
- msg_putchar(' ');
+ items[i] = alist_name(&ARGLIST[i]);
}
+ list_in_columns(items, ARGCOUNT, curwin->w_arg_idx);
+ xfree(items);
}
} else if (eap->cmdidx == CMD_arglocal) {
garray_T *gap = &curwin->w_alist->al_ga;
@@ -2858,7 +2856,7 @@ static int requires_py_version(char_u *filename)
lines = 5;
}
- file = mch_fopen((char *)filename, "r");
+ file = os_fopen((char *)filename, "r");
if (file != NULL) {
for (i = 0; i < lines; i++) {
if (vim_fgets(IObuff, IOSIZE, file)) {
@@ -3039,6 +3037,7 @@ int do_source(char_u *fname, int check_other, int is_vimrc)
int save_debug_break_level = debug_break_level;
scriptitem_T *si = NULL;
proftime_T wait_start;
+ bool trigger_source_post = false;
p = expand_env_save(fname);
if (p == NULL) {
@@ -3059,6 +3058,10 @@ int do_source(char_u *fname, int check_other, int is_vimrc)
&& apply_autocmds(EVENT_SOURCECMD, fname_exp, fname_exp,
false, curbuf)) {
retval = aborting() ? FAIL : OK;
+ if (retval == OK) {
+ // Apply SourcePost autocommands.
+ apply_autocmds(EVENT_SOURCEPOST, fname_exp, fname_exp, false, curbuf);
+ }
goto theend;
}
@@ -3181,7 +3184,7 @@ int do_source(char_u *fname, int check_other, int is_vimrc)
}
si = &SCRIPT_ITEM(current_SID);
si->sn_name = fname_exp;
- fname_exp = NULL;
+ fname_exp = vim_strsave(si->sn_name); // used for autocmd
if (file_id_ok) {
si->file_id_valid = true;
si->file_id = file_id;
@@ -3261,6 +3264,10 @@ int do_source(char_u *fname, int check_other, int is_vimrc)
time_pop(rel_time);
}
+ if (!got_int) {
+ trigger_source_post = true;
+ }
+
// After a "finish" in debug mode, need to break at first command of next
// sourced file.
if (save_debug_break_level > ex_nesting_level
@@ -3278,6 +3285,10 @@ int do_source(char_u *fname, int check_other, int is_vimrc)
xfree(firstline);
convert_setup(&cookie.conv, NULL, NULL);
+ if (trigger_source_post) {
+ apply_autocmds(EVENT_SOURCEPOST, fname_exp, fname_exp, false, curbuf);
+ }
+
theend:
xfree(fname_exp);
return retval;
diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h
index 6c36922c09..bc7e1e9b59 100644
--- a/src/nvim/ex_cmds_defs.h
+++ b/src/nvim/ex_cmds_defs.h
@@ -143,7 +143,7 @@ struct exarg {
struct expand {
int xp_context; // type of expansion
char_u *xp_pattern; // start of item to expand
- int xp_pattern_len; // bytes in xp_pattern before cursor
+ size_t xp_pattern_len; // bytes in xp_pattern before cursor
char_u *xp_arg; // completion function
int xp_scriptID; // SID for completion function
int xp_backslash; // one of the XP_BS_ values
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index fb15bd4e66..0f345df22b 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -16,6 +16,7 @@
#include "nvim/ascii.h"
#include "nvim/ex_docmd.h"
#include "nvim/buffer.h"
+#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/diff.h"
@@ -1108,9 +1109,8 @@ static int current_tab_nr(tabpage_T *tab)
#define CURRENT_TAB_NR current_tab_nr(curtab)
#define LAST_TAB_NR current_tab_nr(NULL)
-/*
-* Figure out the address type for ":wincmd".
-*/
+
+/// Figure out the address type for ":wincmd".
static void get_wincmd_addr_type(char_u *arg, exarg_T *eap)
{
switch (*arg) {
@@ -1156,13 +1156,13 @@ static void get_wincmd_addr_type(char_u *arg, exarg_T *eap)
case Ctrl_I:
case 'd':
case Ctrl_D:
- /* window size or any count */
- eap->addr_type = ADDR_LINES;
+ // window size or any count
+ eap->addr_type = ADDR_LINES; // -V1037
break;
case Ctrl_HAT:
case '^':
- /* buffer number */
+ // buffer number
eap->addr_type = ADDR_BUFFERS;
break;
@@ -1177,7 +1177,7 @@ static void get_wincmd_addr_type(char_u *arg, exarg_T *eap)
case 'W':
case 'x':
case Ctrl_X:
- /* window number */
+ // window number
eap->addr_type = ADDR_WINDOWS;
break;
@@ -1192,7 +1192,7 @@ static void get_wincmd_addr_type(char_u *arg, exarg_T *eap)
case Ctrl_P:
case '=':
case CAR:
- /* no count */
+ // no count
eap->addr_type = 0;
break;
}
@@ -2452,7 +2452,7 @@ static char_u *find_command(exarg_T *eap, int *full)
if (ASCII_ISLOWER(eap->cmd[0])) {
const int c1 = eap->cmd[0];
- const int c2 = eap->cmd[1];
+ const int c2 = len == 1 ? NUL : eap->cmd[1];
if (command_count != (int)CMD_SIZE) {
iemsg((char *)_("E943: Command table needs to be updated, run 'make'"));
@@ -2503,10 +2503,10 @@ static char_u *find_command(exarg_T *eap, int *full)
static char_u *
find_ucmd (
exarg_T *eap,
- char_u *p, /* end of the command (possibly including count) */
- int *full, /* set to TRUE for a full match */
- expand_T *xp, /* used for completion, NULL otherwise */
- int *compl /* completion flags or NULL */
+ char_u *p, // end of the command (possibly including count)
+ int *full, // set to TRUE for a full match
+ expand_T *xp, // used for completion, NULL otherwise
+ int *complp // completion flags or NULL
)
{
int len = (int)(p - eap->cmd);
@@ -2559,8 +2559,9 @@ find_ucmd (
eap->useridx = j;
eap->addr_type = uc->uc_addr_type;
- if (compl != NULL)
- *compl = uc->uc_compl;
+ if (complp != NULL) {
+ *complp = uc->uc_compl;
+ }
if (xp != NULL) {
xp->xp_arg = uc->uc_compl_arg;
xp->xp_scriptID = uc->uc_scriptID;
@@ -4060,10 +4061,9 @@ static char_u *replace_makeprg(exarg_T *eap, char_u *p, char_u **cmdlinep)
return p;
}
-/*
- * Expand file name in Ex command argument.
- * Return FAIL for failure, OK otherwise.
- */
+// Expand file name in Ex command argument.
+// When an error is detected, "errormsgp" is set to a non-NULL pointer.
+// Return FAIL for failure, OK otherwise.
int expand_filename(exarg_T *eap, char_u **cmdlinep, char_u **errormsgp)
{
int has_wildcards; /* need to expand wildcards */
@@ -4614,7 +4614,7 @@ static void ex_doautocmd(exarg_T *eap)
int call_do_modelines = check_nomodeline(&arg);
bool did_aucmd;
- (void)do_doautocmd(arg, true, &did_aucmd);
+ (void)do_doautocmd(arg, false, &did_aucmd);
// Only when there is no <nomodeline>.
if (call_do_modelines && did_aucmd) {
do_modelines(0);
@@ -5088,7 +5088,7 @@ static void uc_list(char_u *name, size_t name_len)
}
static int uc_scan_attr(char_u *attr, size_t len, uint32_t *argt, long *def,
- int *flags, int * compl, char_u **compl_arg,
+ int *flags, int *complp, char_u **compl_arg,
int *addr_type_arg)
{
char_u *p;
@@ -5185,9 +5185,10 @@ invalid_count:
return FAIL;
}
- if (parse_compl_arg(val, (int)vallen, compl, argt, compl_arg)
- == FAIL)
+ if (parse_compl_arg(val, (int)vallen, complp, argt, compl_arg)
+ == FAIL) {
return FAIL;
+ }
} else if (STRNICMP(attr, "addr", attrlen) == 0) {
*argt |= RANGE;
if (val == NULL) {
@@ -6018,7 +6019,7 @@ static void ex_highlight(exarg_T *eap)
*/
void not_exiting(void)
{
- exiting = FALSE;
+ exiting = false;
}
static bool before_quit_autocmds(win_T *wp, bool quit_all, int forceit)
@@ -6216,7 +6217,7 @@ ex_win_close(
}
need_hide = false;
} else {
- EMSG(_(e_nowrtmsg));
+ no_write_message();
return;
}
}
@@ -6418,7 +6419,7 @@ static void ex_stop(exarg_T *eap)
apply_autocmds(EVENT_VIMSUSPEND, NULL, NULL, false, NULL);
// TODO(bfredl): the TUI should do this on suspend
- ui_cursor_goto((int)Rows - 1, 0);
+ ui_cursor_goto(Rows - 1, 0);
ui_call_grid_scroll(1, 0, Rows, 0, Columns, 1, 0);
ui_flush();
ui_call_suspend(); // call machine specific function
@@ -6762,8 +6763,9 @@ void ex_splitview(exarg_T *eap)
if (*eap->arg != NUL
) {
RESET_BINDING(curwin);
- } else
- do_check_scrollbind(FALSE);
+ } else {
+ do_check_scrollbind(false);
+ }
do_exedit(eap, old_curwin);
}
@@ -6922,16 +6924,17 @@ static void ex_resize(exarg_T *eap)
n = atol((char *)eap->arg);
if (cmdmod.split & WSP_VERT) {
- if (*eap->arg == '-' || *eap->arg == '+')
+ if (*eap->arg == '-' || *eap->arg == '+') {
n += curwin->w_width;
- else if (n == 0 && eap->arg[0] == NUL) /* default is very wide */
- n = 9999;
+ } else if (n == 0 && eap->arg[0] == NUL) { // default is very wide
+ n = Columns;
+ }
win_setwidth_win(n, wp);
} else {
if (*eap->arg == '-' || *eap->arg == '+') {
n += curwin->w_height;
} else if (n == 0 && eap->arg[0] == NUL) { // default is very high
- n = 9999;
+ n = Rows-1;
}
win_setheight_win(n, wp);
}
@@ -7520,7 +7523,7 @@ static void ex_operators(exarg_T *eap)
case CMD_yank:
oa.op_type = OP_YANK;
- (void)op_yank(&oa, true);
+ (void)op_yank(&oa, true, false);
break;
default: /* CMD_rshift or CMD_lshift */
@@ -7874,6 +7877,22 @@ static void ex_redrawstatus(exarg_T *eap)
ui_flush();
}
+// ":redrawtabline": force redraw of the tabline
+static void ex_redrawtabline(exarg_T *eap FUNC_ATTR_UNUSED)
+{
+ const int r = RedrawingDisabled;
+ const int p = p_lz;
+
+ RedrawingDisabled = 0;
+ p_lz = false;
+
+ draw_tabline();
+
+ RedrawingDisabled = r;
+ p_lz = p;
+ ui_flush();
+}
+
static void close_redir(void)
{
if (redir_fd != NULL) {
@@ -8090,8 +8109,9 @@ open_exfile (
return NULL;
}
- if ((fd = mch_fopen((char *)fname, mode)) == NULL)
+ if ((fd = os_fopen((char *)fname, mode)) == NULL) {
EMSG2(_("E190: Cannot open \"%s\" for writing"), fname);
+ }
return fd;
}
@@ -8452,24 +8472,23 @@ static void ex_tag_cmd(exarg_T *eap, char_u *name)
int cmd;
switch (name[1]) {
- case 'j': cmd = DT_JUMP; /* ":tjump" */
+ case 'j': cmd = DT_JUMP; // ":tjump"
break;
- case 's': cmd = DT_SELECT; /* ":tselect" */
+ case 's': cmd = DT_SELECT; // ":tselect"
break;
- case 'p': cmd = DT_PREV; /* ":tprevious" */
+ case 'p': // ":tprevious"
+ case 'N': cmd = DT_PREV; // ":tNext"
break;
- case 'N': cmd = DT_PREV; /* ":tNext" */
+ case 'n': cmd = DT_NEXT; // ":tnext"
break;
- case 'n': cmd = DT_NEXT; /* ":tnext" */
+ case 'o': cmd = DT_POP; // ":pop"
break;
- case 'o': cmd = DT_POP; /* ":pop" */
+ case 'f': // ":tfirst"
+ case 'r': cmd = DT_FIRST; // ":trewind"
break;
- case 'f': /* ":tfirst" */
- case 'r': cmd = DT_FIRST; /* ":trewind" */
+ case 'l': cmd = DT_LAST; // ":tlast"
break;
- case 'l': cmd = DT_LAST; /* ":tlast" */
- break;
- default: /* ":tag" */
+ default: // ":tag"
if (p_cst && *eap->arg != NUL) {
ex_cstag(eap);
return;
@@ -9322,26 +9341,30 @@ static frame_T *ses_skipframe(frame_T *fr)
{
frame_T *frc;
- for (frc = fr; frc != NULL; frc = frc->fr_next)
- if (ses_do_frame(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 int ses_do_frame(frame_T *fr)
+// 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)
{
- frame_T *frc;
+ const frame_T *frc;
- if (fr->fr_layout == FR_LEAF)
+ if (fr->fr_layout == FR_LEAF) {
return ses_do_win(fr->fr_win);
- for (frc = fr->fr_child; frc != NULL; frc = frc->fr_next)
- if (ses_do_frame(frc))
- return TRUE;
- return FALSE;
+ }
+ 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.
@@ -9584,7 +9607,7 @@ ses_arglist(
if (fputs(cmd, fd) < 0 || put_eol(fd) == FAIL) {
return FAIL;
}
- if (put_line(fd, "silent! argdel *") == FAIL) {
+ if (put_line(fd, "%argdel") == FAIL) {
return FAIL;
}
for (int i = 0; i < gap->ga_len; ++i) {
@@ -10153,7 +10176,7 @@ Dictionary commands_array(buf_T *buf)
Dictionary rv = ARRAY_DICT_INIT;
Object obj = NIL;
(void)obj; // Avoid "dead assignment" warning.
- char str[10];
+ char str[20];
garray_T *gap = (buf == NULL) ? &ucmds : &buf->b_ucmds;
for (int i = 0; i < gap->ga_len; i++) {
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 479d195966..38432a34db 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -142,12 +142,12 @@ typedef struct command_line_state {
long count;
int indent;
int c;
- int i;
- int j;
int gotesc; // TRUE when <ESC> just typed
int do_abbr; // when TRUE check for abbr.
char_u *lookfor; // string to match
int hiscnt; // current history line in use
+ int save_hiscnt; // history line before attempting
+ // to jump to next match
int histype; // history type to be used
pos_T search_start; // where 'incsearch' starts searching
pos_T save_cursor;
@@ -200,7 +200,7 @@ static Array cmdline_block = ARRAY_DICT_INIT;
*/
typedef void *(*user_expand_func_T)(const char_u *,
int,
- const char_u * const *,
+ typval_T *,
bool);
static histentry_T *(history[HIST_COUNT]) = {NULL, NULL, NULL, NULL, NULL};
@@ -279,6 +279,8 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)
s->old_topfill = curwin->w_topfill;
s->old_botline = curwin->w_botline;
+ assert(indent >= 0);
+
// set some variables for redrawcmd()
ccline.cmdfirstc = (s->firstc == '@' ? 0 : s->firstc);
ccline.cmdindent = (s->firstc > 0 ? s->indent : 0);
@@ -294,7 +296,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)
// autoindent for :insert and :append
if (s->firstc <= 0) {
- memset(ccline.cmdbuff, ' ', s->indent);
+ memset(ccline.cmdbuff, ' ', (size_t)s->indent);
ccline.cmdbuff[s->indent] = NUL;
ccline.cmdpos = s->indent;
ccline.cmdspos = s->indent;
@@ -315,7 +317,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)
if (!cmd_silent) {
gotocmdline(true);
redrawcmdprompt(); // draw prompt or indent
- set_cmdspos();
+ ccline.cmdspos = cmd_startcol();
if (!msg_scroll) {
msg_ext_clear(false);
}
@@ -382,7 +384,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)
bool tl_ret = true;
dict_T *dict = get_vim_var_dict(VV_EVENT);
char firstcbuf[2];
- firstcbuf[0] = firstc > 0 ? firstc : '-';
+ firstcbuf[0] = (char)(firstc > 0 ? firstc : '-');
firstcbuf[1] = 0;
if (has_event(EVENT_CMDLINEENTER)) {
@@ -676,37 +678,37 @@ static int command_line_execute(VimState *state, int key)
// Hitting <Down> after "emenu Name.": complete submenu
if (s->c == K_DOWN && ccline.cmdpos > 0
&& ccline.cmdbuff[ccline.cmdpos - 1] == '.') {
- s->c = p_wc;
+ s->c = (int)p_wc;
} else if (s->c == K_UP) {
// Hitting <Up>: Remove one submenu name in front of the
// cursor
int found = false;
- s->j = (int)(s->xpc.xp_pattern - ccline.cmdbuff);
- s->i = 0;
- while (--s->j > 0) {
+ int j = (int)(s->xpc.xp_pattern - ccline.cmdbuff);
+ int i = 0;
+ while (--j > 0) {
// check for start of menu name
- if (ccline.cmdbuff[s->j] == ' '
- && ccline.cmdbuff[s->j - 1] != '\\') {
- s->i = s->j + 1;
+ if (ccline.cmdbuff[j] == ' '
+ && ccline.cmdbuff[j - 1] != '\\') {
+ i = j + 1;
break;
}
// check for start of submenu name
- if (ccline.cmdbuff[s->j] == '.'
- && ccline.cmdbuff[s->j - 1] != '\\') {
+ if (ccline.cmdbuff[j] == '.'
+ && ccline.cmdbuff[j - 1] != '\\') {
if (found) {
- s->i = s->j + 1;
+ i = j + 1;
break;
} else {
found = true;
}
}
}
- if (s->i > 0) {
- cmdline_del(s->i);
+ if (i > 0) {
+ cmdline_del(i);
}
- s->c = p_wc;
+ s->c = (int)p_wc;
s->xpc.xp_context = EXPAND_NOTHING;
}
}
@@ -728,44 +730,44 @@ static int command_line_execute(VimState *state, int key)
|| ccline.cmdbuff[ccline.cmdpos - 2] != '.'
|| ccline.cmdbuff[ccline.cmdpos - 3] != '.')) {
// go down a directory
- s->c = p_wc;
+ s->c = (int)p_wc;
} else if (STRNCMP(s->xpc.xp_pattern, upseg + 1, 3) == 0
&& s->c == K_DOWN) {
// If in a direct ancestor, strip off one ../ to go down
int found = false;
- s->j = ccline.cmdpos;
- s->i = (int)(s->xpc.xp_pattern - ccline.cmdbuff);
- while (--s->j > s->i) {
- s->j -= utf_head_off(ccline.cmdbuff, ccline.cmdbuff + s->j);
- if (vim_ispathsep(ccline.cmdbuff[s->j])) {
+ int j = ccline.cmdpos;
+ int i = (int)(s->xpc.xp_pattern - ccline.cmdbuff);
+ while (--j > i) {
+ j -= utf_head_off(ccline.cmdbuff, ccline.cmdbuff + j);
+ if (vim_ispathsep(ccline.cmdbuff[j])) {
found = true;
break;
}
}
if (found
- && ccline.cmdbuff[s->j - 1] == '.'
- && ccline.cmdbuff[s->j - 2] == '.'
- && (vim_ispathsep(ccline.cmdbuff[s->j - 3]) || s->j == s->i + 2)) {
- cmdline_del(s->j - 2);
- s->c = p_wc;
+ && ccline.cmdbuff[j - 1] == '.'
+ && ccline.cmdbuff[j - 2] == '.'
+ && (vim_ispathsep(ccline.cmdbuff[j - 3]) || j == i + 2)) {
+ cmdline_del(j - 2);
+ s->c = (int)p_wc;
}
} else if (s->c == K_UP) {
// go up a directory
int found = false;
- s->j = ccline.cmdpos - 1;
- s->i = (int)(s->xpc.xp_pattern - ccline.cmdbuff);
- while (--s->j > s->i) {
- s->j -= utf_head_off(ccline.cmdbuff, ccline.cmdbuff + s->j);
- if (vim_ispathsep(ccline.cmdbuff[s->j])
+ int j = ccline.cmdpos - 1;
+ int i = (int)(s->xpc.xp_pattern - ccline.cmdbuff);
+ while (--j > i) {
+ j -= utf_head_off(ccline.cmdbuff, ccline.cmdbuff + j);
+ if (vim_ispathsep(ccline.cmdbuff[j])
#ifdef BACKSLASH_IN_FILENAME
- && vim_strchr((const char_u *)" *?[{`$%#", ccline.cmdbuff[s->j + 1])
+ && vim_strchr((const char_u *)" *?[{`$%#", ccline.cmdbuff[j + 1])
== NULL
#endif
) {
if (found) {
- s->i = s->j + 1;
+ i = j + 1;
break;
} else {
found = true;
@@ -774,28 +776,28 @@ static int command_line_execute(VimState *state, int key)
}
if (!found) {
- s->j = s->i;
- } else if (STRNCMP(ccline.cmdbuff + s->j, upseg, 4) == 0) {
- s->j += 4;
- } else if (STRNCMP(ccline.cmdbuff + s->j, upseg + 1, 3) == 0
- && s->j == s->i) {
- s->j += 3;
+ j = i;
+ } else if (STRNCMP(ccline.cmdbuff + j, upseg, 4) == 0) {
+ j += 4;
+ } else if (STRNCMP(ccline.cmdbuff + j, upseg + 1, 3) == 0
+ && j == i) {
+ j += 3;
} else {
- s->j = 0;
+ j = 0;
}
- if (s->j > 0) {
+ if (j > 0) {
// TODO(tarruda): this is only for DOS/Unix systems - need to put in
// machine-specific stuff here and in upseg init
- cmdline_del(s->j);
+ cmdline_del(j);
put_on_cmdline(upseg + 1, 3, false);
- } else if (ccline.cmdpos > s->i) {
- cmdline_del(s->i);
+ } else if (ccline.cmdpos > i) {
+ cmdline_del(i);
}
// Now complete in the new directory. Set KeyTyped in case the
// Up key came from a mapping.
- s->c = p_wc;
+ s->c = (int)p_wc;
KeyTyped = true;
}
}
@@ -943,7 +945,7 @@ static int command_line_execute(VimState *state, int key)
}
} else { // typed p_wc first time
s->wim_index = 0;
- s->j = ccline.cmdpos;
+ int j = ccline.cmdpos;
// if 'wildmode' first contains "longest", get longest
// common part
@@ -970,7 +972,7 @@ static int command_line_execute(VimState *state, int key)
if (s->res == OK && s->xpc.xp_numfiles > 1) {
// a "longest" that didn't do anything is skipped (but not
// "list:longest")
- if (wim_flags[0] == WIM_LONGEST && ccline.cmdpos == s->j) {
+ if (wim_flags[0] == WIM_LONGEST && ccline.cmdpos == j) {
s->wim_index = 1;
}
if ((wim_flags[s->wim_index] & WIM_LIST)
@@ -1069,13 +1071,13 @@ static void command_line_next_incsearch(CommandLineState *s, bool next_match)
search_flags += SEARCH_KEEP;
}
emsg_off++;
- s->i = searchit(curwin, curbuf, &t, NULL,
- next_match ? FORWARD : BACKWARD,
- pat, s->count, search_flags,
- RE_SEARCH, 0, NULL, NULL);
+ int found = searchit(curwin, curbuf, &t, NULL,
+ next_match ? FORWARD : BACKWARD,
+ pat, s->count, search_flags,
+ RE_SEARCH, 0, NULL, NULL);
emsg_off--;
ui_busy_stop();
- if (s->i) {
+ if (found) {
s->search_start = s->match_start;
s->match_end = t;
s->match_start = t;
@@ -1124,7 +1126,7 @@ static void command_line_next_incsearch(CommandLineState *s, bool next_match)
static void command_line_next_histidx(CommandLineState *s, bool next_match)
{
- s->j = (int)STRLEN(s->lookfor);
+ int j = (int)STRLEN(s->lookfor);
for (;; ) {
// one step backwards
if (!next_match) {
@@ -1137,7 +1139,7 @@ static void command_line_next_histidx(CommandLineState *s, bool next_match)
s->hiscnt--;
} else {
// at top of list
- s->hiscnt = s->i;
+ s->hiscnt = s->save_hiscnt;
break;
}
} else { // one step forwards
@@ -1161,14 +1163,14 @@ static void command_line_next_histidx(CommandLineState *s, bool next_match)
}
if (s->hiscnt < 0 || history[s->histype][s->hiscnt].hisstr == NULL) {
- s->hiscnt = s->i;
+ s->hiscnt = s->save_hiscnt;
break;
}
if ((s->c != K_UP && s->c != K_DOWN)
- || s->hiscnt == s->i
+ || s->hiscnt == s->save_hiscnt
|| STRNCMP(history[s->histype][s->hiscnt].hisstr,
- s->lookfor, (size_t)s->j) == 0) {
+ s->lookfor, (size_t)j) == 0) {
break;
}
}
@@ -1193,7 +1195,7 @@ static int command_line_handle_key(CommandLineState *s)
++ccline.cmdpos;
}
- if (has_mbyte && s->c == K_DEL) {
+ if (s->c == K_DEL) {
ccline.cmdpos += mb_off_next(ccline.cmdbuff,
ccline.cmdbuff + ccline.cmdpos);
}
@@ -1201,43 +1203,30 @@ static int command_line_handle_key(CommandLineState *s)
if (ccline.cmdpos > 0) {
char_u *p;
- s->j = ccline.cmdpos;
- p = ccline.cmdbuff + s->j;
- if (has_mbyte) {
- p = mb_prevptr(ccline.cmdbuff, p);
-
- if (s->c == Ctrl_W) {
- while (p > ccline.cmdbuff && ascii_isspace(*p)) {
- p = mb_prevptr(ccline.cmdbuff, p);
- }
+ int j = ccline.cmdpos;
+ p = mb_prevptr(ccline.cmdbuff, ccline.cmdbuff + j);
- s->i = mb_get_class(p);
- while (p > ccline.cmdbuff && mb_get_class(p) == s->i)
- p = mb_prevptr(ccline.cmdbuff, p);
-
- if (mb_get_class(p) != s->i) {
- p += (*mb_ptr2len)(p);
- }
+ if (s->c == Ctrl_W) {
+ while (p > ccline.cmdbuff && ascii_isspace(*p)) {
+ p = mb_prevptr(ccline.cmdbuff, p);
}
- } else if (s->c == Ctrl_W) {
- while (p > ccline.cmdbuff && ascii_isspace(p[-1])) {
- --p;
+
+ int i = mb_get_class(p);
+ while (p > ccline.cmdbuff && mb_get_class(p) == i) {
+ p = mb_prevptr(ccline.cmdbuff, p);
}
- s->i = vim_iswordc(p[-1]);
- while (p > ccline.cmdbuff && !ascii_isspace(p[-1])
- && vim_iswordc(p[-1]) == s->i)
- --p;
- } else {
- --p;
+ if (mb_get_class(p) != i) {
+ p += utfc_ptr2len(p);
+ }
}
ccline.cmdpos = (int)(p - ccline.cmdbuff);
- ccline.cmdlen -= s->j - ccline.cmdpos;
- s->i = ccline.cmdpos;
+ ccline.cmdlen -= j - ccline.cmdpos;
+ int i = ccline.cmdpos;
- while (s->i < ccline.cmdlen) {
- ccline.cmdbuff[s->i++] = ccline.cmdbuff[s->j++];
+ while (i < ccline.cmdlen) {
+ ccline.cmdbuff[i++] = ccline.cmdbuff[j++];
}
// Truncate at the end, required for multi-byte chars.
@@ -1307,13 +1296,13 @@ static int command_line_handle_key(CommandLineState *s)
status_redraw_curbuf();
return command_line_not_changed(s);
- case Ctrl_U:
+ case Ctrl_U: {
// delete all characters left of the cursor
- s->j = ccline.cmdpos;
- ccline.cmdlen -= s->j;
- s->i = ccline.cmdpos = 0;
- while (s->i < ccline.cmdlen) {
- ccline.cmdbuff[s->i++] = ccline.cmdbuff[s->j++];
+ int j = ccline.cmdpos;
+ ccline.cmdlen -= j;
+ int i = ccline.cmdpos = 0;
+ while (i < ccline.cmdlen) {
+ ccline.cmdbuff[i++] = ccline.cmdbuff[j++];
}
// Truncate at the end, required for multi-byte chars.
@@ -1323,6 +1312,7 @@ static int command_line_handle_key(CommandLineState *s)
}
redrawcmd();
return command_line_changed(s);
+ }
case ESC: // get here if p_wc != ESC or when ESC typed twice
case Ctrl_C:
@@ -1339,15 +1329,15 @@ static int command_line_handle_key(CommandLineState *s)
// putting it in history
return 0; // back to cmd mode
- case Ctrl_R: // insert register
+ case Ctrl_R: { // insert register
putcmdline('"', true);
- ++no_mapping;
- s->i = s->c = plain_vgetc(); // CTRL-R <char>
- if (s->i == Ctrl_O) {
- s->i = Ctrl_R; // CTRL-R CTRL-O == CTRL-R CTRL-R
+ no_mapping++;
+ int i = s->c = plain_vgetc(); // CTRL-R <char>
+ if (i == Ctrl_O) {
+ i = Ctrl_R; // CTRL-R CTRL-O == CTRL-R CTRL-R
}
- if (s->i == Ctrl_R) {
+ if (i == Ctrl_R) {
s->c = plain_vgetc(); // CTRL-R CTRL-R <char>
}
--no_mapping;
@@ -1369,7 +1359,7 @@ static int command_line_handle_key(CommandLineState *s)
}
if (s->c != ESC) { // use ESC to cancel inserting register
- cmdline_paste(s->c, s->i == Ctrl_R, false);
+ cmdline_paste(s->c, i == Ctrl_R, false);
// When there was a serious error abort getting the
// command line.
@@ -1391,6 +1381,7 @@ static int command_line_handle_key(CommandLineState *s)
ccline.special_char = NUL;
redrawcmd();
return command_line_changed(s);
+ }
case Ctrl_D:
if (showmatches(&s->xpc, false) == EXPAND_NOTHING) {
@@ -1409,24 +1400,17 @@ static int command_line_handle_key(CommandLineState *s)
break;
}
- s->i = cmdline_charsize(ccline.cmdpos);
- if (KeyTyped && ccline.cmdspos + s->i >= Columns * Rows) {
+ int cells = cmdline_charsize(ccline.cmdpos);
+ if (KeyTyped && ccline.cmdspos + cells >= Columns * Rows) {
break;
}
- ccline.cmdspos += s->i;
- if (has_mbyte) {
- ccline.cmdpos += (*mb_ptr2len)(ccline.cmdbuff
- + ccline.cmdpos);
- } else {
- ++ccline.cmdpos;
- }
+ ccline.cmdspos += cells;
+ ccline.cmdpos += utfc_ptr2len(ccline.cmdbuff + ccline.cmdpos);
} while ((s->c == K_S_RIGHT || s->c == K_C_RIGHT
|| (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL)))
&& ccline.cmdbuff[ccline.cmdpos] != ' ');
- if (has_mbyte) {
- set_cmdspos_cursor();
- }
+ ccline.cmdspos = cmd_screencol(ccline.cmdpos);
return command_line_not_changed(s);
case K_LEFT:
@@ -1446,7 +1430,7 @@ static int command_line_handle_key(CommandLineState *s)
|| (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL)))
&& ccline.cmdbuff[ccline.cmdpos - 1] != ' ');
- set_cmdspos_cursor();
+ ccline.cmdspos = cmd_screencol(ccline.cmdpos);
if (ccline.special_char != NUL) {
putcmdline(ccline.special_char, ccline.special_shift);
}
@@ -1493,22 +1477,19 @@ static int command_line_handle_key(CommandLineState *s)
return command_line_not_changed(s); // Ignore mouse
}
- set_cmdspos();
+ ccline.cmdspos = cmd_startcol();
for (ccline.cmdpos = 0; ccline.cmdpos < ccline.cmdlen;
- ++ccline.cmdpos) {
- s->i = cmdline_charsize(ccline.cmdpos);
+ ccline.cmdpos++) {
+ int cells = cmdline_charsize(ccline.cmdpos);
if (mouse_row <= cmdline_row + ccline.cmdspos / Columns
- && mouse_col < ccline.cmdspos % Columns + s->i) {
+ && mouse_col < ccline.cmdspos % Columns + cells) {
break;
}
- if (has_mbyte) {
- // Count ">" for double-wide char that doesn't fit.
- correct_cmdspos(ccline.cmdpos, s->i);
- ccline.cmdpos += (*mb_ptr2len)(ccline.cmdbuff
- + ccline.cmdpos) - 1;
- }
- ccline.cmdspos += s->i;
+ // Count ">" for double-wide char that doesn't fit.
+ correct_screencol(ccline.cmdpos, cells, &ccline.cmdspos);
+ ccline.cmdpos += utfc_ptr2len(ccline.cmdbuff + ccline.cmdpos) - 1;
+ ccline.cmdspos += cells;
}
return command_line_not_changed(s);
@@ -1537,7 +1518,7 @@ static int command_line_handle_key(CommandLineState *s)
case K_S_HOME:
case K_C_HOME:
ccline.cmdpos = 0;
- set_cmdspos();
+ ccline.cmdspos = cmd_startcol();
return command_line_not_changed(s);
case Ctrl_E: // end of command line
@@ -1546,7 +1527,7 @@ static int command_line_handle_key(CommandLineState *s)
case K_S_END:
case K_C_END:
ccline.cmdpos = ccline.cmdlen;
- set_cmdspos_cursor();
+ ccline.cmdspos = cmd_screencol(ccline.cmdpos);
return command_line_not_changed(s);
case Ctrl_A: // all matches
@@ -1613,7 +1594,7 @@ static int command_line_handle_key(CommandLineState *s)
return command_line_not_changed(s);
}
- s->i = s->hiscnt;
+ s->save_hiscnt = s->hiscnt;
// save current command string so it can be restored later
if (s->lookfor == NULL) {
@@ -1625,7 +1606,7 @@ static int command_line_handle_key(CommandLineState *s)
|| s->c == K_PAGEDOWN || s->c == K_KPAGEDOWN);
command_line_next_histidx(s, next_match);
- if (s->hiscnt != s->i) {
+ if (s->hiscnt != s->save_hiscnt) {
// jumped to other entry
char_u *p;
int len = 0;
@@ -1646,35 +1627,35 @@ static int command_line_handle_key(CommandLineState *s)
// adding the history entry vs the one used now.
// First loop: count length.
// Second loop: copy the characters.
- for (s->i = 0; s->i <= 1; ++s->i) {
+ for (int i = 0; i <= 1; i++) {
len = 0;
- for (s->j = 0; p[s->j] != NUL; ++s->j) {
+ for (int j = 0; p[j] != NUL; j++) {
// Replace old sep with new sep, unless it is
// escaped.
- if (p[s->j] == old_firstc
- && (s->j == 0 || p[s->j - 1] != '\\')) {
- if (s->i > 0) {
- ccline.cmdbuff[len] = s->firstc;
+ if (p[j] == old_firstc
+ && (j == 0 || p[j - 1] != '\\')) {
+ if (i > 0) {
+ ccline.cmdbuff[len] = (char_u)s->firstc;
}
} else {
// Escape new sep, unless it is already
// escaped.
- if (p[s->j] == s->firstc
- && (s->j == 0 || p[s->j - 1] != '\\')) {
- if (s->i > 0) {
+ if (p[j] == s->firstc
+ && (j == 0 || p[j - 1] != '\\')) {
+ if (i > 0) {
ccline.cmdbuff[len] = '\\';
}
++len;
}
- if (s->i > 0) {
- ccline.cmdbuff[len] = p[s->j];
+ if (i > 0) {
+ ccline.cmdbuff[len] = p[j];
}
}
++len;
}
- if (s->i == 0) {
+ if (i == 0) {
alloc_cmdbuff(len);
}
}
@@ -1766,9 +1747,9 @@ static int command_line_handle_key(CommandLineState *s)
if (IS_SPECIAL(s->c) || mod_mask != 0) {
put_on_cmdline(get_special_key_name(s->c, mod_mask), -1, true);
} else {
- s->j = utf_char2bytes(s->c, IObuff);
- IObuff[s->j] = NUL; // exclude composing chars
- put_on_cmdline(IObuff, s->j, true);
+ int j = utf_char2bytes(s->c, IObuff);
+ IObuff[j] = NUL; // exclude composing chars
+ put_on_cmdline(IObuff, j, true);
}
return command_line_changed(s);
}
@@ -1810,7 +1791,7 @@ static int command_line_changed(CommandLineState *s)
dict_T *dict = get_vim_var_dict(VV_EVENT);
char firstcbuf[2];
- firstcbuf[0] = s->firstc > 0 ? s->firstc : '-';
+ firstcbuf[0] = (char)(s->firstc > 0 ? s->firstc : '-');
firstcbuf[1] = 0;
// set v:event to a dictionary with information about the commandline
@@ -1845,10 +1826,11 @@ static int command_line_changed(CommandLineState *s)
s->incsearch_postponed = false;
curwin->w_cursor = s->search_start; // start at old position
save_last_search_pattern();
+ int i;
// If there is no command line, don't do anything
if (ccline.cmdlen == 0) {
- s->i = 0;
+ i = 0;
SET_NO_HLSEARCH(true); // turn off previous highlight
redraw_all_later(SOME_VALID);
} else {
@@ -1861,15 +1843,14 @@ static int command_line_changed(CommandLineState *s)
if (!p_hls) {
search_flags += SEARCH_KEEP;
}
- s->i = do_search(NULL, s->firstc, ccline.cmdbuff, s->count,
- search_flags,
- &tm, NULL);
+ i = do_search(NULL, s->firstc, ccline.cmdbuff, s->count,
+ search_flags, &tm, NULL);
emsg_off--;
// if interrupted while searching, behave like it failed
if (got_int) {
(void)vpeekc(); // remove <C-C> from input stream
got_int = false; // don't abandon the command line
- s->i = 0;
+ i = 0;
} else if (char_avail()) {
// cancelled searching because a char was typed
s->incsearch_postponed = true;
@@ -1877,7 +1858,7 @@ static int command_line_changed(CommandLineState *s)
ui_busy_stop();
}
- if (s->i != 0) {
+ if (i != 0) {
highlight_match = true; // highlight position
} else {
highlight_match = false; // remove highlight
@@ -1892,7 +1873,7 @@ static int command_line_changed(CommandLineState *s)
changed_cline_bef_curs();
update_topline();
- if (s->i != 0) {
+ if (i != 0) {
pos_T save_pos = curwin->w_cursor;
s->match_start = curwin->w_cursor;
@@ -1921,7 +1902,7 @@ static int command_line_changed(CommandLineState *s)
restore_last_search_pattern();
// Leave it at the end to make CTRL-R CTRL-W work.
- if (s->i != 0) {
+ if (i != 0) {
curwin->w_cursor = end_pos;
}
@@ -1978,6 +1959,7 @@ static int command_line_changed(CommandLineState *s)
static void abandon_cmdline(void)
{
XFREE_CLEAR(ccline.cmdbuff);
+ ccline.redraw_state = kCmdRedrawNone;
if (msg_scrolled == 0) {
compute_cmdrow();
}
@@ -2131,58 +2113,52 @@ static int cmdline_charsize(int idx)
return ptr2cells(ccline.cmdbuff + idx);
}
-/*
- * Compute the offset of the cursor on the command line for the prompt and
- * indent.
- */
-static void set_cmdspos(void)
+/// Compute the offset of the cursor on the command line for the prompt and
+/// indent.
+static int cmd_startcol(void)
{
- if (ccline.cmdfirstc != NUL)
- ccline.cmdspos = 1 + ccline.cmdindent;
- else
- ccline.cmdspos = 0 + ccline.cmdindent;
+ return ccline.cmdindent + ((ccline.cmdfirstc != NUL) ? 1 : 0);
}
-/*
- * Compute the screen position for the cursor on the command line.
- */
-static void set_cmdspos_cursor(void)
+
+/// Compute the column position for a byte position on the command line.
+static int cmd_screencol(int bytepos)
{
- int i, m, c;
+ int m; // maximum column
- set_cmdspos();
+ int col = cmd_startcol();
if (KeyTyped) {
m = Columns * Rows;
if (m < 0) /* overflow, Columns or Rows at weird value */
m = MAXCOL;
- } else
+ } else {
m = MAXCOL;
- for (i = 0; i < ccline.cmdlen && i < ccline.cmdpos; ++i) {
- c = cmdline_charsize(i);
- /* Count ">" for double-wide multi-byte char that doesn't fit. */
- if (has_mbyte)
- correct_cmdspos(i, c);
- /* If the cmdline doesn't fit, show cursor on last visible char.
- * Don't move the cursor itself, so we can still append. */
- if ((ccline.cmdspos += c) >= m) {
- ccline.cmdspos -= c;
+ }
+
+ for (int i = 0; i < ccline.cmdlen && i < bytepos;
+ i += utfc_ptr2len(ccline.cmdbuff + i)) {
+ int c = cmdline_charsize(i);
+ // Count ">" for double-wide multi-byte char that doesn't fit.
+ correct_screencol(i, c, &col);
+
+ // If the cmdline doesn't fit, show cursor on last visible char.
+ // Don't move the cursor itself, so we can still append.
+ if ((col += c) >= m) {
+ col -= c;
break;
}
- if (has_mbyte)
- i += (*mb_ptr2len)(ccline.cmdbuff + i) - 1;
}
+ return col;
}
-/*
- * Check if the character at "idx", which is "cells" wide, is a multi-byte
- * character that doesn't fit, so that a ">" must be displayed.
- */
-static void correct_cmdspos(int idx, int cells)
+/// Check if the character at "idx", which is "cells" wide, is a multi-byte
+/// character that doesn't fit, so that a ">" must be displayed.
+static void correct_screencol(int idx, int cells, int *col)
{
if (utfc_ptr2len(ccline.cmdbuff + idx) > 1
&& utf_ptr2cells(ccline.cmdbuff + idx) > 1
- && ccline.cmdspos % Columns + cells > Columns) {
- ccline.cmdspos++;
+ && (*col) % Columns + cells > Columns) {
+ (*col)++;
}
}
@@ -2323,8 +2299,10 @@ add_indent:
char_u *s = skipwhite(p);
// Insert spaces after leading whitespaces.
- memmove(s + num_spaces, s, line_ga.ga_len - (s - p) + 1);
- memset(s, ' ', num_spaces);
+ long move_len = line_ga.ga_len - (s - p) + 1;
+ assert(move_len >= 0);
+ memmove(s + num_spaces, s, (size_t)move_len);
+ memset(s, ' ', (size_t)num_spaces);
line_ga.ga_len += num_spaces;
}
@@ -2377,8 +2355,10 @@ redraw:
while ((old_indent = get_indent_str(p, 8, FALSE)) > indent) {
*--to = NUL;
}
- memmove(to, from, line_ga.ga_len - (from - p) + 1);
- line_ga.ga_len -= from - to;
+ long move_len = line_ga.ga_len - (from - p) + 1;
+ assert(move_len > 0);
+ memmove(to, from, (size_t)move_len);
+ line_ga.ga_len -= (int)(from - to);
// Removed to much indentation, fix it before redrawing.
num_spaces = indent - old_indent;
@@ -2484,7 +2464,7 @@ static void alloc_cmdbuff(int len)
else
len += 20;
- ccline.cmdbuff = xmalloc(len);
+ ccline.cmdbuff = xmalloc((size_t)len);
ccline.cmdbufflen = len;
}
@@ -2521,7 +2501,7 @@ static void realloc_cmdbuff(int len)
static char_u *arshape_buf = NULL;
# if defined(EXITFREE)
-void free_cmdline_buf(void)
+void free_arshape_buf(void)
{
xfree(arshape_buf);
}
@@ -2561,26 +2541,28 @@ static void color_expr_cmdline(const CmdlineInfo *const colored_ccline,
size_t prev_end = 0;
for (size_t i = 0 ; i < kv_size(colors) ; i++) {
const ParserHighlightChunk chunk = kv_A(colors, i);
+ assert(chunk.start.col < INT_MAX);
+ assert(chunk.end_col < INT_MAX);
if (chunk.start.col != prev_end) {
kv_push(ret_ccline_colors->colors, ((CmdlineColorChunk) {
- .start = prev_end,
- .end = chunk.start.col,
+ .start = (int)prev_end,
+ .end = (int)chunk.start.col,
.attr = 0,
}));
}
const int id = syn_name2id((const char_u *)chunk.group);
const int attr = (id == 0 ? 0 : syn_id2attr(id));
kv_push(ret_ccline_colors->colors, ((CmdlineColorChunk) {
- .start = chunk.start.col,
- .end = chunk.end_col,
+ .start = (int)chunk.start.col,
+ .end = (int)chunk.end_col,
.attr = attr,
}));
prev_end = chunk.end_col;
}
if (prev_end < (size_t)colored_ccline->cmdlen) {
kv_push(ret_ccline_colors->colors, ((CmdlineColorChunk) {
- .start = prev_end,
- .end = (size_t)colored_ccline->cmdlen,
+ .start = (int)prev_end,
+ .end = colored_ccline->cmdlen,
.attr = 0,
}));
}
@@ -2745,8 +2727,8 @@ static bool color_cmdline(CmdlineInfo *colored_ccline)
}
if (start != prev_end) {
kv_push(ccline_colors->colors, ((CmdlineColorChunk) {
- .start = prev_end,
- .end = start,
+ .start = (int)prev_end,
+ .end = (int)start,
.attr = 0,
}));
}
@@ -2775,15 +2757,15 @@ static bool color_cmdline(CmdlineInfo *colored_ccline)
const int id = syn_name2id((char_u *)group);
const int attr = (id == 0 ? 0 : syn_id2attr(id));
kv_push(ccline_colors->colors, ((CmdlineColorChunk) {
- .start = start,
- .end = end,
+ .start = (int)start,
+ .end = (int)end,
.attr = attr,
}));
i++;
});
if (prev_end < colored_ccline->cmdlen) {
kv_push(ccline_colors->colors, ((CmdlineColorChunk) {
- .start = prev_end,
+ .start = (int)prev_end,
.end = colored_ccline->cmdlen,
.attr = 0,
}));
@@ -2861,15 +2843,16 @@ static void draw_cmdline(int start, int len)
goto draw_cmdline_no_arabicshape;
}
- static int buflen = 0;
+ static size_t buflen = 0;
+ assert(len >= 0);
// Do arabic shaping into a temporary buffer. This is very
// inefficient!
- if (len * 2 + 2 > buflen) {
+ if ((size_t)len * 2 + 2 > buflen) {
// Re-allocate the buffer. We keep it around to avoid a lot of
// alloc()/free() calls.
xfree(arshape_buf);
- buflen = len * 2 + 2;
+ buflen = (size_t)len * 2 + 2;
arshape_buf = xmalloc(buflen);
}
@@ -2927,7 +2910,7 @@ static void draw_cmdline(int start, int len)
}
} else {
prev_c = u8c;
- memmove(arshape_buf + newlen, p, mb_l);
+ memmove(arshape_buf + newlen, p, (size_t)mb_l);
newlen += mb_l;
}
}
@@ -2972,8 +2955,9 @@ static void ui_ext_cmdline_show(CmdlineInfo *line)
Array item = ARRAY_DICT_INIT;
ADD(item, INTEGER_OBJ(chunk.attr));
+ assert(chunk.end >= chunk.start);
ADD(item, STRING_OBJ(cbuf_to_string((char *)line->cmdbuff + chunk.start,
- chunk.end-chunk.start)));
+ (size_t)(chunk.end-chunk.start))));
ADD(content, ARRAY_OBJ(item));
}
} else {
@@ -2994,7 +2978,7 @@ static void ui_ext_cmdline_show(CmdlineInfo *line)
}
}
-void ui_ext_cmdline_block_append(int indent, const char *line)
+void ui_ext_cmdline_block_append(size_t indent, const char *line)
{
char *buf = xmallocz(indent + strlen(line));
memset(buf, ' ', indent);
@@ -3073,7 +3057,7 @@ void cmdline_ui_flush(void)
* right when "shift" is TRUE. Used for CTRL-V, CTRL-K, etc.
* "c" must be printable (fit in one display cell)!
*/
-void putcmdline(int c, int shift)
+void putcmdline(char c, int shift)
{
if (cmd_silent) {
return;
@@ -3215,7 +3199,7 @@ void put_on_cmdline(char_u *str, int len, int redraw)
c = cmdline_charsize(ccline.cmdpos);
// count ">" for a double-wide char that doesn't fit.
if (has_mbyte) {
- correct_cmdspos(ccline.cmdpos, c);
+ correct_screencol(ccline.cmdpos, c, &ccline.cmdspos);
}
// Stop cursor at the end of the screen, but do increment the
// insert position, so that entering a very long command
@@ -3392,8 +3376,9 @@ void cmdline_paste_str(char_u *s, int literally)
/// Delete characters on the command line, from "from" to the current position.
static void cmdline_del(int from)
{
+ assert(ccline.cmdpos <= ccline.cmdlen);
memmove(ccline.cmdbuff + from, ccline.cmdbuff + ccline.cmdpos,
- (size_t)ccline.cmdlen - ccline.cmdpos + 1);
+ (size_t)ccline.cmdlen - (size_t)ccline.cmdpos + 1);
ccline.cmdlen -= ccline.cmdpos - from;
ccline.cmdpos = from;
}
@@ -3428,7 +3413,7 @@ static void redrawcmdprompt(void)
if (ccline.cmdprompt != NULL) {
msg_puts_attr((const char *)ccline.cmdprompt, ccline.cmdattr);
ccline.cmdindent = msg_col + (msg_row - cmdline_row) * Columns;
- // do the reverse of set_cmdspos()
+ // do the reverse of cmd_startcol()
if (ccline.cmdfirstc != NUL) {
ccline.cmdindent--;
}
@@ -3470,7 +3455,7 @@ void redrawcmd(void)
msg_clr_eos();
msg_no_more = FALSE;
- set_cmdspos_cursor();
+ ccline.cmdspos = cmd_screencol(ccline.cmdpos);
if (ccline.special_char != NUL) {
putcmdline(ccline.special_char, ccline.special_shift);
@@ -3514,15 +3499,17 @@ static void cursorcmd(void)
}
if (cmdmsg_rl) {
- msg_row = cmdline_row + (ccline.cmdspos / (int)(Columns - 1));
- msg_col = (int)Columns - (ccline.cmdspos % (int)(Columns - 1)) - 1;
- if (msg_row <= 0)
+ msg_row = cmdline_row + (ccline.cmdspos / (Columns - 1));
+ msg_col = Columns - (ccline.cmdspos % (Columns - 1)) - 1;
+ if (msg_row <= 0) {
msg_row = Rows - 1;
+ }
} else {
- msg_row = cmdline_row + (ccline.cmdspos / (int)Columns);
- msg_col = ccline.cmdspos % (int)Columns;
- if (msg_row >= Rows)
+ msg_row = cmdline_row + (ccline.cmdspos / Columns);
+ msg_col = ccline.cmdspos % Columns;
+ if (msg_row >= Rows) {
msg_row = Rows - 1;
+ }
}
ui_cursor_goto(msg_row, msg_col);
@@ -3624,7 +3611,8 @@ nextwild (
}
i = (int)(xp->xp_pattern - ccline.cmdbuff);
- xp->xp_pattern_len = ccline.cmdpos - i;
+ assert(ccline.cmdpos >= i);
+ xp->xp_pattern_len = (size_t)ccline.cmdpos - (size_t)i;
if (type == WILD_NEXT || type == WILD_PREV) {
// Get next/previous match for a previous expanded pattern.
@@ -3644,7 +3632,7 @@ nextwild (
xfree(p1);
// Longest match: make sure it is not shorter, happens with :help.
if (p2 != NULL && type == WILD_LONGEST) {
- for (j = 0; j < xp->xp_pattern_len; j++) {
+ for (j = 0; (size_t)j < xp->xp_pattern_len; j++) {
if (ccline.cmdbuff[i + j] == '*'
|| ccline.cmdbuff[i + j] == '?') {
break;
@@ -3657,14 +3645,15 @@ nextwild (
}
if (p2 != NULL && !got_int) {
- difflen = (int)STRLEN(p2) - xp->xp_pattern_len;
+ difflen = (int)STRLEN(p2) - (int)xp->xp_pattern_len;
if (ccline.cmdlen + difflen + 4 > ccline.cmdbufflen) {
realloc_cmdbuff(ccline.cmdlen + difflen + 4);
xp->xp_pattern = ccline.cmdbuff + i;
}
+ assert(ccline.cmdpos <= ccline.cmdlen);
memmove(&ccline.cmdbuff[ccline.cmdpos + difflen],
&ccline.cmdbuff[ccline.cmdpos],
- (size_t)ccline.cmdlen - ccline.cmdpos + 1);
+ (size_t)ccline.cmdlen - (size_t)ccline.cmdpos + 1);
memmove(&ccline.cmdbuff[i], p2, STRLEN(p2));
ccline.cmdlen += difflen;
ccline.cmdpos += difflen;
@@ -3861,7 +3850,7 @@ ExpandOne (
size_t len = 0;
for (size_t mb_len; xp->xp_files[0][len]; len += mb_len) {
- mb_len = utfc_ptr2len(&xp->xp_files[0][len]);
+ mb_len = (size_t)utfc_ptr2len(&xp->xp_files[0][len]);
int c0 = utf_ptr2char(&xp->xp_files[0][len]);
for (i = 1; i < xp->xp_numfiles; i++) {
int ci = utf_ptr2char(&xp->xp_files[i][len]);
@@ -4100,7 +4089,8 @@ void cmdline_pum_display(bool changed_array)
*/
static int showmatches(expand_T *xp, int wildmenu)
{
-#define L_SHOWFILE(m) (showtail ? sm_gettail(files_found[m]) : files_found[m])
+#define L_SHOWFILE(m) (showtail \
+ ? sm_gettail(files_found[m], false) : files_found[m])
int num_files;
char_u **files_found;
int i, j, k;
@@ -4132,19 +4122,19 @@ static int showmatches(expand_T *xp, int wildmenu)
|| ui_has(kUIWildmenu);
if (compl_use_pum) {
+ assert(num_files >= 0);
compl_match_arraysize = num_files;
- compl_match_array = xcalloc(compl_match_arraysize, sizeof(pumitem_T));
+ compl_match_array = xcalloc((size_t)compl_match_arraysize,
+ sizeof(pumitem_T));
for (i = 0; i < num_files; i++) {
compl_match_array[i].pum_text = L_SHOWFILE(i);
}
- ssize_t offset = showtail ? sm_gettail(xp->xp_pattern)-xp->xp_pattern : 0;
+ char_u *endpos = (showtail
+ ? sm_gettail(xp->xp_pattern, true) : xp->xp_pattern);
if (ui_has(kUICmdline)) {
- compl_startcol = ccline.cmdpos - strnlen((char *)xp->xp_pattern+offset,
- xp->xp_pattern_len-offset);
+ compl_startcol = (int)(endpos - ccline.cmdbuff);
} else {
- compl_startcol = ccline.cmdspos
- - mb_string2cells_len(xp->xp_pattern+offset,
- xp->xp_pattern_len-offset);
+ compl_startcol = cmd_screencol((int)(endpos - ccline.cmdbuff));
}
compl_selected = -1;
cmdline_pum_display(true);
@@ -4180,14 +4170,15 @@ static int showmatches(expand_T *xp, int wildmenu)
maxlen = j;
}
- if (xp->xp_context == EXPAND_TAGS_LISTFILES)
+ if (xp->xp_context == EXPAND_TAGS_LISTFILES) {
lines = num_files;
- else {
- /* compute the number of columns and lines for the listing */
- maxlen += 2; /* two spaces between file names */
- columns = ((int)Columns + 2) / maxlen;
- if (columns < 1)
+ } else {
+ // compute the number of columns and lines for the listing
+ maxlen += 2; // two spaces between file names
+ columns = (Columns + 2) / maxlen;
+ if (columns < 1) {
columns = 1;
+ }
lines = (num_files + columns - 1) / columns;
}
@@ -4276,7 +4267,7 @@ static int showmatches(expand_T *xp, int wildmenu)
* Private path_tail for showmatches() (and win_redr_status_matches()):
* Find tail of file name path, but ignore trailing "/".
*/
-char_u *sm_gettail(char_u *s)
+char_u *sm_gettail(char_u *s, bool eager)
{
char_u *p;
char_u *t = s;
@@ -4287,9 +4278,13 @@ char_u *sm_gettail(char_u *s)
#ifdef BACKSLASH_IN_FILENAME
&& !rem_backslash(p)
#endif
- )
- had_sep = TRUE;
- else if (had_sep) {
+ ) {
+ if (eager) {
+ t = p+1;
+ } else {
+ had_sep = true;
+ }
+ } else if (had_sep) {
t = p;
had_sep = FALSE;
}
@@ -4329,24 +4324,20 @@ static int expand_showtail(expand_T *xp)
return TRUE;
}
-/*
- * Prepare a string for expansion.
- * When expanding file names: The string will be used with expand_wildcards().
- * Copy "fname[len]" into allocated memory and add a '*' at the end.
- * When expanding other names: The string will be used with regcomp(). Copy
- * the name into allocated memory and prepend "^".
- */
-char_u *
-addstar (
- char_u *fname,
- int len,
- int context /* EXPAND_FILES etc. */
-)
+/// Prepare a string for expansion.
+///
+/// When expanding file names: The string will be used with expand_wildcards().
+/// Copy "fname[len]" into allocated memory and add a '*' at the end.
+/// When expanding other names: The string will be used with regcomp(). Copy
+/// the name into allocated memory and prepend "^".
+///
+/// @param context EXPAND_FILES etc.
+char_u *addstar(char_u *fname, size_t len, int context)
FUNC_ATTR_NONNULL_RET
{
char_u *retval;
- int i, j;
- int new_len;
+ size_t i, j;
+ size_t new_len;
char_u *tail;
int ends_in_star;
@@ -4437,9 +4428,10 @@ addstar (
tail = path_tail(retval);
ends_in_star = (len > 0 && retval[len - 1] == '*');
#ifndef BACKSLASH_IN_FILENAME
- for (i = len - 2; i >= 0; --i) {
- if (retval[i] != '\\')
+ for (ssize_t k = (ssize_t)len - 2; k >= 0; k--) {
+ if (retval[k] != '\\') {
break;
+ }
ends_in_star = !ends_in_star;
}
#endif
@@ -4520,7 +4512,7 @@ set_cmd_context (
int use_ccline // use ccline for info
)
{
- int old_char = NUL;
+ char_u old_char = NUL;
/*
* Avoid a UMR warning from Purify, only save the character if it has been
@@ -4584,8 +4576,9 @@ expand_cmdline (
return EXPAND_NOTHING;
}
- /* add star to file name, or convert to regexp if not exp. files. */
- xp->xp_pattern_len = (int)(str + col - xp->xp_pattern);
+ // add star to file name, or convert to regexp if not exp. files.
+ assert((str + col) - xp->xp_pattern >= 0);
+ xp->xp_pattern_len = (size_t)((str + col) - xp->xp_pattern);
file_str = addstar(xp->xp_pattern, xp->xp_pattern_len, xp->xp_context);
if (p_wic)
@@ -4875,7 +4868,7 @@ void ExpandGeneric(
)
{
int i;
- int count = 0;
+ size_t count = 0;
char_u *str;
// count the number of matching names
@@ -4891,7 +4884,8 @@ void ExpandGeneric(
}
if (count == 0)
return;
- *num_file = count;
+ assert(count < INT_MAX);
+ *num_file = (int)count;
*file = (char_u **)xmalloc(count * sizeof(char_u *));
// copy the matching names into allocated memory
@@ -4952,7 +4946,7 @@ static void expand_shellcmd(char_u *filepat, int *num_file, char_u ***file,
{
char_u *pat;
int i;
- char_u *path;
+ char_u *path = NULL;
garray_T ga;
char_u *buf = xmalloc(MAXPATHL);
size_t l;
@@ -4971,15 +4965,14 @@ static void expand_shellcmd(char_u *filepat, int *num_file, char_u ***file,
flags |= EW_FILE | EW_EXEC | EW_SHELLCMD;
bool mustfree = false; // Track memory allocation for *path.
- // For an absolute name we don't use $PATH.
- if (path_is_absolute(pat)) {
- path = (char_u *)" ";
- } else if (pat[0] == '.' && (vim_ispathsep(pat[1])
- || (pat[1] == '.'
- && vim_ispathsep(pat[2])))) {
+ if (pat[0] == '.' && (vim_ispathsep(pat[1])
+ || (pat[1] == '.' && vim_ispathsep(pat[2])))) {
path = (char_u *)".";
} else {
- path = (char_u *)vim_getenv("PATH");
+ // For an absolute name we don't use $PATH.
+ if (!path_is_absolute(pat)) {
+ path = (char_u *)vim_getenv("PATH");
+ }
if (path == NULL) {
path = (char_u *)"";
} else {
@@ -4993,6 +4986,8 @@ static void expand_shellcmd(char_u *filepat, int *num_file, char_u ***file,
* current directory, to find "subdir/cmd".
*/
ga_init(&ga, (int)sizeof(char *), 10);
+ hashtab_T found_ht;
+ hash_init(&found_ht);
for (s = path; ; s = e) {
if (*s == NUL) {
if (did_curdir) {
@@ -5004,17 +4999,15 @@ static void expand_shellcmd(char_u *filepat, int *num_file, char_u ***file,
did_curdir = true;
}
- if (*s == ' ') {
- s++; // Skip space used for absolute path name.
- }
-
- e = vim_strchr(s, ':');
- if (e == NULL)
+ e = vim_strchr(s, ENV_SEPCHAR);
+ if (e == NULL) {
e = s + STRLEN(s);
+ }
- l = e - s;
- if (l > MAXPATHL - 5)
+ l = (size_t)(e - s);
+ if (l > MAXPATHL - 5) {
break;
+ }
STRLCPY(buf, s, l + 1);
add_pathsep((char *)buf);
l = STRLEN(buf);
@@ -5025,14 +5018,24 @@ static void expand_shellcmd(char_u *filepat, int *num_file, char_u ***file,
if (ret == OK) {
ga_grow(&ga, *num_file);
{
- for (i = 0; i < *num_file; ++i) {
- s = (*file)[i];
- if (STRLEN(s) > l) {
- /* Remove the path again. */
- STRMOVE(s, s + l);
- ((char_u **)ga.ga_data)[ga.ga_len++] = s;
- } else
- xfree(s);
+ for (i = 0; i < *num_file; i++) {
+ char_u *name = (*file)[i];
+
+ if (STRLEN(name) > l) {
+ // Check if this name was already found.
+ hash_T hash = hash_hash(name + l);
+ hashitem_T *hi =
+ hash_lookup(&found_ht, (const char *)(name + l),
+ STRLEN(name + l), hash);
+ if (HASHITEM_EMPTY(hi)) {
+ // Remove the path that was prepended.
+ STRMOVE(name, name + l);
+ ((char_u **)ga.ga_data)[ga.ga_len++] = name;
+ hash_add_item(&found_ht, hi, name, hash);
+ name = NULL;
+ }
+ }
+ xfree(name);
}
xfree(*file);
}
@@ -5048,6 +5051,7 @@ static void expand_shellcmd(char_u *filepat, int *num_file, char_u ***file,
if (mustfree) {
xfree(path);
}
+ hash_clear(&found_ht);
}
/// Call "user_expand_func()" to invoke a user defined Vim script function and
@@ -5055,9 +5059,9 @@ static void expand_shellcmd(char_u *filepat, int *num_file, char_u ***file,
static void * call_user_expand_func(user_expand_func_T user_expand_func,
expand_T *xp, int *num_file, char_u ***file)
{
- int keep = 0;
- char_u num[50];
- char_u *args[3];
+ char_u keep = 0;
+ typval_T args[4];
+ char_u *pat = NULL;
int save_current_SID = current_SID;
void *ret;
struct cmdline_info save_ccline;
@@ -5072,10 +5076,14 @@ static void * call_user_expand_func(user_expand_func_T user_expand_func,
ccline.cmdbuff[ccline.cmdlen] = 0;
}
- args[0] = vim_strnsave(xp->xp_pattern, xp->xp_pattern_len);
- args[1] = xp->xp_line;
- sprintf((char *)num, "%d", xp->xp_col);
- args[2] = num;
+ pat = vim_strnsave(xp->xp_pattern, xp->xp_pattern_len);
+ args[0].v_type = VAR_STRING;
+ args[1].v_type = VAR_STRING;
+ args[2].v_type = VAR_NUMBER;
+ args[3].v_type = VAR_UNKNOWN;
+ args[0].vval.v_string = pat;
+ args[1].vval.v_string = xp->xp_line;
+ args[2].vval.v_number = xp->xp_col;
/* Save the cmdline, we don't know what the function may do. */
save_ccline = ccline;
@@ -5085,7 +5093,7 @@ static void * call_user_expand_func(user_expand_func_T user_expand_func,
ret = user_expand_func(xp->xp_arg,
3,
- (const char_u * const *)args,
+ args,
false);
ccline = save_ccline;
@@ -5093,7 +5101,7 @@ static void * call_user_expand_func(user_expand_func_T user_expand_func,
if (ccline.cmdbuff != NULL)
ccline.cmdbuff[ccline.cmdlen] = keep;
- xfree(args[0]);
+ xfree(pat);
return ret;
}
@@ -5117,14 +5125,14 @@ static int ExpandUserDefined(expand_T *xp, regmatch_T *regmatch, int *num_file,
e = vim_strchr(s, '\n');
if (e == NULL)
e = s + STRLEN(s);
- const int keep = *e;
+ const char_u keep = *e;
*e = NUL;
const bool skip = xp->xp_pattern[0]
&& vim_regexec(regmatch, s, (colnr_T)0) == 0;
*e = keep;
if (!skip) {
- GA_APPEND(char_u *, &ga, vim_strnsave(s, (int)(e - s)));
+ GA_APPEND(char_u *, &ga, vim_strnsave(s, (size_t)(e - s)));
}
if (*e != NUL) {
@@ -5226,7 +5234,8 @@ static int ExpandRTDir(char_u *pat, int flags, int *num_file, char_u ***file,
}
s++;
*e = NUL;
- memmove(match, s, e - s + 1);
+ assert((e - s) + 1 >= 0);
+ memmove(match, s, (size_t)(e - s) + 1);
}
}
@@ -5262,8 +5271,7 @@ static int ExpandPackAddDir(char_u *pat, int *num_file, char_u ***file)
for (int i = 0; i < ga.ga_len; i++) {
char_u *match = ((char_u **)ga.ga_data)[i];
s = path_tail(match);
- char_u *e = s + STRLEN(s);
- memmove(match, s, e - s + 1);
+ memmove(match, s, STRLEN(s)+1);
}
if (GA_EMPTY(&ga)) {
@@ -5407,7 +5415,9 @@ void init_history(void)
// On copying them to the new arrays, we take the chance to reorder them.
if (newlen != oldlen) {
for (int type = 0; type < HIST_COUNT; type++) {
- histentry_T *temp = newlen ? xmalloc(newlen * sizeof(*temp)) : NULL;
+ histentry_T *temp = (newlen
+ ? xmalloc((size_t)newlen * sizeof(*temp))
+ : NULL);
int j = hisidx[type];
if (j >= 0) {
@@ -5572,7 +5582,6 @@ add_to_history (
)
{
histentry_T *hisptr;
- int len;
if (hislen == 0 || histype == HIST_INVALID) { // no history
return;
@@ -5604,12 +5613,12 @@ add_to_history (
hisptr = &history[histype][hisidx[histype]];
hist_free_entry(hisptr);
- /* Store the separator after the NUL of the string. */
- len = (int)STRLEN(new_entry);
+ // Store the separator after the NUL of the string.
+ size_t len = STRLEN(new_entry);
hisptr->hisstr = vim_strnsave(new_entry, len + 2);
hisptr->timestamp = os_time();
hisptr->additional_elements = NULL;
- hisptr->hisstr[len + 1] = sep;
+ hisptr->hisstr[len + 1] = (char_u)sep;
hisptr->hisnum = ++hisnum[histype];
if (histype == HIST_SEARCH && in_map)
@@ -5663,7 +5672,7 @@ char_u *get_cmdline_str(void)
if (p == NULL)
return NULL;
- return vim_strnsave(p->cmdbuff, p->cmdlen);
+ return vim_strnsave(p->cmdbuff, (size_t)p->cmdlen);
}
/*
@@ -5932,7 +5941,7 @@ void ex_history(exarg_T *eap)
while (ASCII_ISALPHA(*end)
|| vim_strchr((char_u *)":=@>/?", *end) != NULL)
end++;
- histype1 = get_histtype((const char *)arg, end - arg, false);
+ histype1 = get_histtype((const char *)arg, (size_t)(end - arg), false);
if (histype1 == HIST_INVALID) {
if (STRNICMP(arg, "all", end - arg) == 0) {
histype1 = 0;
@@ -5971,13 +5980,14 @@ void ex_history(exarg_T *eap)
if (hist[i].hisstr != NULL
&& hist[i].hisnum >= j && hist[i].hisnum <= k) {
msg_putchar('\n');
- sprintf((char *)IObuff, "%c%6d ", i == idx ? '>' : ' ',
- hist[i].hisnum);
- if (vim_strsize(hist[i].hisstr) > (int)Columns - 10)
+ snprintf((char *)IObuff, IOSIZE, "%c%6d ", i == idx ? '>' : ' ',
+ hist[i].hisnum);
+ if (vim_strsize(hist[i].hisstr) > Columns - 10) {
trunc_string(hist[i].hisstr, IObuff + STRLEN(IObuff),
- (int)Columns - 10, IOSIZE - (int)STRLEN(IObuff));
- else
+ Columns - 10, IOSIZE - (int)STRLEN(IObuff));
+ } else {
STRCAT(IObuff, hist[i].hisstr);
+ }
msg_outtrans(IObuff);
ui_flush();
}
@@ -6138,8 +6148,8 @@ static int open_cmdwin(void)
State = NORMAL;
setmouse();
- /* Trigger CmdwinEnter autocommands. */
- typestr[0] = cmdwin_type;
+ // Trigger CmdwinEnter autocommands.
+ typestr[0] = (char_u)cmdwin_type;
typestr[1] = NUL;
apply_autocmds(EVENT_CMDWINENTER, typestr, typestr, FALSE, curbuf);
if (restart_edit != 0) /* autocmd with ":startinsert" */
@@ -6217,7 +6227,7 @@ static int open_cmdwin(void)
if (ccline.cmdpos > ccline.cmdlen)
ccline.cmdpos = ccline.cmdlen;
if (cmdwin_result == K_IGNORE) {
- set_cmdspos_cursor();
+ ccline.cmdspos = cmd_screencol(ccline.cmdpos);
redrawcmd();
}
}
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index f2a664288b..d2620376c6 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -15,6 +15,7 @@
#include "nvim/ascii.h"
#include "nvim/fileio.h"
#include "nvim/buffer.h"
+#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/diff.h"
@@ -58,10 +59,6 @@
#include "nvim/os/time.h"
#include "nvim/os/input.h"
-#if defined(HAVE_UTIME) && defined(HAVE_UTIME_H)
-# include <utime.h> /* for struct utimbuf */
-#endif
-
#define BUFSIZE 8192 /* size of normal write buffer */
#define SMBUFSIZE 256 /* size of emergency write buffer */
@@ -167,22 +164,22 @@ typedef struct AutoPatCmd {
* Structure to pass arguments from buf_write() to buf_write_bytes().
*/
struct bw_info {
- int bw_fd; /* file descriptor */
- char_u *bw_buf; /* buffer with data to be written */
- int bw_len; /* length of data */
+ int bw_fd; // file descriptor
+ char_u *bw_buf; // buffer with data to be written
+ int bw_len; // length of data
#ifdef HAS_BW_FLAGS
- int bw_flags; /* FIO_ flags */
+ int bw_flags; // FIO_ flags
#endif
- char_u bw_rest[CONV_RESTLEN]; /* not converted bytes */
- int bw_restlen; /* nr of bytes in bw_rest[] */
- int bw_first; /* first write call */
- char_u *bw_conv_buf; /* buffer for writing converted chars */
- int bw_conv_buflen; /* size of bw_conv_buf */
- int bw_conv_error; /* set for conversion error */
- linenr_T bw_conv_error_lnum; /* first line with error or zero */
- linenr_T bw_start_lnum; /* line number at start of buffer */
-# ifdef USE_ICONV
- iconv_t bw_iconv_fd; /* descriptor for iconv() or -1 */
+ char_u bw_rest[CONV_RESTLEN]; // not converted bytes
+ int bw_restlen; // nr of bytes in bw_rest[]
+ int bw_first; // first write call
+ char_u *bw_conv_buf; // buffer for writing converted chars
+ int bw_conv_buflen; // size of bw_conv_buf
+ int bw_conv_error; // set for conversion error
+ linenr_T bw_conv_error_lnum; // first line with error or zero
+ linenr_T bw_start_lnum; // line number at start of buffer
+# ifdef HAVE_ICONV
+ iconv_t bw_iconv_fd; // descriptor for iconv() or -1
# endif
};
@@ -190,10 +187,6 @@ struct bw_info {
# include "fileio.c.generated.h"
#endif
-#ifdef UNIX
-#endif
-
-
static char *e_auchangedbuf = N_(
"E812: Autocommands changed buffer or buffer name");
@@ -266,8 +259,8 @@ static AutoPat *last_autopat[NUM_EVENTS] = {
*
* return FAIL for failure, NOTDONE for directory (failure), or OK
*/
-int
-readfile (
+int
+readfile(
char_u *fname,
char_u *sfname,
linenr_T from,
@@ -297,7 +290,7 @@ readfile (
int wasempty; /* buffer was empty before reading */
colnr_T len;
long size = 0;
- char_u *p = NULL;
+ uint8_t *p = NULL;
off_T filesize = 0;
int skip_read = false;
context_sha256_T sha_ctx;
@@ -335,10 +328,10 @@ readfile (
char_u *fenc_next = NULL; // next item in 'fencs' or NULL
bool advance_fenc = false;
long real_size = 0;
-# ifdef USE_ICONV
- iconv_t iconv_fd = (iconv_t)-1; /* descriptor for iconv() or -1 */
- int did_iconv = FALSE; /* TRUE when iconv() failed and trying
- 'charconvert' next */
+# ifdef HAVE_ICONV
+ iconv_t iconv_fd = (iconv_t)-1; // descriptor for iconv() or -1
+ int did_iconv = false; // TRUE when iconv() failed and trying
+ // 'charconvert' next
# endif
int converted = FALSE; /* TRUE if conversion done */
int notconverted = FALSE; /* TRUE if conversion wanted but it
@@ -849,7 +842,7 @@ retry:
fileformat = EOL_UNKNOWN; /* detect from file */
}
-# ifdef USE_ICONV
+# ifdef HAVE_ICONV
if (iconv_fd != (iconv_t)-1) {
/* aborted conversion with iconv(), close the descriptor */
iconv_close(iconv_fd);
@@ -916,15 +909,14 @@ retry:
-# ifdef USE_ICONV
- /*
- * Try using iconv() if we can't convert internally.
- */
+# ifdef HAVE_ICONV
+ // Try using iconv() if we can't convert internally.
if (fio_flags == 0
&& !did_iconv
- )
+ ) {
iconv_fd = (iconv_t)my_iconv_open(
enc_utf8 ? (char_u *)"utf-8" : p_enc, fenc);
+ }
# endif
/*
@@ -933,12 +925,12 @@ retry:
*/
if (fio_flags == 0 && !read_stdin && !read_buffer && *p_ccv != NUL
&& !read_fifo
-# ifdef USE_ICONV
+# ifdef HAVE_ICONV
&& iconv_fd == (iconv_t)-1
# endif
) {
-# ifdef USE_ICONV
- did_iconv = FALSE;
+# ifdef HAVE_ICONV
+ did_iconv = false;
# endif
/* Skip conversion when it's already done (retry for wrong
* "fileformat"). */
@@ -958,7 +950,7 @@ retry:
}
} else {
if (fio_flags == 0
-# ifdef USE_ICONV
+# ifdef HAVE_ICONV
&& iconv_fd == (iconv_t)-1
# endif
) {
@@ -1031,20 +1023,23 @@ retry:
* ucs-4 to utf-8: 4 bytes become up to 6 bytes, size must be
* multiple of 4 */
real_size = (int)size;
-# ifdef USE_ICONV
- if (iconv_fd != (iconv_t)-1)
+# ifdef HAVE_ICONV
+ if (iconv_fd != (iconv_t)-1) {
size = size / ICONV_MULT;
- else
+ } else {
# endif
- if (fio_flags & FIO_LATIN1)
+ if (fio_flags & FIO_LATIN1) {
size = size / 2;
- else if (fio_flags & (FIO_UCS2 | FIO_UTF16))
+ } else if (fio_flags & (FIO_UCS2 | FIO_UTF16)) {
size = (size * 2 / 3) & ~1;
- else if (fio_flags & FIO_UCS4)
+ } else if (fio_flags & FIO_UCS4) {
size = (size * 2 / 3) & ~3;
- else if (fio_flags == FIO_UCSBOM)
- size = size / ICONV_MULT; /* worst case */
-
+ } else if (fio_flags == FIO_UCSBOM) {
+ size = size / ICONV_MULT; // worst case
+ }
+# ifdef HAVE_ICONV
+ }
+# endif
if (conv_restlen > 0) {
// Insert unconverted bytes from previous line.
memmove(ptr, conv_rest, conv_restlen); // -V614
@@ -1120,7 +1115,7 @@ retry:
/* When we did a conversion report an error. */
if (fio_flags != 0
-# ifdef USE_ICONV
+# ifdef HAVE_ICONV
|| iconv_fd != (iconv_t)-1
# endif
) {
@@ -1143,7 +1138,7 @@ retry:
* leave the UTF8 checking code to do it, as it
* works slightly differently. */
if (bad_char_behavior != BAD_KEEP && (fio_flags != 0
-# ifdef USE_ICONV
+# ifdef HAVE_ICONV
|| iconv_fd != (iconv_t)-1
# endif
)) {
@@ -1152,8 +1147,8 @@ retry:
--conv_restlen;
}
}
- fio_flags = 0; /* don't convert this */
-# ifdef USE_ICONV
+ fio_flags = 0; // don't convert this
+# ifdef HAVE_ICONV
if (iconv_fd != (iconv_t)-1) {
iconv_close(iconv_fd);
iconv_fd = (iconv_t)-1;
@@ -1224,7 +1219,7 @@ retry:
if (size <= 0)
break;
-# ifdef USE_ICONV
+# ifdef HAVE_ICONV
if (iconv_fd != (iconv_t)-1) {
/*
* Attempt conversion of the read bytes to 'encoding' using
@@ -1283,7 +1278,7 @@ retry:
# endif
if (fio_flags != 0) {
- int u8c;
+ unsigned int u8c;
char_u *dest;
char_u *tail = NULL;
@@ -1431,33 +1426,13 @@ retry:
}
}
}
- if (enc_utf8) { /* produce UTF-8 */
- dest -= utf_char2len(u8c);
- (void)utf_char2bytes(u8c, dest);
- } else { /* produce Latin1 */
- --dest;
- if (u8c >= 0x100) {
- /* character doesn't fit in latin1, retry with
- * another fenc when possible, otherwise just
- * report the error. */
- if (can_retry)
- goto rewind_retry;
- if (conv_error == 0)
- conv_error = readfile_linenr(linecnt, ptr, p);
- if (bad_char_behavior == BAD_DROP)
- ++dest;
- else if (bad_char_behavior == BAD_KEEP)
- *dest = u8c;
- else if (eap != NULL && eap->bad_char != 0)
- *dest = bad_char_behavior;
- else
- *dest = 0xBF;
- } else
- *dest = u8c;
- }
+ assert(u8c <= INT_MAX);
+ // produce UTF-8
+ dest -= utf_char2len((int)u8c);
+ (void)utf_char2bytes((int)u8c, dest);
}
- /* move the linerest to before the converted characters */
+ // move the linerest to before the converted characters
line_start = dest - linerest;
memmove(line_start, buffer, (size_t)linerest);
size = (long)((ptr + real_size) - dest);
@@ -1465,18 +1440,19 @@ retry:
} else if (enc_utf8 && !curbuf->b_p_bin) {
int incomplete_tail = FALSE;
- /* Reading UTF-8: Check if the bytes are valid UTF-8. */
- for (p = ptr;; ++p) {
+ // Reading UTF-8: Check if the bytes are valid UTF-8.
+ for (p = ptr;; p++) {
int todo = (int)((ptr + size) - p);
int l;
- if (todo <= 0)
+ if (todo <= 0) {
break;
+ }
if (*p >= 0x80) {
- /* A length of 1 means it's an illegal byte. Accept
- * an incomplete character at the end though, the next
- * read() will get the next bytes, we'll check it
- * then. */
+ // A length of 1 means it's an illegal byte. Accept
+ // an incomplete character at the end though, the next
+ // read() will get the next bytes, we'll check it
+ // then.
l = utf_ptr2len_len(p, todo);
if (l > todo && !incomplete_tail) {
/* Avoid retrying with a different encoding when
@@ -1501,10 +1477,11 @@ retry:
* file is more likely than a conversion error. */
if (can_retry && !incomplete_tail)
break;
-# ifdef USE_ICONV
- /* When we did a conversion report an error. */
- if (iconv_fd != (iconv_t)-1 && conv_error == 0)
+# ifdef HAVE_ICONV
+ // When we did a conversion report an error.
+ if (iconv_fd != (iconv_t)-1 && conv_error == 0) {
conv_error = readfile_linenr(linecnt, ptr, p);
+ }
# endif
/* Remember the first linenr with an illegal byte */
if (conv_error == 0 && illegal_byte == 0)
@@ -1524,15 +1501,18 @@ retry:
if (p < ptr + size && !incomplete_tail) {
/* Detected a UTF-8 error. */
rewind_retry:
- /* Retry reading with another conversion. */
-# ifdef USE_ICONV
- if (*p_ccv != NUL && iconv_fd != (iconv_t)-1)
- /* iconv() failed, try 'charconvert' */
- did_iconv = TRUE;
- else
+ // Retry reading with another conversion.
+# ifdef HAVE_ICONV
+ if (*p_ccv != NUL && iconv_fd != (iconv_t)-1) {
+ // iconv() failed, try 'charconvert'
+ did_iconv = true;
+ } else {
# endif
// use next item from 'fileencodings'
advance_fenc = true;
+# ifdef HAVE_ICONV
+ }
+# endif
file_rewind = true;
goto retry;
}
@@ -1731,13 +1711,13 @@ failed:
// Remember the current file format.
save_file_ff(curbuf);
// If editing a new file: set 'fenc' for the current buffer.
- // Also for ":read ++edit file".
+ // Also for ":read ++edit file".
set_string_option_direct((char_u *)"fenc", -1, fenc,
OPT_FREE | OPT_LOCAL, 0);
}
if (fenc_alloced)
xfree(fenc);
-# ifdef USE_ICONV
+# ifdef HAVE_ICONV
if (iconv_fd != (iconv_t)-1) {
iconv_close(iconv_fd);
# ifndef __clang_analyzer__
@@ -1782,6 +1762,9 @@ failed:
ml_delete(curbuf->b_ml.ml_line_count, false);
linecnt--;
}
+ curbuf->deleted_bytes = 0;
+ curbuf->deleted_codepoints = 0;
+ curbuf->deleted_codeunits = 0;
linecnt = curbuf->b_ml.ml_line_count - linecnt;
if (filesize == 0)
linecnt = 0;
@@ -2030,11 +2013,11 @@ bool is_dev_fd_file(char_u *fname)
* line number where we are now.
* Used for error messages that include a line number.
*/
-static linenr_T
-readfile_linenr (
- linenr_T linecnt, /* line count before reading more bytes */
- char_u *p, /* start of more bytes read */
- char_u *endp /* end of more bytes read */
+static linenr_T
+readfile_linenr(
+ linenr_T linecnt, // line count before reading more bytes
+ char_u *p, // start of more bytes read
+ char_u *endp // end of more bytes read
)
{
char_u *s;
@@ -2198,34 +2181,6 @@ static void check_marks_read(void)
curbuf->b_marks_read = true;
}
-#ifdef UNIX
-static void
-set_file_time (
- char_u *fname,
- time_t atime, /* access time */
- time_t mtime /* modification time */
-)
-{
-# if defined(HAVE_UTIME) && defined(HAVE_UTIME_H)
- struct utimbuf buf;
-
- buf.actime = atime;
- buf.modtime = mtime;
- (void)utime((char *)fname, &buf);
-# else
-# if defined(HAVE_UTIMES)
- struct timeval tvp[2];
-
- tvp[0].tv_sec = atime;
- tvp[0].tv_usec = 0;
- tvp[1].tv_sec = mtime;
- tvp[1].tv_usec = 0;
- (void)utimes((char *)fname, (const struct timeval *)&tvp);
-# endif
-# endif
-}
-#endif /* UNIX */
-
/*
* buf_write() - write to file "fname" lines "start" through "end"
*
@@ -2242,8 +2197,8 @@ set_file_time (
*
* return FAIL for failure, OK otherwise
*/
-int
-buf_write (
+int
+buf_write(
buf_T *buf,
char_u *fname,
char_u *sfname,
@@ -2346,7 +2301,7 @@ buf_write (
write_info.bw_conv_error = FALSE;
write_info.bw_conv_error_lnum = 0;
write_info.bw_restlen = 0;
-# ifdef USE_ICONV
+# ifdef HAVE_ICONV
write_info.bw_iconv_fd = (iconv_t)-1;
# endif
@@ -2887,9 +2842,9 @@ buf_write (
}
#ifdef UNIX
- set_file_time(backup,
- file_info_old.stat.st_atim.tv_sec,
- file_info_old.stat.st_mtim.tv_sec);
+ os_file_settime((char *)backup,
+ file_info_old.stat.st_atim.tv_sec,
+ file_info_old.stat.st_mtim.tv_sec);
#endif
#ifdef HAVE_ACL
mch_set_acl(backup, acl);
@@ -3067,7 +3022,7 @@ nobackup:
if (converted && wb_flags == 0) {
-# ifdef USE_ICONV
+# ifdef HAVE_ICONV
// Use iconv() conversion when conversion is needed and it's not done
// internally.
write_info.bw_iconv_fd = (iconv_t)my_iconv_open(fenc,
@@ -3097,7 +3052,7 @@ nobackup:
}
}
if (converted && wb_flags == 0
-# ifdef USE_ICONV
+# ifdef HAVE_ICONV
&& write_info.bw_iconv_fd == (iconv_t)-1
# endif
&& wfname == fname
@@ -3572,9 +3527,9 @@ restore_backup:
vim_rename(backup, (char_u *)org);
XFREE_CLEAR(backup); // don't delete the file
#ifdef UNIX
- set_file_time((char_u *)org,
- file_info_old.stat.st_atim.tv_sec,
- file_info_old.stat.st_mtim.tv_sec);
+ os_file_settime(org,
+ file_info_old.stat.st_atim.tv_sec,
+ file_info_old.stat.st_mtim.tv_sec);
#endif
}
}
@@ -3625,7 +3580,7 @@ nofail:
xfree(buffer);
xfree(fenc_tofree);
xfree(write_info.bw_conv_buf);
-# ifdef USE_ICONV
+# ifdef HAVE_ICONV
if (write_info.bw_iconv_fd != (iconv_t)-1) {
iconv_close(write_info.bw_iconv_fd);
write_info.bw_iconv_fd = (iconv_t)-1;
@@ -4000,7 +3955,7 @@ static int buf_write_bytes(struct bw_info *ip)
}
}
-# ifdef USE_ICONV
+# ifdef HAVE_ICONV
if (ip->bw_iconv_fd != (iconv_t)-1) {
const char *from;
size_t fromlen;
@@ -4303,15 +4258,13 @@ void shorten_buf_fname(buf_T *buf, char_u *dirname, int force)
buf->b_sfname = vim_strsave(p);
buf->b_fname = buf->b_sfname;
}
- if (p == NULL || buf->b_fname == NULL) {
+ if (p == NULL) {
buf->b_fname = buf->b_ffname;
}
}
}
-/*
- * Shorten filenames for all buffers.
- */
+/// Shorten filenames for all buffers.
void shorten_fnames(int force)
{
char_u dirname[MAXPATHL];
@@ -4320,8 +4273,8 @@ void shorten_fnames(int force)
FOR_ALL_BUFFERS(buf) {
shorten_buf_fname(buf, dirname, force);
- /* Always make the swap file name a full path, a "nofile" buffer may
- * also have a swap file. */
+ // Always make the swap file name a full path, a "nofile" buffer may
+ // also have a swap file.
mf_fullname(buf->b_ml.ml_mfp);
}
status_redraw_all();
@@ -4743,17 +4696,15 @@ int vim_rename(const char_u *from, const char_u *to)
static int already_warned = FALSE;
-/*
- * Check if any not hidden buffer has been changed.
- * Postpone the check if there are characters in the stuff buffer, a global
- * command is being executed, a mapping is being executed or an autocommand is
- * busy.
- * Returns TRUE if some message was written (screen should be redrawn and
- * cursor positioned).
- */
-int
-check_timestamps (
- int focus /* called for GUI focus event */
+// Check if any not hidden buffer has been changed.
+// Postpone the check if there are characters in the stuff buffer, a global
+// command is being executed, a mapping is being executed or an autocommand is
+// busy.
+// Returns TRUE if some message was written (screen should be redrawn and
+// cursor positioned).
+int
+check_timestamps(
+ int focus // called for GUI focus event
)
{
int didit = 0;
@@ -4855,8 +4806,8 @@ static int move_lines(buf_T *frombuf, buf_T *tobuf)
* return 2 if a message has been displayed.
* return 0 otherwise.
*/
-int
-buf_check_timestamp (
+int
+buf_check_timestamp(
buf_T *buf,
int focus /* called for GUI focus event */
)
@@ -4866,13 +4817,12 @@ buf_check_timestamp (
char_u *path;
char *mesg = NULL;
char *mesg2 = "";
- int helpmesg = FALSE;
- int reload = FALSE;
- int can_reload = FALSE;
+ bool helpmesg = false;
+ bool reload = false;
+ bool can_reload = false;
uint64_t orig_size = buf->b_orig_size;
int orig_mode = buf->b_orig_mode;
- static int busy = FALSE;
- int n;
+ static bool busy = false;
char_u *s;
char *reason;
@@ -4897,16 +4847,16 @@ buf_check_timestamp (
&& buf->b_mtime != 0
&& (!(file_info_ok = os_fileinfo((char *)buf->b_ffname, &file_info))
|| time_differs(file_info.stat.st_mtim.tv_sec, buf->b_mtime)
- || (int)file_info.stat.st_mode != buf->b_orig_mode
- )) {
+ || (int)file_info.stat.st_mode != buf->b_orig_mode)) {
+ const long prev_b_mtime = buf->b_mtime;
+
retval = 1;
// set b_mtime to stop further warnings (e.g., when executing
// FileChangedShell autocmd)
if (!file_info_ok) {
- // When 'autoread' is set we'll check the file again to see if it
- // re-appears.
- buf->b_mtime = buf->b_p_ar;
+ // Check the file again later to see if it re-appears.
+ buf->b_mtime = -1;
buf->b_orig_size = 0;
buf->b_orig_mode = 0;
} else {
@@ -4915,28 +4865,25 @@ buf_check_timestamp (
/* Don't do anything for a directory. Might contain the file
* explorer. */
- if (os_isdir(buf->b_fname))
- ;
-
- /*
- * If 'autoread' is set, the buffer has no changes and the file still
- * exists, reload the buffer. Use the buffer-local option value if it
- * was set, the global option value otherwise.
- */
- else if ((buf->b_p_ar >= 0 ? buf->b_p_ar : p_ar)
- && !bufIsChanged(buf) && file_info_ok)
- reload = TRUE;
- else {
- if (!file_info_ok)
+ if (os_isdir(buf->b_fname)) {
+ } else if ((buf->b_p_ar >= 0 ? buf->b_p_ar : p_ar)
+ && !bufIsChanged(buf) && file_info_ok) {
+ // If 'autoread' is set, the buffer has no changes and the file still
+ // exists, reload the buffer. Use the buffer-local option value if it
+ // was set, the global option value otherwise.
+ reload = true;
+ } else {
+ if (!file_info_ok) {
reason = "deleted";
- else if (bufIsChanged(buf))
+ } else if (bufIsChanged(buf)) {
reason = "conflict";
- else if (orig_size != buf->b_orig_size || buf_contents_changed(buf))
+ } else if (orig_size != buf->b_orig_size || buf_contents_changed(buf)) {
reason = "changed";
- else if (orig_mode != buf->b_orig_mode)
+ } else if (orig_mode != buf->b_orig_mode) {
reason = "mode";
- else
+ } else {
reason = "time";
+ }
// Only give the warning if there are no FileChangedShell
// autocommands.
@@ -4945,8 +4892,8 @@ buf_check_timestamp (
set_vim_var_string(VV_FCS_REASON, reason, -1);
set_vim_var_string(VV_FCS_CHOICE, "", -1);
allbuf_lock++;
- n = apply_autocmds(EVENT_FILECHANGEDSHELL,
- buf->b_fname, buf->b_fname, false, buf);
+ bool n = apply_autocmds(EVENT_FILECHANGEDSHELL,
+ buf->b_fname, buf->b_fname, false, buf);
allbuf_lock--;
busy = false;
if (n) {
@@ -4954,25 +4901,28 @@ buf_check_timestamp (
EMSG(_("E246: FileChangedShell autocommand deleted buffer"));
}
s = get_vim_var_str(VV_FCS_CHOICE);
- if (STRCMP(s, "reload") == 0 && *reason != 'd')
- reload = TRUE;
- else if (STRCMP(s, "ask") == 0)
- n = FALSE;
- else
+ if (STRCMP(s, "reload") == 0 && *reason != 'd') {
+ reload = true;
+ } else if (STRCMP(s, "ask") == 0) {
+ n = false;
+ } else {
return 2;
+ }
}
if (!n) {
- if (*reason == 'd')
- mesg = _("E211: File \"%s\" no longer available");
- else {
- helpmesg = TRUE;
- can_reload = TRUE;
- /*
- * Check if the file contents really changed to avoid
- * giving a warning when only the timestamp was set (e.g.,
- * checked out of CVS). Always warn when the buffer was
- * changed.
- */
+ if (*reason == 'd') {
+ // Only give the message once.
+ if (prev_b_mtime != -1) {
+ mesg = _("E211: File \"%s\" no longer available");
+ }
+ } else {
+ helpmesg = true;
+ can_reload = true;
+
+ // Check if the file contents really changed to avoid
+ // giving a warning when only the timestamp was set (e.g.,
+ // checked out of CVS). Always warn when the buffer was
+ // changed.
if (reason[2] == 'n') {
mesg = _(
"W12: Warning: File \"%s\" has changed and the buffer was changed in Vim as well");
@@ -4998,7 +4948,7 @@ buf_check_timestamp (
retval = 1;
mesg = _("W13: Warning: File \"%s\" has been created after editing started");
buf->b_flags |= BF_NEW_W;
- can_reload = TRUE;
+ can_reload = true;
}
if (mesg != NULL) {
@@ -6304,12 +6254,10 @@ static int do_autocmd_event(event_T event, char_u *pat, bool once, int nested,
return OK;
}
-/*
- * Implementation of ":doautocmd [group] event [fname]".
- * Return OK for success, FAIL for failure;
- */
-int
-do_doautocmd (
+// Implementation of ":doautocmd [group] event [fname]".
+// Return OK for success, FAIL for failure;
+int
+do_doautocmd(
char_u *arg,
int do_msg, // give message for no matching autocmds?
bool *did_something
@@ -7093,11 +7041,9 @@ void unblock_autocmds(void)
apply_autocmds(EVENT_TERMRESPONSE, NULL, NULL, FALSE, curbuf);
}
-/*
- * Find next autocommand pattern that matches.
- */
-static void
-auto_next_pat (
+// Find next autocommand pattern that matches.
+static void
+auto_next_pat(
AutoPatCmd *apc,
int stop_at_last /* stop when 'last' flag is set */
)
diff --git a/src/nvim/fold.c b/src/nvim/fold.c
index 72d8c14468..ad0bfe29e2 100644
--- a/src/nvim/fold.c
+++ b/src/nvim/fold.c
@@ -13,6 +13,7 @@
#include "nvim/vim.h"
#include "nvim/ascii.h"
#include "nvim/fold.h"
+#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/diff.h"
diff --git a/src/nvim/func_attr.h b/src/nvim/func_attr.h
index d3b600a40c..14b8158c7f 100644
--- a/src/nvim/func_attr.h
+++ b/src/nvim/func_attr.h
@@ -205,8 +205,8 @@
#endif
#ifdef DEFINE_FUNC_ATTRIBUTES
-/// Non-deferred API function.
-# define FUNC_API_ASYNC
+/// Fast (non-deferred) API function.
+# define FUNC_API_FAST
/// Internal C function not exposed in the RPC API.
# define FUNC_API_NOEXPORT
/// API function not exposed in VimL/eval.
diff --git a/src/nvim/garray.c b/src/nvim/garray.c
index 74fd9d89cb..1cfc2b6176 100644
--- a/src/nvim/garray.c
+++ b/src/nvim/garray.c
@@ -89,6 +89,14 @@ void ga_grow(garray_T *gap, int n)
if (n < gap->ga_growsize) {
n = gap->ga_growsize;
}
+
+ // A linear growth is very inefficient when the array grows big. This
+ // is a compromise between allocating memory that won't be used and too
+ // many copy operations. A factor of 1.5 seems reasonable.
+ if (n < gap->ga_len / 2) {
+ n = gap->ga_len / 2;
+ }
+
int new_maxlen = gap->ga_len + n;
size_t new_size = (size_t)gap->ga_itemsize * (size_t)new_maxlen;
diff --git a/src/nvim/generators/c_grammar.lua b/src/nvim/generators/c_grammar.lua
index 40bdc3e30c..1aa8223da0 100644
--- a/src/nvim/generators/c_grammar.lua
+++ b/src/nvim/generators/c_grammar.lua
@@ -1,4 +1,4 @@
-lpeg = require('lpeg')
+local lpeg = require('lpeg')
-- lpeg grammar for building api metadata from a set of header files. It
-- ignores comments and preprocessor commands and parses a very small subset
@@ -35,11 +35,11 @@ local c_params = Ct(c_void + c_param_list)
local c_proto = Ct(
Cg(c_type, 'return_type') * Cg(c_id, 'name') *
fill * P('(') * fill * Cg(c_params, 'parameters') * fill * P(')') *
- Cg(Cc(false), 'async') *
+ Cg(Cc(false), 'fast') *
(fill * Cg((P('FUNC_API_SINCE(') * C(num ^ 1)) * P(')'), 'since') ^ -1) *
(fill * Cg((P('FUNC_API_DEPRECATED_SINCE(') * C(num ^ 1)) * P(')'),
'deprecated_since') ^ -1) *
- (fill * Cg((P('FUNC_API_ASYNC') * Cc(true)), 'async') ^ -1) *
+ (fill * Cg((P('FUNC_API_FAST') * Cc(true)), 'fast') ^ -1) *
(fill * Cg((P('FUNC_API_NOEXPORT') * Cc(true)), 'noexport') ^ -1) *
(fill * Cg((P('FUNC_API_REMOTE_ONLY') * Cc(true)), 'remote_only') ^ -1) *
(fill * Cg((P('FUNC_API_REMOTE_IMPL') * Cc(true)), 'remote_impl') ^ -1) *
diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua
index f52d05a4a5..76dcf849d1 100644
--- a/src/nvim/generators/gen_api_dispatch.lua
+++ b/src/nvim/generators/gen_api_dispatch.lua
@@ -1,4 +1,4 @@
-mpack = require('mpack')
+local mpack = require('mpack')
-- we need at least 4 arguments since the last two are output files
if arg[1] == '--help' then
@@ -11,27 +11,27 @@ if arg[1] == '--help' then
print(' rest: C files where API functions are defined')
end
assert(#arg >= 4)
-functions = {}
+local functions = {}
local nvimdir = arg[1]
package.path = nvimdir .. '/?.lua;' .. package.path
-- names of all headers relative to the source root (for inclusion in the
-- generated file)
-headers = {}
+local headers = {}
-- output h file with generated dispatch functions
-dispatch_outputf = arg[2]
+local dispatch_outputf = arg[2]
-- output h file with packed metadata
-funcs_metadata_outputf = arg[3]
+local funcs_metadata_outputf = arg[3]
-- output metadata mpack file, for use by other build scripts
-mpack_outputf = arg[4]
-lua_c_bindings_outputf = arg[5]
+local mpack_outputf = arg[4]
+local lua_c_bindings_outputf = arg[5]
-- set of function names, used to detect duplicates
-function_names = {}
+local function_names = {}
-c_grammar = require('generators.c_grammar')
+local c_grammar = require('generators.c_grammar')
-- read each input file, parse and append to the api metadata
for i = 6, #arg do
@@ -45,10 +45,10 @@ for i = 6, #arg do
local input = io.open(full_path, 'rb')
local tmp = c_grammar.grammar:match(input:read('*all'))
- for i = 1, #tmp do
- local fn = tmp[i]
+ for j = 1, #tmp do
+ local fn = tmp[j]
if not fn.noexport then
- functions[#functions + 1] = tmp[i]
+ functions[#functions + 1] = tmp[j]
function_names[fn.name] = true
if #fn.parameters ~= 0 and fn.parameters[1][2] == 'channel_id' then
-- this function should receive the channel id
@@ -83,7 +83,7 @@ end
-- Export functions under older deprecated names.
-- These will be removed eventually.
local deprecated_aliases = require("api.dispatch_deprecated")
-for i,f in ipairs(shallowcopy(functions)) do
+for _,f in ipairs(shallowcopy(functions)) do
local ismethod = false
if startswith(f.name, "nvim_") then
if startswith(f.name, "nvim__") then
@@ -135,9 +135,9 @@ for i,f in ipairs(shallowcopy(functions)) do
end
-- don't expose internal attributes like "impl_name" in public metadata
-exported_attributes = {'name', 'return_type', 'method',
- 'since', 'deprecated_since'}
-exported_functions = {}
+local exported_attributes = {'name', 'return_type', 'method',
+ 'since', 'deprecated_since'}
+local exported_functions = {}
for _,f in ipairs(functions) do
if not startswith(f.name, "nvim__") then
local f_exported = {}
@@ -158,14 +158,14 @@ end
-- serialize the API metadata using msgpack and embed into the resulting
-- binary for easy querying by clients
-funcs_metadata_output = io.open(funcs_metadata_outputf, 'wb')
-packed = mpack.pack(exported_functions)
-dump_bin_array = require("generators.dump_bin_array")
+local funcs_metadata_output = io.open(funcs_metadata_outputf, 'wb')
+local packed = mpack.pack(exported_functions)
+local dump_bin_array = require("generators.dump_bin_array")
dump_bin_array(funcs_metadata_output, 'funcs_metadata', packed)
funcs_metadata_output:close()
-- start building the dispatch wrapper output
-output = io.open(dispatch_outputf, 'wb')
+local output = io.open(dispatch_outputf, 'wb')
local function real_type(type)
local rv = type
@@ -198,7 +198,8 @@ for i = 1, #functions do
output:write('Object handle_'..fn.name..'(uint64_t channel_id, Array args, Error *error)')
output:write('\n{')
output:write('\n#if MIN_LOG_LEVEL <= DEBUG_LOG_LEVEL')
- output:write('\n logmsg(DEBUG_LOG_LEVEL, "RPC: ", NULL, -1, true, "invoke '..fn.name..'");')
+ output:write('\n logmsg(DEBUG_LOG_LEVEL, "RPC: ", NULL, -1, true, "ch %" PRIu64 ": invoke '
+ ..fn.name..'", channel_id);')
output:write('\n#endif')
output:write('\n Object ret = NIL;')
-- Declare/initialize variables that will hold converted arguments
@@ -209,20 +210,22 @@ for i = 1, #functions do
end
output:write('\n')
output:write('\n if (args.size != '..#fn.parameters..') {')
- output:write('\n api_set_error(error, kErrorTypeException, "Wrong number of arguments: expecting '..#fn.parameters..' but got %zu", args.size);')
+ output:write('\n api_set_error(error, kErrorTypeException, \
+ "Wrong number of arguments: expecting '..#fn.parameters..' but got %zu", args.size);')
output:write('\n goto cleanup;')
output:write('\n }\n')
-- Validation/conversion for each argument
for j = 1, #fn.parameters do
- local converted, convert_arg, param, arg
+ local converted, param
param = fn.parameters[j]
converted = 'arg_'..j
local rt = real_type(param[1])
if rt ~= 'Object' then
if rt:match('^Buffer$') or rt:match('^Window$') or rt:match('^Tabpage$') then
-- Buffer, Window, and Tabpage have a specific type, but are stored in integer
- output:write('\n if (args.items['..(j - 1)..'].type == kObjectType'..rt..' && args.items['..(j - 1)..'].data.integer >= 0) {')
+ output:write('\n if (args.items['..
+ (j - 1)..'].type == kObjectType'..rt..' && args.items['..(j - 1)..'].data.integer >= 0) {')
output:write('\n '..converted..' = (handle_T)args.items['..(j - 1)..'].data.integer;')
else
output:write('\n if (args.items['..(j - 1)..'].type == kObjectType'..rt..') {')
@@ -230,16 +233,18 @@ for i = 1, #functions do
end
if rt:match('^Buffer$') or rt:match('^Window$') or rt:match('^Tabpage$') or rt:match('^Boolean$') then
-- accept nonnegative integers for Booleans, Buffers, Windows and Tabpages
- output:write('\n } else if (args.items['..(j - 1)..'].type == kObjectTypeInteger && args.items['..(j - 1)..'].data.integer >= 0) {')
+ output:write('\n } else if (args.items['..
+ (j - 1)..'].type == kObjectTypeInteger && args.items['..(j - 1)..'].data.integer >= 0) {')
output:write('\n '..converted..' = (handle_T)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) {')
+ output:write('\n } else if (args.items['..(j - 1)..'].type == kObjectTypeArray && args.items['..(j - 1)..'].data.array.size == 0) {') --luacheck: ignore 631
output:write('\n '..converted..' = (Dictionary)ARRAY_DICT_INIT;')
end
output:write('\n } else {')
- output:write('\n api_set_error(error, kErrorTypeException, "Wrong type for argument '..j..', expecting '..param[1]..'");')
+ output:write('\n api_set_error(error, kErrorTypeException, \
+ "Wrong type for argument '..j..', expecting '..param[1]..'");')
output:write('\n goto cleanup;')
output:write('\n }\n')
else
@@ -309,26 +314,26 @@ for i = 1, #functions do
'(String) {.data = "'..fn.name..'", '..
'.size = sizeof("'..fn.name..'") - 1}, '..
'(MsgpackRpcRequestHandler) {.fn = handle_'.. (fn.impl_name or fn.name)..
- ', .async = '..tostring(fn.async)..'});\n')
+ ', .fast = '..tostring(fn.fast)..'});\n')
end
output:write('\n}\n\n')
output:close()
-mpack_output = io.open(mpack_outputf, 'wb')
+local mpack_output = io.open(mpack_outputf, 'wb')
mpack_output:write(mpack.pack(functions))
mpack_output:close()
-local function include_headers(output, headers)
- for i = 1, #headers do
- if headers[i]:sub(-12) ~= '.generated.h' then
- output:write('\n#include "nvim/'..headers[i]..'"')
+local function include_headers(output_handle, headers_to_include)
+ for i = 1, #headers_to_include do
+ if headers_to_include[i]:sub(-12) ~= '.generated.h' then
+ output_handle:write('\n#include "nvim/'..headers_to_include[i]..'"')
end
end
end
-local function write_shifted_output(output, str)
+local function write_shifted_output(_, str)
str = str:gsub('\n ', '\n')
str = str:gsub('^ ', '')
str = str:gsub(' +$', '')
@@ -349,14 +354,15 @@ output:write([[
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
#include "nvim/lua/converter.h"
+#include "nvim/lua/executor.h"
]])
include_headers(output, headers)
output:write('\n')
-lua_c_functions = {}
+local lua_c_functions = {}
local function process_function(fn)
- lua_c_function_name = ('nlua_msgpack_%s'):format(fn.name)
+ local lua_c_function_name = ('nlua_msgpack_%s'):format(fn.name)
write_shifted_output(output, string.format([[
static int %s(lua_State *lstate)
@@ -372,14 +378,22 @@ local function process_function(fn)
binding=lua_c_function_name,
api=fn.name
}
+
+ if not fn.fast then
+ write_shifted_output(output, string.format([[
+ if (!nlua_is_deferred_safe(lstate)) {
+ return luaL_error(lstate, e_luv_api_disabled, "%s");
+ }
+ ]], fn.name))
+ end
local cparams = ''
local free_code = {}
for j = #fn.parameters,1,-1 do
- param = fn.parameters[j]
- cparam = string.format('arg%u', j)
- param_type = real_type(param[1])
- lc_param_type = real_type(param[1]):lower()
- extra = ((param_type == "Object" or param_type == "Dictionary") and "false, ") or ""
+ local param = fn.parameters[j]
+ local cparam = string.format('arg%u', j)
+ local param_type = real_type(param[1])
+ local lc_param_type = real_type(param[1]):lower()
+ local extra = ((param_type == "Object" or param_type == "Dictionary") and "false, ") or ""
if param[1] == "DictionaryOf(LuaRef)" then
extra = "true, "
end
@@ -424,6 +438,7 @@ local function process_function(fn)
return lua_error(lstate);
}
]]
+ local return_type
if fn.return_type ~= 'void' then
if fn.return_type:match('^ArrayOf') then
return_type = 'Array'
@@ -432,7 +447,7 @@ local function process_function(fn)
end
write_shifted_output(output, string.format([[
const %s ret = %s(%s);
- nlua_push_%s(lstate, ret);
+ nlua_push_%s(lstate, ret, true);
api_free_%s(ret);
%s
%s
diff --git a/src/nvim/generators/gen_api_ui_events.lua b/src/nvim/generators/gen_api_ui_events.lua
index c8ab310b02..3cb117d8b5 100644
--- a/src/nvim/generators/gen_api_ui_events.lua
+++ b/src/nvim/generators/gen_api_ui_events.lua
@@ -1,20 +1,20 @@
-mpack = require('mpack')
+local mpack = require('mpack')
local nvimdir = arg[1]
package.path = nvimdir .. '/?.lua;' .. package.path
assert(#arg == 7)
-input = io.open(arg[2], 'rb')
-proto_output = io.open(arg[3], 'wb')
-call_output = io.open(arg[4], 'wb')
-remote_output = io.open(arg[5], 'wb')
-bridge_output = io.open(arg[6], 'wb')
-metadata_output = io.open(arg[7], 'wb')
-
-c_grammar = require('generators.c_grammar')
+local input = io.open(arg[2], 'rb')
+local proto_output = io.open(arg[3], 'wb')
+local call_output = io.open(arg[4], 'wb')
+local remote_output = io.open(arg[5], 'wb')
+local bridge_output = io.open(arg[6], 'wb')
+local metadata_output = io.open(arg[7], 'wb')
+
+local c_grammar = require('generators.c_grammar')
local events = c_grammar.grammar:match(input:read('*all'))
-function write_signature(output, ev, prefix, notype)
+local function write_signature(output, ev, prefix, notype)
output:write('('..prefix)
if prefix == "" and #ev.parameters == 0 then
output:write('void')
@@ -32,7 +32,7 @@ function write_signature(output, ev, prefix, notype)
output:write(')')
end
-function write_arglist(output, ev, need_copy)
+local function write_arglist(output, ev, need_copy)
output:write(' Array args = ARRAY_DICT_INIT;\n')
for j = 1, #ev.parameters do
local param = ev.parameters[j]
@@ -51,7 +51,7 @@ function write_arglist(output, ev, need_copy)
end
for i = 1, #events do
- ev = events[i]
+ local ev = events[i]
assert(ev.return_type == 'void')
if ev.since == nil and not ev.noexport then
@@ -75,11 +75,11 @@ for i = 1, #events do
end
if not ev.bridge_impl and not ev.noexport then
- send, argv, recv, recv_argv, recv_cleanup = '', '', '', '', ''
- argc = 1
+ local send, argv, recv, recv_argv, recv_cleanup = '', '', '', '', ''
+ local argc = 1
for j = 1, #ev.parameters do
local param = ev.parameters[j]
- copy = 'copy_'..param[2]
+ local copy = 'copy_'..param[2]
if param[1] == 'String' then
send = send..' String copy_'..param[2]..' = copy_string('..param[2]..');\n'
argv = argv..', '..copy..'.data, INT2PTR('..copy..'.size)'
@@ -160,7 +160,6 @@ for i = 1, #events do
call_output:write(";\n")
call_output:write("}\n\n")
end
-
end
proto_output:close()
@@ -169,9 +168,9 @@ remote_output:close()
bridge_output:close()
-- don't expose internal attributes like "impl_name" in public metadata
-exported_attributes = {'name', 'parameters',
+local exported_attributes = {'name', 'parameters',
'since', 'deprecated_since'}
-exported_events = {}
+local exported_events = {}
for _,ev in ipairs(events) do
local ev_exported = {}
for _,attr in ipairs(exported_attributes) do
@@ -187,7 +186,7 @@ for _,ev in ipairs(events) do
end
end
-packed = mpack.pack(exported_events)
-dump_bin_array = require("generators.dump_bin_array")
+local packed = mpack.pack(exported_events)
+local dump_bin_array = require("generators.dump_bin_array")
dump_bin_array(metadata_output, 'ui_events_metadata', packed)
metadata_output:close()
diff --git a/src/nvim/generators/gen_char_blob.lua b/src/nvim/generators/gen_char_blob.lua
index d860375e26..1702add2e4 100644
--- a/src/nvim/generators/gen_char_blob.lua
+++ b/src/nvim/generators/gen_char_blob.lua
@@ -13,16 +13,17 @@ local source_file = arg[1]
local target_file = arg[2]
local varname = arg[3]
-source = io.open(source_file, 'r')
-target = io.open(target_file, 'w')
+local source = io.open(source_file, 'r')
+local target = io.open(target_file, 'w')
target:write('#include <stdint.h>\n\n')
target:write(('static const uint8_t %s[] = {\n'):format(varname))
-num_bytes = 0
-MAX_NUM_BYTES = 15 -- 78 / 5: maximum number of bytes on one line
+local num_bytes = 0
+local MAX_NUM_BYTES = 15 -- 78 / 5: maximum number of bytes on one line
target:write(' ')
+local increase_num_bytes
increase_num_bytes = function()
num_bytes = num_bytes + 1
if num_bytes == MAX_NUM_BYTES then
@@ -33,7 +34,7 @@ end
for line in source:lines() do
for i = 1,string.len(line) do
- byte = string.byte(line, i)
+ local byte = string.byte(line, i)
assert(byte ~= 0)
target:write(string.format(' %3u,', byte))
increase_num_bytes()
diff --git a/src/nvim/generators/gen_declarations.lua b/src/nvim/generators/gen_declarations.lua
index c40c37bb3e..ad44613f42 100755
--- a/src/nvim/generators/gen_declarations.lua
+++ b/src/nvim/generators/gen_declarations.lua
@@ -10,7 +10,7 @@ local lpeg = require('lpeg')
local fold = function (func, ...)
local result = nil
- for i, v in ipairs({...}) do
+ for _, v in ipairs({...}) do
if result == nil then
result = v
else
@@ -107,7 +107,7 @@ local typ_part = concat(
)),
spaces
)
-local typ = one_or_more(typ_part)
+
local typ_id = two_or_more(typ_part)
local arg = typ_id -- argument name is swallowed by typ
local pattern = concat(
@@ -222,7 +222,6 @@ local non_static = header
local static = header
local filepattern = '^#%a* (%d+) "([^"]-)/?([^"/]+)"'
-local curfile
local init = 1
local curfile = nil
@@ -248,14 +247,14 @@ while init ~= nil do
else
declline = declline - 1
end
- elseif init < declendpos then
+ elseif init < declendpos then -- luacheck: ignore 542
-- Skipping over declaration
elseif is_needed_file then
s = init
- e = pattern:match(text, init)
+ local e = pattern:match(text, init)
if e ~= nil then
local declaration = text:sub(s, e - 1)
- -- Comments are really handled by preprocessor, so the following is not
+ -- Comments are really handled by preprocessor, so the following is not
-- needed
declaration = declaration:gsub('/%*.-%*/', '')
declaration = declaration:gsub('//.-\n', '\n')
diff --git a/src/nvim/generators/gen_eval.lua b/src/nvim/generators/gen_eval.lua
index 23435a1d0b..2c6f8f2603 100644
--- a/src/nvim/generators/gen_eval.lua
+++ b/src/nvim/generators/gen_eval.lua
@@ -1,4 +1,4 @@
-mpack = require('mpack')
+local mpack = require('mpack')
local nvimsrcdir = arg[1]
local autodir = arg[2]
@@ -8,9 +8,9 @@ local funcs_file = arg[4]
if nvimsrcdir == '--help' then
print([[
Usage:
- lua geneval.lua src/nvim build/src/nvim/auto
+ lua gen_eval.lua src/nvim build/src/nvim/auto
-Will generate build/src/nvim/auto/funcs.generated.h with definition of functions
+Will generate build/src/nvim/auto/funcs.generated.h with definition of functions
static const array.
]])
os.exit(0)
@@ -23,8 +23,8 @@ local funcsfname = autodir .. '/funcs.generated.h'
local gperfpipe = io.open(funcsfname .. '.gperf', 'wb')
local funcs = require('eval').funcs
-local metadata = mpack.unpack(io.open(arg[3], 'rb'):read("*all"))
-for i,fun in ipairs(metadata) do
+local metadata = mpack.unpack(io.open(metadata_file, 'rb'):read("*all"))
+for _,fun in ipairs(metadata) do
if not fun.remote_only then
funcs[fun.name] = {
args=#fun.parameters,
@@ -53,14 +53,14 @@ VimLFuncDef;
]])
for name, def in pairs(funcs) do
- args = def.args or 0
+ local args = def.args or 0
if type(args) == 'number' then
args = {args, args}
elseif #args == 1 then
args[2] = 'MAX_FUNC_ARGS'
end
- func = def.func or ('f_' .. name)
- data = def.data or "NULL"
+ local func = def.func or ('f_' .. name)
+ local data = def.data or "NULL"
gperfpipe:write(('%s, %s, %s, &%s, (FunPtr)%s\n')
:format(name, args[1], args[2], func, data))
end
diff --git a/src/nvim/generators/gen_events.lua b/src/nvim/generators/gen_events.lua
index d03c787b2b..98c3254e7a 100644
--- a/src/nvim/generators/gen_events.lua
+++ b/src/nvim/generators/gen_events.lua
@@ -13,8 +13,8 @@ local auevents = require('auevents')
local events = auevents.events
local aliases = auevents.aliases
-enum_tgt = io.open(fileio_enum_file, 'w')
-names_tgt = io.open(names_file, 'w')
+local enum_tgt = io.open(fileio_enum_file, 'w')
+local names_tgt = io.open(names_file, 'w')
enum_tgt:write('typedef enum auto_event {')
names_tgt:write([[
@@ -42,8 +42,8 @@ enum_tgt:write('\n} event_T;\n')
names_tgt:write('\n};\n')
names_tgt:write('\nstatic AutoPat *first_autopat[NUM_EVENTS] = {\n ')
-line_len = 1
-for i = 1,((#events) - 1) do
+local line_len = 1
+for _ = 1,((#events) - 1) do
line_len = line_len + #(' NULL,')
if line_len > 80 then
names_tgt:write('\n ')
diff --git a/src/nvim/generators/gen_ex_cmds.lua b/src/nvim/generators/gen_ex_cmds.lua
index 7859d7c71a..075d8ba9cc 100644
--- a/src/nvim/generators/gen_ex_cmds.lua
+++ b/src/nvim/generators/gen_ex_cmds.lua
@@ -7,8 +7,8 @@ if nvimsrcdir == '--help' then
Usage:
lua genex_cmds.lua src/nvim build/include build/src/nvim/auto
-Will generate files build/include/ex_cmds_enum.generated.h with cmdidx_T
-enum and build/src/nvim/auto/ex_cmds_defs.generated.h with main Ex commands
+Will generate files build/include/ex_cmds_enum.generated.h with cmdidx_T
+enum and build/src/nvim/auto/ex_cmds_defs.generated.h with main Ex commands
definitions.
]])
os.exit(0)
@@ -23,10 +23,7 @@ local enumfile = io.open(enumfname, 'w')
local defsfile = io.open(defsfname, 'w')
local defs = require('ex_cmds')
-local lastchar = nil
-local i
-local cmd
local first = true
local byte_a = string.byte('a')
@@ -58,7 +55,7 @@ defsfile:write(string.format([[
static CommandDefinition cmdnames[%u] = {
]], #defs))
local cmds, cmdidxs1, cmdidxs2 = {}, {}, {}
-for i, cmd in ipairs(defs) do
+for _, cmd in ipairs(defs) do
local enumname = cmd.enum or ('CMD_' .. cmd.command)
local byte_cmd = cmd.command:sub(1, 1):byte()
if byte_a <= byte_cmd and byte_cmd <= byte_z then
diff --git a/src/nvim/generators/gen_options.lua b/src/nvim/generators/gen_options.lua
index fdc00d5dc0..a8cf496cb9 100644
--- a/src/nvim/generators/gen_options.lua
+++ b/src/nvim/generators/gen_options.lua
@@ -20,7 +20,7 @@ end
local options = require('options')
-cstr = options.cstr
+local cstr = options.cstr
local type_flags={
bool='P_BOOL',
@@ -79,6 +79,7 @@ local get_flags = function(o)
{'pri_mkrc'},
{'deny_in_modelines', 'P_NO_ML'},
{'deny_duplicates', 'P_NODUP'},
+ {'modelineexpr', 'P_MLE'},
}) do
local key_name = flag_desc[1]
local def_name = flag_desc[2] or ('P_' .. key_name:upper())
@@ -107,12 +108,12 @@ get_cond = function(c, base_string)
return cond_string
end
-value_dumpers = {
+local value_dumpers = {
['function']=function(v) return v() end,
string=cstr,
boolean=function(v) return v and 'true' or 'false' end,
number=function(v) return ('%iL'):format(v) end,
- ['nil']=function(v) return '0L' end,
+ ['nil']=function(_) return '0L' end,
}
local get_value = function(v)
diff --git a/src/nvim/generators/gen_unicode_tables.lua b/src/nvim/generators/gen_unicode_tables.lua
index 3130cecd82..aa96c97bc1 100644
--- a/src/nvim/generators/gen_unicode_tables.lua
+++ b/src/nvim/generators/gen_unicode_tables.lua
@@ -1,16 +1,16 @@
-- Script creates the following tables in unicode_tables.generated.h:
--
--- 1. doublewidth and ambiguous tables: sorted list of non-overlapping closed
--- intervals. Codepoints in these intervals have double (W or F) or ambiguous
+-- 1. doublewidth and ambiguous tables: sorted list of non-overlapping closed
+-- intervals. Codepoints in these intervals have double (W or F) or ambiguous
-- (A) east asian width respectively.
--- 2. combining table: same as the above, but characters inside are combining
+-- 2. combining table: same as the above, but characters inside are combining
-- characters (i.e. have general categories equal to Mn, Mc or Me).
--- 3. foldCase, toLower and toUpper tables used to convert characters to
--- folded/lower/upper variants. In these tables first two values are
--- character ranges: like in previous tables they are sorted and must be
--- non-overlapping. Third value means step inside the range: e.g. if it is
--- 2 then interval applies only to first, third, fifth, … character in range.
--- Fourth value is number that should be added to the codepoint to yield
+-- 3. foldCase, toLower and toUpper tables used to convert characters to
+-- folded/lower/upper variants. In these tables first two values are
+-- character ranges: like in previous tables they are sorted and must be
+-- non-overlapping. Third value means step inside the range: e.g. if it is
+-- 2 then interval applies only to first, third, fifth, … character in range.
+-- Fourth value is number that should be added to the codepoint to yield
-- folded/lower/upper codepoint.
-- 4. emoji_width and emoji_all tables: sorted lists of non-overlapping closed
-- intervals of Emoji characters. emoji_width contains all the characters
@@ -38,7 +38,7 @@ local split_on_semicolons = function(s)
local ret = {}
local idx = 1
while idx <= #s + 1 do
- item = s:match('^[^;]*', idx)
+ local item = s:match('^[^;]*', idx)
idx = idx + #item + 1
if idx <= #s + 1 then
assert(s:sub(idx - 1, idx - 1) == ';')
@@ -208,7 +208,7 @@ local build_width_table = function(ut_fp, dataprops, widthprops, widths,
-- But use all chars from a range.
local dp = dataprops[dataidx]
if (n_last > n) or (not (({Mn=true, Mc=true, Me=true})[dp[3]])) then
- if start >= 0 and end_ + 1 == n then
+ if start >= 0 and end_ + 1 == n then -- luacheck: ignore 542
-- Continue with the same range.
else
if start >= 0 then
@@ -235,6 +235,8 @@ local build_emoji_table = function(ut_fp, emojiprops, doublewidth, ambiwidth)
for _, p in ipairs(emojiprops) do
if p[2]:match('Emoji%s+#') then
local rng_start, rng_end = p[1]:find('%.%.')
+ local n
+ local n_last
if rng_start then
n = tonumber(p[1]:sub(1, rng_start - 1), 16)
n_last = tonumber(p[1]:sub(rng_end + 1), 16)
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
index 7e4a0e1321..03f64c2019 100644
--- a/src/nvim/getchar.c
+++ b/src/nvim/getchar.c
@@ -15,6 +15,7 @@
#include <string.h>
#include <inttypes.h>
+#include "nvim/assert.h"
#include "nvim/vim.h"
#include "nvim/ascii.h"
#include "nvim/getchar.h"
@@ -82,10 +83,10 @@ static buffheader_T old_redobuff = { { NULL, { NUL } }, NULL, 0, 0 };
static buffheader_T recordbuff = { { NULL, { NUL } }, NULL, 0, 0 };
// First read ahead buffer. Used for translated commands.
-static buffheader_T readbuf1 = {{NULL, {NUL}}, NULL, 0, 0};
+static buffheader_T readbuf1 = { { NULL, { NUL } }, NULL, 0, 0 };
// Second read ahead buffer. Used for redo.
-static buffheader_T readbuf2 = {{NULL, {NUL}}, NULL, 0, 0};
+static buffheader_T readbuf2 = { { NULL, { NUL } }, NULL, 0, 0 };
static int typeahead_char = 0; /* typeahead char that's not flushed */
@@ -181,18 +182,22 @@ static char_u *get_buffcont(buffheader_T *buffer,
size_t count = 0;
char_u *p = NULL;
char_u *p2;
- char_u *str;
- /* compute the total length of the string */
- for (buffblock_T *bp = buffer->bh_first.b_next; bp != NULL; bp = bp->b_next)
+ // compute the total length of the string
+ for (const buffblock_T *bp = buffer->bh_first.b_next;
+ bp != NULL; bp = bp->b_next) {
count += STRLEN(bp->b_str);
+ }
if (count || dozero) {
p = xmalloc(count + 1);
p2 = p;
- for (buffblock_T *bp = buffer->bh_first.b_next; bp != NULL; bp = bp->b_next)
- for (str = bp->b_str; *str; )
+ for (const buffblock_T *bp = buffer->bh_first.b_next;
+ bp != NULL; bp = bp->b_next) {
+ for (const char_u *str = bp->b_str; *str;) {
*p2++ = *str++;
+ }
+ }
*p2 = NUL;
}
return p;
@@ -356,12 +361,12 @@ static int read_readbuffers(int advance)
static int read_readbuf(buffheader_T *buf, int advance)
{
char_u c;
- buffblock_T *curr;
- if (buf->bh_first.b_next == NULL) /* buffer is empty */
+ if (buf->bh_first.b_next == NULL) { // buffer is empty
return NUL;
+ }
- curr = buf->bh_first.b_next;
+ buffblock_T *const curr = buf->bh_first.b_next;
c = curr->b_str[buf->bh_index];
if (advance) {
@@ -668,12 +673,10 @@ static int read_redo(bool init, bool old_redo)
int i;
if (init) {
- if (old_redo)
- bp = old_redobuff.bh_first.b_next;
- else
- bp = redobuff.bh_first.b_next;
- if (bp == NULL)
+ bp = old_redo ? old_redobuff.bh_first.b_next : redobuff.bh_first.b_next;
+ if (bp == NULL) {
return FAIL;
+ }
p = bp->b_str;
return OK;
}
@@ -896,18 +899,19 @@ int ins_typebuf(char_u *str, int noremap, int offset, int nottyped, bool silent)
s2 = xmalloc((size_t)newlen);
typebuf.tb_buflen = newlen;
- /* copy the old chars, before the insertion point */
- memmove(s1 + newoff, typebuf.tb_buf + typebuf.tb_off,
- (size_t)offset);
- /* copy the new chars */
+ // copy the old chars, before the insertion point
+ memmove(s1 + newoff, typebuf.tb_buf + typebuf.tb_off, (size_t)offset);
+ // copy the new chars
memmove(s1 + newoff + offset, str, (size_t)addlen);
- /* copy the old chars, after the insertion point, including the NUL at
- * the end */
+ // copy the old chars, after the insertion point, including the NUL at
+ // the end
+ int bytes = typebuf.tb_len - offset + 1;
+ assert(bytes > 0);
memmove(s1 + newoff + offset + addlen,
- typebuf.tb_buf + typebuf.tb_off + offset,
- (size_t)(typebuf.tb_len - offset + 1));
- if (typebuf.tb_buf != typebuf_init)
+ typebuf.tb_buf + typebuf.tb_off + offset, (size_t)bytes);
+ if (typebuf.tb_buf != typebuf_init) {
xfree(typebuf.tb_buf);
+ }
typebuf.tb_buf = s1;
memmove(s2 + newoff, typebuf.tb_noremap + typebuf.tb_off,
@@ -1053,11 +1057,12 @@ void del_typebuf(int len, int offset)
typebuf.tb_noremap + typebuf.tb_off, (size_t)offset);
typebuf.tb_off = MAXMAPLEN;
}
- /* adjust typebuf.tb_buf (include the NUL at the end) */
+ // adjust typebuf.tb_buf (include the NUL at the end)
+ int bytes = typebuf.tb_len - offset + 1;
+ assert(bytes > 0);
memmove(typebuf.tb_buf + typebuf.tb_off + offset,
- typebuf.tb_buf + i + len,
- (size_t)(typebuf.tb_len - offset + 1));
- /* adjust typebuf.tb_noremap[] */
+ typebuf.tb_buf + i + len, (size_t)bytes);
+ // adjust typebuf.tb_noremap[]
memmove(typebuf.tb_noremap + typebuf.tb_off + offset,
typebuf.tb_noremap + i + len,
(size_t)(typebuf.tb_len - offset));
@@ -1244,9 +1249,17 @@ openscript (
EMSG(_(e_nesting));
return;
}
- if (ignore_script)
- /* Not reading from script, also don't open one. Warning message? */
+
+ // Disallow sourcing a file in the sandbox, the commands would be executed
+ // later, possibly outside of the sandbox.
+ if (check_secure()) {
+ return;
+ }
+
+ if (ignore_script) {
+ // Not reading from script, also don't open one. Warning message?
return;
+ }
if (scriptin[curscript] != NULL) /* already reading script */
++curscript;
@@ -1761,7 +1774,7 @@ static int vgetorpeek(int advance)
&& !(State == HITRETURN && (c1 == CAR || c1 == ' '))
&& State != ASKMORE
&& State != CONFIRM
- && !((ctrl_x_mode != 0 && vim_is_ctrl_x_key(c1))
+ && !((ctrl_x_mode_not_default() && vim_is_ctrl_x_key(c1))
|| ((compl_cont_status & CONT_LOCAL)
&& (c1 == Ctrl_N || c1 == Ctrl_P)))
) {
@@ -1908,7 +1921,7 @@ static int vgetorpeek(int advance)
set_option_value("paste", !p_paste, NULL, 0);
if (!(State & INSERT)) {
msg_col = 0;
- msg_row = (int)Rows - 1;
+ msg_row = Rows - 1;
msg_clr_eos(); // clear ruler
}
status_redraw_all();
@@ -1927,10 +1940,12 @@ static int vgetorpeek(int advance)
}
if ((mp == NULL || max_mlen >= mp_match_len)
- && keylen != KEYLEN_PART_MAP) {
+ && keylen != KEYLEN_PART_MAP
+ && !(keylen == KEYLEN_PART_KEY && c1 == ui_toggle[0])) {
// No matching mapping found or found a non-matching mapping that
// matches at least what the matching mapping matched
keylen = 0;
+ (void)keylen; // suppress clang/dead assignment
// If there was no mapping, use the character from the typeahead
// buffer right here. Otherwise, use the mapping (loop around).
if (mp == NULL) {
@@ -2252,14 +2267,13 @@ static int vgetorpeek(int advance)
curwin->w_wrow = old_wrow;
}
- /* this looks nice when typing a dead character map */
- if ((State & CMDLINE)
- && cmdline_star == 0
- && ptr2cells(typebuf.tb_buf + typebuf.tb_off
- + typebuf.tb_len - 1) == 1) {
- putcmdline(typebuf.tb_buf[typebuf.tb_off
- + typebuf.tb_len - 1], FALSE);
- c1 = 1;
+ // this looks nice when typing a dead character map
+ if ((State & CMDLINE) && cmdline_star == 0) {
+ char_u *p = typebuf.tb_buf + typebuf.tb_off + typebuf.tb_len - 1;
+ if (ptr2cells(p) == 1 && *p < 128) {
+ putcmdline((char)(*p), false);
+ c1 = 1;
+ }
}
}
@@ -2342,6 +2356,17 @@ static int vgetorpeek(int advance)
}
}
+ if (timedout && c == ESC) {
+ char_u nop_buf[3];
+
+ // When recording there will be no timeout. Add a <Nop> after the ESC
+ // to avoid that it forms a key code with following characters.
+ nop_buf[0] = K_SPECIAL;
+ nop_buf[1] = KS_EXTRA;
+ nop_buf[2] = KE_NOP;
+ gotchars(nop_buf, 3);
+ }
+
--vgetc_busy;
return c;
@@ -3264,29 +3289,36 @@ char *map_mode_to_chars(int mode)
ga_init(&mapmode, 1, 7);
- if ((mode & (INSERT + CMDLINE)) == INSERT + CMDLINE)
- ga_append(&mapmode, '!'); /* :map! */
- else if (mode & INSERT)
- ga_append(&mapmode, 'i'); /* :imap */
- else if (mode & LANGMAP)
- ga_append(&mapmode, 'l'); /* :lmap */
- else if (mode & CMDLINE)
- ga_append(&mapmode, 'c'); /* :cmap */
- else if ((mode & (NORMAL + VISUAL + SELECTMODE + OP_PENDING))
- == NORMAL + VISUAL + SELECTMODE + OP_PENDING)
- ga_append(&mapmode, ' '); /* :map */
- else {
- if (mode & NORMAL)
- ga_append(&mapmode, 'n'); /* :nmap */
- if (mode & OP_PENDING)
- ga_append(&mapmode, 'o'); /* :omap */
- if ((mode & (VISUAL + SELECTMODE)) == VISUAL + SELECTMODE)
- ga_append(&mapmode, 'v'); /* :vmap */
- else {
- if (mode & VISUAL)
- ga_append(&mapmode, 'x'); /* :xmap */
- if (mode & SELECTMODE)
- ga_append(&mapmode, 's'); /* :smap */
+ if ((mode & (INSERT + CMDLINE)) == INSERT + CMDLINE) {
+ ga_append(&mapmode, '!'); // :map!
+ } else if (mode & INSERT) {
+ ga_append(&mapmode, 'i'); // :imap
+ } else if (mode & LANGMAP) {
+ ga_append(&mapmode, 'l'); // :lmap
+ } else if (mode & CMDLINE) {
+ ga_append(&mapmode, 'c'); // :cmap
+ } else if ((mode & (NORMAL + VISUAL + SELECTMODE + OP_PENDING))
+ == NORMAL + VISUAL + SELECTMODE + OP_PENDING) {
+ ga_append(&mapmode, ' '); // :map
+ } else {
+ if (mode & NORMAL) {
+ ga_append(&mapmode, 'n'); // :nmap
+ }
+ if (mode & OP_PENDING) {
+ ga_append(&mapmode, 'o'); // :omap
+ }
+ if (mode & TERM_FOCUS) {
+ ga_append(&mapmode, 't'); // :tmap
+ }
+ if ((mode & (VISUAL + SELECTMODE)) == VISUAL + SELECTMODE) {
+ ga_append(&mapmode, 'v'); // :vmap
+ } else {
+ if (mode & VISUAL) {
+ ga_append(&mapmode, 'x'); // :xmap
+ }
+ if (mode & SELECTMODE) {
+ ga_append(&mapmode, 's'); // :smap
+ }
}
}
@@ -3417,7 +3449,7 @@ int map_to_exists_mode(const char *const rhs, const int mode, const bool abbr)
{
mapblock_T *mp;
int hash;
- bool expand_buffer = false;
+ bool exp_buffer = false;
validate_maphash();
@@ -3428,12 +3460,12 @@ int map_to_exists_mode(const char *const rhs, const int mode, const bool abbr)
if (hash > 0) { // There is only one abbr list.
break;
}
- if (expand_buffer) {
+ if (exp_buffer) {
mp = curbuf->b_first_abbr;
} else {
mp = first_abbr;
}
- } else if (expand_buffer) {
+ } else if (exp_buffer) {
mp = curbuf->b_maphash[hash];
} else {
mp = maphash[hash];
@@ -3445,10 +3477,10 @@ int map_to_exists_mode(const char *const rhs, const int mode, const bool abbr)
}
}
}
- if (expand_buffer) {
+ if (exp_buffer) {
break;
}
- expand_buffer = true;
+ exp_buffer = true;
}
return false;
@@ -3527,11 +3559,9 @@ set_context_in_map_cmd (
return NULL;
}
-/*
- * Find all mapping/abbreviation names that match regexp 'prog'.
- * For command line expansion of ":[un]map" and ":[un]abbrev" in all modes.
- * Return OK if matches found, FAIL otherwise.
- */
+// Find all mapping/abbreviation names that match regexp "regmatch".
+// For command line expansion of ":[un]map" and ":[un]abbrev" in all modes.
+// Return OK if matches found, FAIL otherwise.
int ExpandMappings(regmatch_T *regmatch, int *num_file, char_u ***file)
{
mapblock_T *mp;
@@ -3591,7 +3621,7 @@ int ExpandMappings(regmatch_T *regmatch, int *num_file, char_u ***file)
mp = maphash[hash];
for (; mp; mp = mp->m_next) {
if (mp->m_mode & expand_mapmodes) {
- p = translate_mapping(mp->m_keys, true, CPO_TO_CPO_FLAGS);
+ p = translate_mapping(mp->m_keys, CPO_TO_CPO_FLAGS);
if (p != NULL && vim_regexec(regmatch, p, (colnr_T)0)) {
if (round == 1)
++count;
@@ -3642,7 +3672,9 @@ int ExpandMappings(regmatch_T *regmatch, int *num_file, char_u ***file)
/*
* Check for an abbreviation.
- * Cursor is at ptr[col]. When inserting, mincol is where insert started.
+ * Cursor is at ptr[col].
+ * When inserting, mincol is where insert started.
+ * For the command line, mincol is what is to be skipped over.
* "c" is the character typed before check_abbr was called. It may have
* ABBR_OFF added to avoid prepending a CTRL-V to it.
*
@@ -3921,10 +3953,10 @@ void vim_unescape_csi(char_u *p)
* Write map commands for the current mappings to an .exrc file.
* Return FAIL on error, OK otherwise.
*/
-int
-makemap (
+int
+makemap(
FILE *fd,
- buf_T *buf /* buffer for local mappings or NULL */
+ buf_T *buf // buffer for local mappings or NULL
)
{
mapblock_T *mp;
@@ -3937,50 +3969,56 @@ makemap (
validate_maphash();
- /*
- * Do the loop twice: Once for mappings, once for abbreviations.
- * Then loop over all map hash lists.
- */
- for (abbr = 0; abbr < 2; ++abbr)
- for (hash = 0; hash < 256; ++hash) {
+ // Do the loop twice: Once for mappings, once for abbreviations.
+ // Then loop over all map hash lists.
+ for (abbr = 0; abbr < 2; abbr++) {
+ for (hash = 0; hash < 256; hash++) {
if (abbr) {
- if (hash > 0) /* there is only one abbr list */
+ if (hash > 0) { // there is only one abbr list
break;
- if (buf != NULL)
+ }
+ if (buf != NULL) {
mp = buf->b_first_abbr;
- else
+ } else {
mp = first_abbr;
+ }
} else {
- if (buf != NULL)
+ if (buf != NULL) {
mp = buf->b_maphash[hash];
- else
+ } else {
mp = maphash[hash];
+ }
}
for (; mp; mp = mp->m_next) {
- /* skip script-local mappings */
- if (mp->m_noremap == REMAP_SCRIPT)
+ // skip script-local mappings
+ if (mp->m_noremap == REMAP_SCRIPT) {
continue;
+ }
- /* skip mappings that contain a <SNR> (script-local thing),
- * they probably don't work when loaded again */
- for (p = mp->m_str; *p != NUL; ++p)
+ // skip mappings that contain a <SNR> (script-local thing),
+ // they probably don't work when loaded again
+ for (p = mp->m_str; *p != NUL; p++) {
if (p[0] == K_SPECIAL && p[1] == KS_EXTRA
- && p[2] == (int)KE_SNR)
+ && p[2] == (int)KE_SNR) {
break;
- if (*p != NUL)
+ }
+ }
+ if (*p != NUL) {
continue;
+ }
- /* It's possible to create a mapping and then ":unmap" certain
- * modes. We recreate this here by mapping the individual
- * modes, which requires up to three of them. */
+ // It's possible to create a mapping and then ":unmap" certain
+ // modes. We recreate this here by mapping the individual
+ // modes, which requires up to three of them.
c1 = NUL;
c2 = NUL;
c3 = NUL;
- if (abbr)
+ if (abbr) {
cmd = "abbr";
- else
+ } else {
cmd = "map";
+ }
switch (mp->m_mode) {
case NORMAL + VISUAL + SELECTMODE + OP_PENDING:
break;
@@ -4038,8 +4076,9 @@ makemap (
c2 = 'o';
break;
case CMDLINE + INSERT:
- if (!abbr)
+ if (!abbr) {
cmd = "map!";
+ }
break;
case CMDLINE:
c1 = 'c';
@@ -4057,9 +4096,10 @@ makemap (
IEMSG(_("E228: makemap: Illegal mode"));
return FAIL;
}
- do { /* do this twice if c2 is set, 3 times with c3 */
- /* When outputting <> form, need to make sure that 'cpo'
- * is set to the Vim default. */
+ do {
+ // do this twice if c2 is set, 3 times with c3 */
+ // When outputting <> form, need to make sure that 'cpo'
+ // is set to the Vim default.
if (!did_cpo) {
if (*mp->m_str == NUL) { // Will use <Nop>.
did_cpo = true;
@@ -4074,63 +4114,69 @@ makemap (
if (fprintf(fd, "let s:cpo_save=&cpo") < 0
|| put_eol(fd) < 0
|| fprintf(fd, "set cpo&vim") < 0
- || put_eol(fd) < 0)
+ || put_eol(fd) < 0) {
return FAIL;
+ }
}
}
- if (c1 && putc(c1, fd) < 0)
+ if (c1 && putc(c1, fd) < 0) {
return FAIL;
- if (mp->m_noremap != REMAP_YES && fprintf(fd, "nore") < 0)
- return FAIL;
- if (fputs(cmd, fd) < 0)
+ }
+ if (mp->m_noremap != REMAP_YES && fprintf(fd, "nore") < 0) {
return FAIL;
- if (buf != NULL && fputs(" <buffer>", fd) < 0)
+ }
+ if (fputs(cmd, fd) < 0) {
return FAIL;
- if (mp->m_nowait && fputs(" <nowait>", fd) < 0)
+ }
+ if (buf != NULL && fputs(" <buffer>", fd) < 0) {
return FAIL;
- if (mp->m_silent && fputs(" <silent>", fd) < 0)
+ }
+ if (mp->m_nowait && fputs(" <nowait>", fd) < 0) {
return FAIL;
- if (mp->m_noremap == REMAP_SCRIPT
- && fputs("<script>", fd) < 0)
+ }
+ if (mp->m_silent && fputs(" <silent>", fd) < 0) {
return FAIL;
- if (mp->m_expr && fputs(" <expr>", fd) < 0)
+ }
+ if (mp->m_expr && fputs(" <expr>", fd) < 0) {
return FAIL;
+ }
- if ( putc(' ', fd) < 0
- || put_escstr(fd, mp->m_keys, 0) == FAIL
- || putc(' ', fd) < 0
- || put_escstr(fd, mp->m_str, 1) == FAIL
- || put_eol(fd) < 0)
+ if (putc(' ', fd) < 0
+ || put_escstr(fd, mp->m_keys, 0) == FAIL
+ || putc(' ', fd) < 0
+ || put_escstr(fd, mp->m_str, 1) == FAIL
+ || put_eol(fd) < 0) {
return FAIL;
+ }
c1 = c2;
c2 = c3;
c3 = NUL;
} while (c1 != NUL);
}
}
-
- if (did_cpo)
+ }
+ if (did_cpo) {
if (fprintf(fd, "let &cpo=s:cpo_save") < 0
|| put_eol(fd) < 0
|| fprintf(fd, "unlet s:cpo_save") < 0
- || put_eol(fd) < 0)
+ || put_eol(fd) < 0) {
return FAIL;
+ }
+ }
return OK;
}
-/*
- * write escape string to file
- * "what": 0 for :map lhs, 1 for :map rhs, 2 for :set
- *
- * return FAIL for failure, OK otherwise
- */
+// write escape string to file
+// "what": 0 for :map lhs, 1 for :map rhs, 2 for :set
+//
+// return FAIL for failure, OK otherwise
int put_escstr(FILE *fd, char_u *strstart, int what)
{
char_u *str = strstart;
int c;
int modifiers;
- /* :map xx <Nop> */
+ // :map xx <Nop>
if (*str == NUL && what == 1) {
if (fprintf(fd, "<Nop>") < 0)
return FAIL;
@@ -4299,16 +4345,15 @@ void add_map(char_u *map, int mode)
// corresponding external one recognized by :map/:abbrev commands.
//
// This function is called when expanding mappings/abbreviations on the
-// command-line, and for building the "Ambiguous mapping..." error message.
+// command-line.
//
-// It uses a growarray to build the translation string since the
-// latter can be wider than the original description. The caller has to
-// free the string afterwards.
+// It uses a growarray to build the translation string since the latter can be
+// wider than the original description. The caller has to free the string
+// afterwards.
//
// Returns NULL when there is a problem.
static char_u * translate_mapping (
char_u *str,
- int expmap, // True when expanding mappings on command-line
int cpo_flags // Value of various flags present in &cpo
)
{
@@ -4326,12 +4371,8 @@ static char_u * translate_mapping (
modifiers = *++str;
c = *++str;
}
-
+
if (c == K_SPECIAL && str[1] != NUL && str[2] != NUL) {
- if (expmap) {
- ga_clear(&ga);
- return NULL;
- }
c = TO_SPECIAL(str[1], str[2]);
if (c == K_ZERO) {
// display <Nul> as ^@
@@ -4340,10 +4381,6 @@ static char_u * translate_mapping (
str += 2;
}
if (IS_SPECIAL(c) || modifiers) { // special key
- if (expmap) {
- ga_clear(&ga);
- return NULL;
- }
ga_concat(&ga, get_special_key_name(c, modifiers));
continue; /* for (str) */
}
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index 6d602d2712..3bdbff79b4 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -88,18 +88,15 @@ EXTERN struct nvim_stats_s {
#define NO_BUFFERS 1 // not all buffers loaded yet
// 0 not starting anymore
-/*
- * Number of Rows and Columns in the screen.
- * Must be long to be able to use them as options in option.c.
- * Note: Use default_grid.Rows and default_grid.Columns to access items in
- * default_grid.chars[]. They may have different values when the screen
- * wasn't (re)allocated yet after setting Rows or Columns (e.g., when starting
- * up).
- */
+// Number of Rows and Columns in the screen.
+// Note: Use default_grid.Rows and default_grid.Columns to access items in
+// default_grid.chars[]. They may have different values when the screen
+// wasn't (re)allocated yet after setting Rows or Columns (e.g., when starting
+// up).
#define DFLT_COLS 80 // default value for 'columns'
#define DFLT_ROWS 24 // default value for 'lines'
-EXTERN long Rows INIT(= DFLT_ROWS); // nr of rows in the screen
-EXTERN long Columns INIT(= DFLT_COLS); // nr of columns in the screen
+EXTERN int Rows INIT(= DFLT_ROWS); // nr of rows in the screen
+EXTERN int Columns INIT(= DFLT_COLS); // nr of columns in the screen
// We use 64-bit file functions here, if available. E.g. ftello() returns
// off_t instead of long, which helps if long is 32 bit and off_t is 64 bit.
@@ -435,10 +432,11 @@ 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)
-/*
- * When using this macro "break" only breaks out of the inner loop. Use "goto"
- * to break out of the tabpage loop.
- */
+# define FOR_ALL_FRAMES(frp, first_frame) \
+ for (frp = first_frame; frp != NULL; frp = frp->fr_next) // NOLINT
+
+// When using this macro "break" only breaks out of the inner loop. Use "goto"
+// to break out of the tabpage loop.
# define FOR_ALL_TAB_WINDOWS(tp, wp) \
FOR_ALL_TABS(tp) \
FOR_ALL_WINDOWS_IN_TAB(wp, tp)
@@ -510,7 +508,7 @@ EXTERN int sc_col; /* column for shown command */
// First NO_SCREEN, then NO_BUFFERS, then 0 when startup finished.
EXTERN int starting INIT(= NO_SCREEN);
// true when planning to exit. Might keep running if there is a changed buffer.
-EXTERN int exiting INIT(= false);
+EXTERN bool exiting INIT(= false);
// is stdin a terminal?
EXTERN int stdin_isatty INIT(= true);
// is stdout a terminal?
@@ -629,6 +627,8 @@ EXTERN pos_T Insstart_orig;
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'.
@@ -655,16 +655,6 @@ EXTERN int vr_lines_changed INIT(= 0); /* #Lines changed by "gR" so far */
/// Encoding used when 'fencs' is set to "default"
EXTERN char_u *fenc_default INIT(= NULL);
-# if defined(USE_ICONV) && defined(DYNAMIC_ICONV)
-/* Pointers to functions and variables to be loaded at runtime */
-EXTERN size_t (*iconv)(iconv_t cd, const char **inbuf, size_t *inbytesleft,
- char **outbuf, size_t *outbytesleft);
-EXTERN iconv_t (*iconv_open)(const char *tocode, const char *fromcode);
-EXTERN int (*iconv_close)(iconv_t cd);
-EXTERN int (*iconvctl)(iconv_t cd, int request, void *argument);
-EXTERN int* (*iconv_errno)(void);
-# endif
-
/// "State" is the main state of Vim.
/// There are other variables that modify the state:
/// Visual_mode: When State is NORMAL or INSERT.
@@ -700,11 +690,10 @@ EXTERN int arrow_used; /* Normally FALSE, set to TRUE after
* to call u_sync() */
EXTERN int 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 ctrl_x_mode INIT(= 0); /* Which Ctrl-X mode are we in? */
+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 */
@@ -757,9 +746,11 @@ EXTERN bool KeyTyped; // true if user typed current char
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 */
-EXTERN int skip_redraw INIT(= FALSE); /* skip redraw once */
-EXTERN int do_redraw INIT(= FALSE); /* extra redraw once */
+EXTERN int must_redraw INIT(= 0); // type of redraw necessary
+EXTERN bool skip_redraw INIT(= false); // skip redraw once
+EXTERN bool do_redraw INIT(= false); // extra redraw once
+EXTERN bool must_redraw_pum INIT(= false); // redraw pum. NB: must_redraw
+ // should also be set.
EXTERN int need_highlight_changed INIT(= true);
@@ -933,6 +924,9 @@ EXTERN char_u e_interr[] INIT(= N_("Interrupted"));
EXTERN char_u e_invaddr[] INIT(= N_("E14: Invalid address"));
EXTERN char_u e_invarg[] INIT(= N_("E474: Invalid argument"));
EXTERN char_u e_invarg2[] INIT(= N_("E475: Invalid argument: %s"));
+EXTERN char_u e_invargval[] INIT(= N_("E475: Invalid value for argument %s"));
+EXTERN char_u e_invargNval[] INIT(= N_(
+ "E475: Invalid value for argument %s: %s"));
EXTERN char_u e_duparg2[] INIT(= N_("E983: Duplicate argument: %s"));
EXTERN char_u e_invexpr2[] INIT(= N_("E15: Invalid expression: %s"));
EXTERN char_u e_invrange[] INIT(= N_("E16: Invalid range"));
@@ -980,9 +974,6 @@ EXTERN char_u e_notmp[] INIT(= N_("E483: Can't get temp file name"));
EXTERN char_u e_notopen[] INIT(= N_("E484: Can't open file %s"));
EXTERN char_u e_notopen_2[] INIT(= N_("E484: Can't open file %s: %s"));
EXTERN char_u e_notread[] INIT(= N_("E485: Can't read file %s"));
-EXTERN char_u e_nowrtmsg[] INIT(= N_(
- "E37: No write since last change (add ! to override)"));
-EXTERN char_u e_nowrtmsg_nobang[] INIT(= N_("E37: No write since last change"));
EXTERN char_u e_null[] INIT(= N_("E38: Null argument"));
EXTERN char_u e_number_exp[] INIT(= N_("E39: Number expected"));
EXTERN char_u e_openerrf[] INIT(= N_("E40: Can't open errorfile %s"));
@@ -1055,6 +1046,9 @@ EXTERN char_u e_cmdmap_key[] INIT(=N_(
EXTERN char_u e_api_error[] INIT(=N_(
"E5555: API call: %s"));
+EXTERN char e_luv_api_disabled[] INIT(=N_(
+ "E5560: %s must not be called in a lua loop callback"));
+
EXTERN char_u e_floatonly[] INIT(=N_(
"E5601: Cannot close window, only floating window would remain"));
EXTERN char_u e_floatexchange[] INIT(=N_(
diff --git a/src/nvim/grid_defs.h b/src/nvim/grid_defs.h
index 38fc513baa..e4021c033b 100644
--- a/src/nvim/grid_defs.h
+++ b/src/nvim/grid_defs.h
@@ -54,6 +54,9 @@ typedef struct {
int row_offset;
int col_offset;
+ // whether the compositor should blend the grid with the background grid
+ bool blending;
+
// state owned by the compositor.
int comp_row;
int comp_col;
@@ -61,7 +64,7 @@ typedef struct {
bool comp_disabled;
} ScreenGrid;
-#define SCREEN_GRID_INIT { 0, NULL, NULL, NULL, NULL, 0, 0, false, 0, 0, 0, \
- 0, 0, false }
+#define SCREEN_GRID_INIT { 0, NULL, NULL, NULL, NULL, 0, 0, false, 0, 0, \
+ false, 0, 0, 0, false }
#endif // NVIM_GRID_DEFS_H
diff --git a/src/nvim/hardcopy.c b/src/nvim/hardcopy.c
index bf2ac35554..3e0e438434 100644
--- a/src/nvim/hardcopy.c
+++ b/src/nvim/hardcopy.c
@@ -582,9 +582,9 @@ static void prt_header(prt_settings_T *const psettings, const int pagenum,
*/
static void prt_message(char_u *s)
{
- grid_fill(&default_grid, (int)Rows - 1, (int)Rows, 0, (int)Columns, ' ', ' ',
- 0);
- grid_puts(&default_grid, s, (int)Rows - 1, 0, HL_ATTR(HLF_R));
+ // TODO(bfredl): delete this
+ grid_fill(&default_grid, Rows - 1, Rows, 0, Columns, ' ', ' ', 0);
+ grid_puts(&default_grid, s, Rows - 1, 0, HL_ATTR(HLF_R));
ui_flush();
}
@@ -1670,7 +1670,7 @@ static int prt_open_resource(struct prt_ps_resource_S *resource)
FILE *fd_resource;
struct prt_dsc_line_S dsc_line;
- fd_resource = mch_fopen((char *)resource->filename, READBIN);
+ fd_resource = os_fopen((char *)resource->filename, READBIN);
if (fd_resource == NULL) {
EMSG2(_("E624: Can't open file \"%s\""), resource->filename);
return FALSE;
@@ -2343,11 +2343,11 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit)
EMSG(_(e_notmp));
return FAIL;
}
- prt_ps_fd = mch_fopen((char *)prt_ps_file_name, WRITEBIN);
+ prt_ps_fd = os_fopen((char *)prt_ps_file_name, WRITEBIN);
} else {
p = expand_env_save(psettings->outfile);
if (p != NULL) {
- prt_ps_fd = mch_fopen((char *)p, WRITEBIN);
+ prt_ps_fd = os_fopen((char *)p, WRITEBIN);
xfree(p);
}
}
@@ -2382,7 +2382,7 @@ static int prt_add_resource(struct prt_ps_resource_S *resource)
char_u resource_buffer[512];
size_t bytes_read;
- fd_resource = mch_fopen((char *)resource->filename, READBIN);
+ fd_resource = os_fopen((char *)resource->filename, READBIN);
if (fd_resource == NULL) {
EMSG2(_("E456: Can't open file \"%s\""), resource->filename);
return FALSE;
diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c
index 3ba02be32d..f11880cb2b 100644
--- a/src/nvim/highlight.c
+++ b/src/nvim/highlight.c
@@ -7,6 +7,7 @@
#include "nvim/highlight.h"
#include "nvim/highlight_defs.h"
#include "nvim/map.h"
+#include "nvim/popupmnu.h"
#include "nvim/screen.h"
#include "nvim/syntax.h"
#include "nvim/ui.h"
@@ -105,14 +106,19 @@ static int get_attr_entry(HlEntry entry)
/// When a UI connects, we need to send it the table of highlights used so far.
void ui_send_all_hls(UI *ui)
{
- if (!ui->hl_attr_define) {
- return;
+ if (ui->hl_attr_define) {
+ for (size_t i = 1; i < kv_size(attr_entries); i++) {
+ Array inspect = hl_inspect((int)i);
+ ui->hl_attr_define(ui, (Integer)i, kv_A(attr_entries, i).attr,
+ kv_A(attr_entries, i).attr, inspect);
+ api_free_array(inspect);
+ }
}
- for (size_t i = 1; i < kv_size(attr_entries); i++) {
- Array inspect = hl_inspect((int)i);
- ui->hl_attr_define(ui, (Integer)i, kv_A(attr_entries, i).attr,
- kv_A(attr_entries, i).attr, inspect);
- api_free_array(inspect);
+ if (ui->hl_group_set) {
+ for (size_t hlf = 0; hlf < HLF_COUNT; hlf++) {
+ ui->hl_group_set(ui, cstr_as_string((char *)hlf_names[hlf]),
+ highlight_attr[hlf]);
+ }
}
}
@@ -140,11 +146,23 @@ int hl_get_ui_attr(int idx, int final_id, bool optional)
HlAttrs attrs = HLATTRS_INIT;
bool available = false;
- int syn_attr = syn_id2attr(final_id);
- if (syn_attr != 0) {
- attrs = syn_attr2entry(syn_attr);
- available = true;
+ if (final_id > 0) {
+ int syn_attr = syn_id2attr(final_id);
+ if (syn_attr != 0) {
+ attrs = syn_attr2entry(syn_attr);
+ available = true;
+ }
+ }
+
+ if (HLF_PNI <= idx && idx <= HLF_PST) {
+ if (attrs.hl_blend == -1 && p_pb > 0) {
+ attrs.hl_blend = (int)p_pb;
+ }
+ if (pum_drawn()) {
+ must_redraw_pum = true;
+ }
}
+
if (optional && !available) {
return 0;
}
@@ -159,28 +177,42 @@ void update_window_hl(win_T *wp, bool invalid)
}
wp->w_hl_needs_update = false;
+ // If a floating window is blending it always have a named
+ // wp->w_hl_attr_normal group. HL_ATTR(HLF_NFLOAT) is always named.
+ bool has_blend = wp->w_floating && wp->w_p_winbl != 0;
+
// determine window specific background set in 'winhighlight'
bool float_win = wp->w_floating && !wp->w_float_config.external;
- if (wp != curwin && wp->w_hl_ids[HLF_INACTIVE] > 0) {
+ if (wp != curwin && wp->w_hl_ids[HLF_INACTIVE] != 0) {
wp->w_hl_attr_normal = hl_get_ui_attr(HLF_INACTIVE,
- wp->w_hl_ids[HLF_INACTIVE], true);
- } else if (float_win && wp->w_hl_ids[HLF_NFLOAT] > 0) {
+ wp->w_hl_ids[HLF_INACTIVE],
+ !has_blend);
+ } else if (float_win && wp->w_hl_ids[HLF_NFLOAT] != 0) {
wp->w_hl_attr_normal = hl_get_ui_attr(HLF_NFLOAT,
- wp->w_hl_ids[HLF_NFLOAT], true);
- } else if (wp->w_hl_id_normal > 0) {
- wp->w_hl_attr_normal = hl_get_ui_attr(-1, wp->w_hl_id_normal, true);
+ wp->w_hl_ids[HLF_NFLOAT], !has_blend);
+ } else if (wp->w_hl_id_normal != 0) {
+ wp->w_hl_attr_normal = hl_get_ui_attr(-1, wp->w_hl_id_normal, !has_blend);
} else {
wp->w_hl_attr_normal = float_win ? HL_ATTR(HLF_NFLOAT) : 0;
}
- if (wp != curwin) {
+ // if blend= attribute is not set, 'winblend' value overrides it.
+ if (wp->w_floating && wp->w_p_winbl > 0) {
+ HlEntry entry = kv_A(attr_entries, wp->w_hl_attr_normal);
+ if (entry.attr.hl_blend == -1) {
+ entry.attr.hl_blend = (int)wp->w_p_winbl;
+ wp->w_hl_attr_normal = get_attr_entry(entry);
+ }
+ }
+
+ if (wp != curwin && wp->w_hl_ids[HLF_INACTIVE] == 0) {
wp->w_hl_attr_normal = hl_combine_attr(HL_ATTR(HLF_INACTIVE),
wp->w_hl_attr_normal);
}
for (int hlf = 0; hlf < (int)HLF_COUNT; hlf++) {
int attr;
- if (wp->w_hl_ids[hlf] > 0) {
+ if (wp->w_hl_ids[hlf] != 0) {
attr = hl_get_ui_attr(hlf, wp->w_hl_ids[hlf], false);
} else {
attr = HL_ATTR(hlf);
@@ -224,6 +256,7 @@ void clear_hl_tables(bool reinit)
map_clear(int, int)(combine_attr_entries);
map_clear(int, int)(blend_attr_entries);
map_clear(int, int)(blendthrough_attr_entries);
+ memset(highlight_attr_last, -1, sizeof(highlight_attr_last));
highlight_attr_set_all();
highlight_changed();
screen_invalidate_highlights();
@@ -240,6 +273,8 @@ void hl_invalidate_blends(void)
{
map_clear(int, int)(blend_attr_entries);
map_clear(int, int)(blendthrough_attr_entries);
+ highlight_changed();
+ update_window_hl(curwin, true);
}
// Combine special attributes (e.g., for spelling) with other attributes
@@ -292,6 +327,10 @@ int hl_combine_attr(int char_attr, int prim_attr)
new_en.rgb_sp_color = spell_aep.rgb_sp_color;
}
+ if (spell_aep.hl_blend >= 0) {
+ new_en.hl_blend = spell_aep.hl_blend;
+ }
+
id = get_attr_entry((HlEntry){ .attr = new_en, .kind = kHlCombine,
.id1 = char_attr, .id2 = prim_attr });
if (id > 0) {
@@ -336,50 +375,59 @@ static HlAttrs get_colors_force(int attr)
/// This is called per-cell, so cache the result.
///
/// @return the resulting attributes.
-int hl_blend_attrs(int back_attr, int front_attr, bool through)
+int hl_blend_attrs(int back_attr, int front_attr, bool *through)
{
+ HlAttrs fattrs = get_colors_force(front_attr);
+ int ratio = fattrs.hl_blend;
+ if (ratio <= 0) {
+ *through = false;
+ return front_attr;
+ }
+
int combine_tag = (back_attr << 16) + front_attr;
- Map(int, int) *map = through ? blendthrough_attr_entries : blend_attr_entries;
+ Map(int, int) *map = (*through
+ ? blendthrough_attr_entries
+ : blend_attr_entries);
int id = map_get(int, int)(map, combine_tag);
if (id > 0) {
return id;
}
HlAttrs battrs = get_colors_force(back_attr);
- HlAttrs fattrs = get_colors_force(front_attr);
HlAttrs cattrs;
- if (through) {
+
+ if (*through) {
cattrs = battrs;
- cattrs.rgb_fg_color = rgb_blend((int)p_pb, battrs.rgb_fg_color,
+ cattrs.rgb_fg_color = rgb_blend(ratio, battrs.rgb_fg_color,
fattrs.rgb_bg_color);
if (cattrs.rgb_ae_attr & (HL_UNDERLINE|HL_UNDERCURL)) {
- cattrs.rgb_sp_color = rgb_blend((int)p_pb, battrs.rgb_sp_color,
+ cattrs.rgb_sp_color = rgb_blend(ratio, battrs.rgb_sp_color,
fattrs.rgb_bg_color);
} else {
cattrs.rgb_sp_color = -1;
}
cattrs.cterm_bg_color = fattrs.cterm_bg_color;
- cattrs.cterm_fg_color = cterm_blend((int)p_pb, battrs.cterm_fg_color,
+ cattrs.cterm_fg_color = cterm_blend(ratio, battrs.cterm_fg_color,
fattrs.cterm_bg_color);
} else {
cattrs = fattrs;
- if (p_pb >= 50) {
+ if (ratio >= 50) {
cattrs.rgb_ae_attr |= battrs.rgb_ae_attr;
}
- cattrs.rgb_fg_color = rgb_blend((int)p_pb/2, battrs.rgb_fg_color,
+ cattrs.rgb_fg_color = rgb_blend(ratio/2, battrs.rgb_fg_color,
fattrs.rgb_fg_color);
if (cattrs.rgb_ae_attr & (HL_UNDERLINE|HL_UNDERCURL)) {
- cattrs.rgb_sp_color = rgb_blend((int)p_pb/2, battrs.rgb_bg_color,
+ cattrs.rgb_sp_color = rgb_blend(ratio/2, battrs.rgb_bg_color,
fattrs.rgb_sp_color);
} else {
cattrs.rgb_sp_color = -1;
}
}
- cattrs.rgb_bg_color = rgb_blend((int)p_pb, battrs.rgb_bg_color,
+ cattrs.rgb_bg_color = rgb_blend(ratio, battrs.rgb_bg_color,
fattrs.rgb_bg_color);
- HlKind kind = through ? kHlBlendThrough : kHlBlend;
+ HlKind kind = *through ? kHlBlendThrough : kHlBlend;
id = get_attr_entry((HlEntry){ .attr = cattrs, .kind = kind,
.id1 = back_attr, .id2 = front_attr });
if (id > 0) {
diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h
index 746d2c2dfc..afccf9e6f6 100644
--- a/src/nvim/highlight_defs.h
+++ b/src/nvim/highlight_defs.h
@@ -25,6 +25,7 @@ typedef struct attr_entry {
int16_t rgb_ae_attr, cterm_ae_attr; ///< HlAttrFlags
RgbValue rgb_fg_color, rgb_bg_color, rgb_sp_color;
int cterm_fg_color, cterm_bg_color;
+ int hl_blend;
} HlAttrs;
#define HLATTRS_INIT (HlAttrs) { \
@@ -35,6 +36,7 @@ typedef struct attr_entry {
.rgb_sp_color = -1, \
.cterm_fg_color = 0, \
.cterm_bg_color = 0, \
+ .hl_blend = -1, \
}
/// Values for index in highlight_attr[].
@@ -148,6 +150,7 @@ EXTERN const char *hlf_names[] INIT(= {
EXTERN int highlight_attr[HLF_COUNT]; // Highl. attr for each context.
+EXTERN int highlight_attr_last[HLF_COUNT]; // copy for detecting changed groups
EXTERN int highlight_user[9]; // User[1-9] attributes
EXTERN int highlight_stlnc[9]; // On top of user
EXTERN int cterm_normal_fg_color INIT(= 0);
diff --git a/src/nvim/iconv.h b/src/nvim/iconv.h
index d7234090c4..a7c9ad4040 100644
--- a/src/nvim/iconv.h
+++ b/src/nvim/iconv.h
@@ -1,52 +1,20 @@
#ifndef NVIM_ICONV_H
#define NVIM_ICONV_H
-// iconv can be linked at compile-time as well as loaded at runtime. In the
-// latter case, some function pointers need to be initialized after loading
-// the library (see `iconv_enabled()` in mbyte.c). These function pointers
-// are stored in globals.h. Since globals.h includes iconv.h to get the
-// definition of USE_ICONV, we can't include it from iconv.h. One way to
-// solve this conundrum would be perhaps to let cmake decide the value of
-// USE_ICONV, or to put the USE_ICONV definition in config.h.in directly. As
-// it stands, globals.h needs to be included alongside iconv.h.
-
#include "auto/config.h"
-// Use iconv() when it's available, either by linking to the library at
-// compile time or by loading it at runtime.
-#if (defined(HAVE_ICONV_H) && defined(HAVE_ICONV)) || defined(DYNAMIC_ICONV)
-# define USE_ICONV
-#endif
-
-// If we don't have the actual iconv header files present but USE_ICONV was
-// defined, we provide a type shim (pull in errno.h and define iconv_t).
-// This enables us to still load and use iconv dynamically at runtime.
-#ifdef USE_ICONV
+#ifdef HAVE_ICONV
# include <errno.h>
-# ifdef HAVE_ICONV_H
-# include <iconv.h>
-# else
-typedef void *iconv_t;
-# endif
-#endif
+# include <iconv.h>
// define some missing constants if necessary
-# ifdef USE_ICONV
# ifndef EILSEQ
# define EILSEQ 123
# endif
-# ifdef DYNAMIC_ICONV
-// on win32 iconv.dll is dynamically loaded
-# define ICONV_ERRNO (*iconv_errno())
-# define ICONV_E2BIG 7
-# define ICONV_EINVAL 22
-# define ICONV_EILSEQ 42
-# else
-# define ICONV_ERRNO errno
-# define ICONV_E2BIG E2BIG
-# define ICONV_EINVAL EINVAL
-# define ICONV_EILSEQ EILSEQ
-# endif
-# endif
+# define ICONV_ERRNO errno
+# define ICONV_E2BIG E2BIG
+# define ICONV_EINVAL EINVAL
+# define ICONV_EILSEQ EILSEQ
+#endif
#endif // NVIM_ICONV_H
diff --git a/src/nvim/if_cscope.c b/src/nvim/if_cscope.c
index a3eabed8a0..0f9984ec38 100644
--- a/src/nvim/if_cscope.c
+++ b/src/nvim/if_cscope.c
@@ -1008,10 +1008,10 @@ static int cs_find_common(char *opt, char *pat, int forceit, int verbose,
qf_info_T *qi = NULL;
win_T *wp = NULL;
- f = mch_fopen((char *)tmp, "w");
- if (f == NULL)
+ f = os_fopen((char *)tmp, "w");
+ if (f == NULL) {
EMSG2(_(e_notopen), tmp);
- else {
+ } else {
cs_file_results(f, nummatches);
fclose(f);
if (use_ll) /* Use location list */
@@ -1648,7 +1648,7 @@ static void cs_print_tags_priv(char **matches, char **cntxts,
assert(buf_len >= 0);
// Print the context only if it fits on the same line.
- if (msg_col + buf_len >= (int)Columns) {
+ if (msg_col + buf_len >= Columns) {
msg_putchar('\n');
}
msg_advance(12);
diff --git a/src/nvim/indent.c b/src/nvim/indent.c
index 8e20aa5be4..efbfea33aa 100644
--- a/src/nvim/indent.c
+++ b/src/nvim/indent.c
@@ -7,6 +7,7 @@
#include "nvim/ascii.h"
#include "nvim/assert.h"
+#include "nvim/change.h"
#include "nvim/indent.h"
#include "nvim/eval.h"
#include "nvim/charset.h"
@@ -316,109 +317,6 @@ int set_indent(int size, int flags)
}
-// Copy the indent from ptr to the current line (and fill to size).
-// Leaves the cursor on the first non-blank in the line.
-// @return true if the line was changed.
-int copy_indent(int size, char_u *src)
-{
- char_u *p = NULL;
- char_u *line = NULL;
- char_u *s;
- int todo;
- int ind_len;
- int line_len = 0;
- int tab_pad;
- int ind_done;
- int round;
-
- // Round 1: compute the number of characters needed for the indent
- // Round 2: copy the characters.
- for (round = 1; round <= 2; ++round) {
- todo = size;
- ind_len = 0;
- ind_done = 0;
- s = src;
-
- // Count/copy the usable portion of the source line.
- while (todo > 0 && ascii_iswhite(*s)) {
- if (*s == TAB) {
- tab_pad = (int)curbuf->b_p_ts
- - (ind_done % (int)curbuf->b_p_ts);
-
- // Stop if this tab will overshoot the target.
- if (todo < tab_pad) {
- break;
- }
- todo -= tab_pad;
- ind_done += tab_pad;
- } else {
- todo--;
- ind_done++;
- }
- ind_len++;
-
- if (p != NULL) {
- *p++ = *s;
- }
- s++;
- }
-
- // Fill to next tabstop with a tab, if possible.
- tab_pad = (int)curbuf->b_p_ts - (ind_done % (int)curbuf->b_p_ts);
-
- if ((todo >= tab_pad) && !curbuf->b_p_et) {
- todo -= tab_pad;
- ind_len++;
-
- if (p != NULL) {
- *p++ = TAB;
- }
- }
-
- // Add tabs required for indent.
- while (todo >= (int)curbuf->b_p_ts && !curbuf->b_p_et) {
- todo -= (int)curbuf->b_p_ts;
- ind_len++;
-
- if (p != NULL) {
- *p++ = TAB;
- }
- }
-
- // Count/add spaces required for indent.
- while (todo > 0) {
- todo--;
- ind_len++;
-
- if (p != NULL) {
- *p++ = ' ';
- }
- }
-
- if (p == NULL) {
- // Allocate memory for the result: the copied indent, new indent
- // and the rest of the line.
- line_len = (int)STRLEN(get_cursor_line_ptr()) + 1;
- assert(ind_len + line_len >= 0);
- size_t line_size;
- STRICT_ADD(ind_len, line_len, &line_size, size_t);
- line = xmalloc(line_size);
- p = line;
- }
- }
-
- // Append the original line
- memmove(p, get_cursor_line_ptr(), (size_t)line_len);
-
- // Replace the line
- ml_replace(curwin->w_cursor.lnum, line, false);
-
- // Put the cursor after the indent.
- curwin->w_cursor.col = ind_len;
- return true;
-}
-
-
// Return the indent of the current line after a number. Return -1 if no
// number was found. Used for 'n' in 'formatoptions': numbered list.
// Since a pattern is used it can actually handle more than numbers.
diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c
index 3729e42b99..9665655e74 100644
--- a/src/nvim/lua/converter.c
+++ b/src/nvim/lua/converter.c
@@ -615,7 +615,7 @@ static inline void nlua_create_typed_table(lua_State *lstate,
/// Convert given String to lua string
///
/// Leaves converted string on top of the stack.
-void nlua_push_String(lua_State *lstate, const String s)
+void nlua_push_String(lua_State *lstate, const String s, bool special)
FUNC_ATTR_NONNULL_ALL
{
lua_pushlstring(lstate, s.data, s.size);
@@ -624,7 +624,7 @@ void nlua_push_String(lua_State *lstate, const String s)
/// Convert given Integer to lua number
///
/// Leaves converted number on top of the stack.
-void nlua_push_Integer(lua_State *lstate, const Integer n)
+void nlua_push_Integer(lua_State *lstate, const Integer n, bool special)
FUNC_ATTR_NONNULL_ALL
{
lua_pushnumber(lstate, (lua_Number)n);
@@ -633,19 +633,23 @@ void nlua_push_Integer(lua_State *lstate, const Integer n)
/// Convert given Float to lua table
///
/// Leaves converted table on top of the stack.
-void nlua_push_Float(lua_State *lstate, const Float f)
+void nlua_push_Float(lua_State *lstate, const Float f, bool special)
FUNC_ATTR_NONNULL_ALL
{
- nlua_create_typed_table(lstate, 0, 1, kObjectTypeFloat);
- nlua_push_val_idx(lstate);
- lua_pushnumber(lstate, (lua_Number)f);
- lua_rawset(lstate, -3);
+ if (special) {
+ nlua_create_typed_table(lstate, 0, 1, kObjectTypeFloat);
+ nlua_push_val_idx(lstate);
+ lua_pushnumber(lstate, (lua_Number)f);
+ lua_rawset(lstate, -3);
+ } else {
+ lua_pushnumber(lstate, (lua_Number)f);
+ }
}
/// Convert given Float to lua boolean
///
/// Leaves converted value on top of the stack.
-void nlua_push_Boolean(lua_State *lstate, const Boolean b)
+void nlua_push_Boolean(lua_State *lstate, const Boolean b, bool special)
FUNC_ATTR_NONNULL_ALL
{
lua_pushboolean(lstate, b);
@@ -654,17 +658,18 @@ void nlua_push_Boolean(lua_State *lstate, const Boolean b)
/// Convert given Dictionary to lua table
///
/// Leaves converted table on top of the stack.
-void nlua_push_Dictionary(lua_State *lstate, const Dictionary dict)
+void nlua_push_Dictionary(lua_State *lstate, const Dictionary dict,
+ bool special)
FUNC_ATTR_NONNULL_ALL
{
- if (dict.size == 0) {
+ if (dict.size == 0 && special) {
nlua_create_typed_table(lstate, 0, 0, kObjectTypeDictionary);
} else {
lua_createtable(lstate, 0, (int)dict.size);
}
for (size_t i = 0; i < dict.size; i++) {
- nlua_push_String(lstate, dict.items[i].key);
- nlua_push_Object(lstate, dict.items[i].value);
+ nlua_push_String(lstate, dict.items[i].key, special);
+ nlua_push_Object(lstate, dict.items[i].value, special);
lua_rawset(lstate, -3);
}
}
@@ -672,18 +677,18 @@ void nlua_push_Dictionary(lua_State *lstate, const Dictionary dict)
/// Convert given Array to lua table
///
/// Leaves converted table on top of the stack.
-void nlua_push_Array(lua_State *lstate, const Array array)
+void nlua_push_Array(lua_State *lstate, const Array array, bool special)
FUNC_ATTR_NONNULL_ALL
{
lua_createtable(lstate, (int)array.size, 0);
for (size_t i = 0; i < array.size; i++) {
- nlua_push_Object(lstate, array.items[i]);
+ nlua_push_Object(lstate, array.items[i], special);
lua_rawseti(lstate, -2, (int)i + 1);
}
}
#define GENERATE_INDEX_FUNCTION(type) \
-void nlua_push_##type(lua_State *lstate, const type item) \
+void nlua_push_##type(lua_State *lstate, const type item, bool special) \
FUNC_ATTR_NONNULL_ALL \
{ \
lua_pushnumber(lstate, (lua_Number)(item)); \
@@ -698,7 +703,7 @@ GENERATE_INDEX_FUNCTION(Tabpage)
/// Convert given Object to lua value
///
/// Leaves converted value on top of the stack.
-void nlua_push_Object(lua_State *lstate, const Object obj)
+void nlua_push_Object(lua_State *lstate, const Object obj, bool special)
FUNC_ATTR_NONNULL_ALL
{
switch (obj.type) {
@@ -712,7 +717,7 @@ void nlua_push_Object(lua_State *lstate, const Object obj)
}
#define ADD_TYPE(type, data_key) \
case kObjectType##type: { \
- nlua_push_##type(lstate, obj.data.data_key); \
+ nlua_push_##type(lstate, obj.data.data_key, special); \
break; \
}
ADD_TYPE(Boolean, boolean)
@@ -724,7 +729,7 @@ void nlua_push_Object(lua_State *lstate, const Object obj)
#undef ADD_TYPE
#define ADD_REMOTE_TYPE(type) \
case kObjectType##type: { \
- nlua_push_##type(lstate, (type)obj.data.integer); \
+ nlua_push_##type(lstate, (type)obj.data.integer, special); \
break; \
}
ADD_REMOTE_TYPE(Buffer)
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c
index df08a9dd87..3de1b531e6 100644
--- a/src/nvim/lua/executor.c
+++ b/src/nvim/lua/executor.c
@@ -23,6 +23,7 @@
#include "nvim/cursor.h"
#include "nvim/undo.h"
#include "nvim/ascii.h"
+#include "nvim/change.h"
#ifdef WIN32
#include "nvim/os/os.h"
@@ -31,6 +32,10 @@
#include "nvim/lua/executor.h"
#include "nvim/lua/converter.h"
+#include "luv/luv.h"
+
+static int in_fast_callback = 0;
+
typedef struct {
Error err;
String lua_err_str;
@@ -108,6 +113,109 @@ static int nlua_stricmp(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
return 1;
}
+/// convert byte index to UTF-32 and UTF-16 indicies
+///
+/// Expects a string and an optional index. If no index is supplied, the length
+/// of the string is returned.
+///
+/// Returns two values: the UTF-32 and UTF-16 indicies.
+static int nlua_str_utfindex(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
+{
+ size_t s1_len;
+ const char *s1 = luaL_checklstring(lstate, 1, &s1_len);
+ intptr_t idx;
+ if (lua_gettop(lstate) >= 2) {
+ idx = luaL_checkinteger(lstate, 2);
+ if (idx < 0 || idx > (intptr_t)s1_len) {
+ return luaL_error(lstate, "index out of range");
+ }
+ } else {
+ idx = (intptr_t)s1_len;
+ }
+
+ size_t codepoints = 0, codeunits = 0;
+ mb_utflen((const char_u *)s1, (size_t)idx, &codepoints, &codeunits);
+
+ lua_pushinteger(lstate, (long)codepoints);
+ lua_pushinteger(lstate, (long)codeunits);
+
+ return 2;
+}
+
+/// convert UTF-32 or UTF-16 indicies to byte index.
+///
+/// Expects up to three args: string, index and use_utf16.
+/// If use_utf16 is not supplied it defaults to false (use UTF-32)
+///
+/// Returns the byte index.
+static int nlua_str_byteindex(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
+{
+ size_t s1_len;
+ const char *s1 = luaL_checklstring(lstate, 1, &s1_len);
+ intptr_t idx = luaL_checkinteger(lstate, 2);
+ if (idx < 0) {
+ return luaL_error(lstate, "index out of range");
+ }
+ bool use_utf16 = false;
+ if (lua_gettop(lstate) >= 3) {
+ use_utf16 = lua_toboolean(lstate, 3);
+ }
+
+ ssize_t byteidx = mb_utf_index_to_bytes((const char_u *)s1, s1_len,
+ (size_t)idx, use_utf16);
+ if (byteidx == -1) {
+ return luaL_error(lstate, "index out of range");
+ }
+
+ lua_pushinteger(lstate, (long)byteidx);
+
+ return 1;
+}
+
+static void nlua_luv_error_event(void **argv)
+{
+ char *error = (char *)argv[0];
+ msg_ext_set_kind("lua_error");
+ emsgf_multiline("Error executing luv callback:\n%s", error);
+ xfree(error);
+}
+
+static int nlua_luv_cfpcall(lua_State *lstate, int nargs, int nresult,
+ int flags)
+ FUNC_ATTR_NONNULL_ALL
+{
+ int retval;
+
+ // luv callbacks might be executed at any os_breakcheck/line_breakcheck
+ // call, so using the API directly here is not safe.
+ in_fast_callback++;
+
+ int top = lua_gettop(lstate);
+ int status = lua_pcall(lstate, nargs, nresult, 0);
+ if (status) {
+ if (status == LUA_ERRMEM && !(flags & LUVF_CALLBACK_NOEXIT)) {
+ // consider out of memory errors unrecoverable, just like xmalloc()
+ mch_errmsg(e_outofmem);
+ mch_errmsg("\n");
+ preserve_exit();
+ }
+ const char *error = lua_tostring(lstate, -1);
+
+ multiqueue_put(main_loop.events, nlua_luv_error_event,
+ 1, xstrdup(error));
+ lua_pop(lstate, 1); // error mesage
+ retval = -status;
+ } else { // LUA_OK
+ if (nresult == LUA_MULTRET) {
+ nresult = lua_gettop(lstate) - top + nargs + 1;
+ }
+ retval = nresult;
+ }
+
+ in_fast_callback--;
+ return retval;
+}
+
static void nlua_schedule_event(void **argv)
{
LuaRef cb = (LuaRef)(ptrdiff_t)argv[0];
@@ -172,9 +280,33 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
// stricmp
lua_pushcfunction(lstate, &nlua_stricmp);
lua_setfield(lstate, -2, "stricmp");
+ // str_utfindex
+ lua_pushcfunction(lstate, &nlua_str_utfindex);
+ lua_setfield(lstate, -2, "str_utfindex");
+ // str_byteindex
+ lua_pushcfunction(lstate, &nlua_str_byteindex);
+ lua_setfield(lstate, -2, "str_byteindex");
// schedule
lua_pushcfunction(lstate, &nlua_schedule);
lua_setfield(lstate, -2, "schedule");
+ // in_fast_event
+ lua_pushcfunction(lstate, &nlua_in_fast_event);
+ lua_setfield(lstate, -2, "in_fast_event");
+
+ // vim.loop
+ luv_set_loop(lstate, &main_loop.uv);
+ luv_set_callback(lstate, nlua_luv_cfpcall);
+ luaopen_luv(lstate);
+ lua_pushvalue(lstate, -1);
+ lua_setfield(lstate, -3, "loop");
+
+ // package.loaded.luv = vim.loop
+ // otherwise luv will be reinitialized when require'luv'
+ lua_getglobal(lstate, "package");
+ lua_getfield(lstate, -1, "loaded");
+ lua_pushvalue(lstate, -3);
+ lua_setfield(lstate, -2, "luv");
+ lua_pop(lstate, 3);
lua_setglobal(lstate, "vim");
return 0;
@@ -260,6 +392,42 @@ void executor_exec_lua(const String str, typval_T *const ret_tv)
nlua_pop_typval(lstate, ret_tv);
}
+static void nlua_print_event(void **argv)
+{
+ char *str = argv[0];
+ const size_t len = (size_t)(intptr_t)argv[1]-1; // exclude final NUL
+
+ for (size_t i = 0; i < len;) {
+ const size_t start = i;
+ while (i < len) {
+ switch (str[i]) {
+ case NUL: {
+ str[i] = NL;
+ i++;
+ continue;
+ }
+ case NL: {
+ // TODO(bfredl): use proper multiline msg? Probably should implement
+ // print() in lua in terms of nvim_message(), when it is available.
+ str[i] = NUL;
+ i++;
+ break;
+ }
+ default: {
+ i++;
+ continue;
+ }
+ }
+ break;
+ }
+ msg((char_u *)str + start);
+ }
+ if (len && str[len - 1] == NUL) { // Last was newline
+ msg((char_u *)"");
+ }
+ xfree(str);
+}
+
/// Print as a Vim message
///
/// @param lstate Lua interpreter state.
@@ -299,47 +467,24 @@ static int nlua_print(lua_State *const lstate)
lua_pop(lstate, 1);
}
#undef PRINT_ERROR
- lua_pop(lstate, nargs + 1);
ga_append(&msg_ga, NUL);
- {
- const size_t len = (size_t)msg_ga.ga_len - 1;
- char *const str = (char *)msg_ga.ga_data;
-
- for (size_t i = 0; i < len;) {
- const size_t start = i;
- while (i < len) {
- switch (str[i]) {
- case NUL: {
- str[i] = NL;
- i++;
- continue;
- }
- case NL: {
- str[i] = NUL;
- i++;
- break;
- }
- default: {
- i++;
- continue;
- }
- }
- break;
- }
- msg((char_u *)str + start);
- }
- if (len && str[len - 1] == NUL) { // Last was newline
- msg((char_u *)"");
- }
+
+ if (in_fast_callback) {
+ multiqueue_put(main_loop.events, nlua_print_event,
+ 2, msg_ga.ga_data, msg_ga.ga_len);
+ } else {
+ nlua_print_event((void *[]){ msg_ga.ga_data,
+ (void *)(intptr_t)msg_ga.ga_len });
}
- ga_clear(&msg_ga);
return 0;
+
nlua_print_error:
- emsgf(_("E5114: Error while converting print argument #%i: %.*s"),
- curargidx, (int)errmsg_len, errmsg);
ga_clear(&msg_ga);
- lua_pop(lstate, lua_gettop(lstate));
- return 0;
+ const char *fmt = _("E5114: Error while converting print argument #%i: %.*s");
+ size_t len = (size_t)vim_snprintf((char *)IObuff, IOSIZE, fmt, curargidx,
+ (int)errmsg_len, errmsg);
+ lua_pushlstring(lstate, (char *)IObuff, len);
+ return lua_error(lstate);
}
/// debug.debug: interaction with user while debugging.
@@ -373,15 +518,20 @@ int nlua_debug(lua_State *lstate)
if (luaL_loadbuffer(lstate, (const char *)input.vval.v_string,
STRLEN(input.vval.v_string), "=(debug command)")) {
nlua_error(lstate, _("E5115: Error while loading debug string: %.*s"));
- }
- tv_clear(&input);
- if (lua_pcall(lstate, 0, 0, 0)) {
+ } else if (lua_pcall(lstate, 0, 0, 0)) {
nlua_error(lstate, _("E5116: Error while calling debug string: %.*s"));
}
+ tv_clear(&input);
}
return 0;
}
+int nlua_in_fast_event(lua_State *lstate)
+{
+ lua_pushboolean(lstate, in_fast_callback > 0);
+ return 1;
+}
+
#ifdef WIN32
/// os.getenv: override os.getenv to maintain coherency. #9681
///
@@ -499,7 +649,7 @@ Object executor_exec_lua_api(const String str, const Array args, Error *err)
}
for (size_t i = 0; i < args.size; i++) {
- nlua_push_Object(lstate, args.items[i]);
+ nlua_push_Object(lstate, args.items[i], false);
}
if (lua_pcall(lstate, (int)args.size, 1, 0)) {
@@ -513,16 +663,17 @@ Object executor_exec_lua_api(const String str, const Array args, Error *err)
return nlua_pop_Object(lstate, false, err);
}
-Object executor_exec_lua_cb(LuaRef ref, const char *name, Array args)
+Object executor_exec_lua_cb(LuaRef ref, const char *name, Array args,
+ bool retval)
{
lua_State *const lstate = nlua_enter();
nlua_pushref(lstate, ref);
lua_pushstring(lstate, name);
for (size_t i = 0; i < args.size; i++) {
- nlua_push_Object(lstate, args.items[i]);
+ nlua_push_Object(lstate, args.items[i], false);
}
- if (lua_pcall(lstate, (int)args.size+1, 1, 0)) {
+ if (lua_pcall(lstate, (int)args.size+1, retval ? 1 : 0, 0)) {
// TODO(bfredl): callbacks:s might not always be msg-safe, for instance
// lua callbacks for redraw events. Later on let the caller deal with the
// error instead.
@@ -531,7 +682,18 @@ Object executor_exec_lua_cb(LuaRef ref, const char *name, Array args)
}
Error err = ERROR_INIT;
- return nlua_pop_Object(lstate, false, &err);
+ if (retval) {
+ return nlua_pop_Object(lstate, false, &err);
+ } else {
+ return NIL;
+ }
+}
+
+/// check if the current execution context is safe for calling deferred API
+/// methods. Luv callbacks are unsafe as they are called inside the uv loop.
+bool nlua_is_deferred_safe(lua_State *lstate)
+{
+ return in_fast_callback == 0;
}
/// Run lua string
diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua
index 848bccaae6..46c96b455f 100644
--- a/src/nvim/lua/vim.lua
+++ b/src/nvim/lua/vim.lua
@@ -172,11 +172,22 @@ local function __index(t, key)
end
end
+--- Defers the wrapped callback until when the nvim API is safe to call.
+---
+--- See |vim-loop-callbacks|
+local function schedule_wrap(cb)
+ return (function (...)
+ local args = {...}
+ vim.schedule(function() cb(unpack(args)) end)
+ end)
+end
+
local module = {
_update_package_paths = _update_package_paths,
_os_proc_children = _os_proc_children,
_os_proc_info = _os_proc_info,
_system = _system,
+ schedule_wrap = schedule_wrap,
}
setmetatable(module, {
diff --git a/src/nvim/macros.h b/src/nvim/macros.h
index 61009528a8..018985fad2 100644
--- a/src/nvim/macros.h
+++ b/src/nvim/macros.h
@@ -86,8 +86,6 @@
#define READBIN "rb"
#define APPENDBIN "ab"
-# define mch_fopen(n, p) fopen((n), (p))
-
/* 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)
@@ -129,7 +127,11 @@
# define MB_CHAR2LEN(c) mb_char2len(c)
# define PTR2CHAR(p) utf_ptr2char(p)
-# define RESET_BINDING(wp) (wp)->w_p_scb = FALSE; (wp)->w_p_crb = FALSE
+# define RESET_BINDING(wp) \
+ do { \
+ (wp)->w_p_scb = false; \
+ (wp)->w_p_crb = false; \
+ } while (0)
/// Calculate the length of a C array
///
@@ -165,7 +167,8 @@
# define NVIM_HAS_ATTRIBUTE __has_attribute
#endif
-#if NVIM_HAS_ATTRIBUTE(fallthrough)
+#if NVIM_HAS_ATTRIBUTE(fallthrough) \
+ && (!defined(__apple_build_version__) || __apple_build_version__ >= 7000000)
# define FALLTHROUGH __attribute__((fallthrough))
#else
# define FALLTHROUGH
diff --git a/src/nvim/main.c b/src/nvim/main.c
index 28ef8e04ea..5aa7ed42d3 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -54,6 +54,7 @@
#include "nvim/strings.h"
#include "nvim/syntax.h"
#include "nvim/ui.h"
+#include "nvim/ui_compositor.h"
#include "nvim/version.h"
#include "nvim/window.h"
#include "nvim/shada.h"
@@ -141,15 +142,12 @@ static const char *err_extra_cmd =
void event_init(void)
{
+ log_init();
loop_init(&main_loop, NULL);
// early msgpack-rpc initialization
msgpack_rpc_init_method_table();
msgpack_rpc_helpers_init();
- // Initialize input events
input_init();
- // Timer to wake the event loop if a timeout argument is passed to
- // `event_poll`
- // Signals
signal_init();
// finish mspgack-rpc initialization
channel_init();
@@ -185,7 +183,6 @@ bool event_teardown(void)
/// Needed for unit tests. Must be called after `time_init()`.
void early_init(void)
{
- log_init();
env_init();
fs_init();
handle_init();
@@ -222,6 +219,9 @@ void early_init(void)
TIME_MSG("inits 1");
set_lang_var(); // set v:lang and v:ctype
+
+ init_signs();
+ ui_comp_syn_init();
}
#ifdef MAKE_LIB
@@ -257,12 +257,13 @@ int main(int argc, char **argv)
init_startuptime(&params);
+ event_init();
+
early_init();
// Check if we have an interactive window.
check_and_set_isatty(&params);
- event_init();
// Process the command line arguments. File names are put in the global
// argument list "global_alist".
command_line_scan(&params);
@@ -343,10 +344,8 @@ int main(int argc, char **argv)
p_lpl = false;
}
- // give embedders a chance to set up nvim, by processing a request before
- // startup. This allows an external UI to show messages and prompts from
- // --cmd and buffer loading (e.g. swap files)
- bool early_ui = false;
+ // Wait for UIs to set up Nvim or show early messages
+ // and prompts (--cmd, swapfile dialog, …).
bool use_remote_ui = (embedded_mode && !headless_mode);
bool use_builtin_ui = (!headless_mode && !embedded_mode && !silent_mode);
if (use_remote_ui || use_builtin_ui) {
@@ -361,7 +360,6 @@ int main(int argc, char **argv)
// prepare screen now, so external UIs can display messages
starting = NO_BUFFERS;
screenclear();
- early_ui = true;
TIME_MSG("initialized screen early for UI");
}
@@ -458,7 +456,7 @@ int main(int argc, char **argv)
setmouse(); // may start using the mouse
- if (exmode_active || early_ui) {
+ if (exmode_active || use_remote_ui || use_builtin_ui) {
// Don't clear the screen when starting in Ex mode, or when a UI might have
// displayed messages.
redraw_later(VALID);
@@ -581,9 +579,7 @@ int main(int argc, char **argv)
void getout(int exitval)
FUNC_ATTR_NORETURN
{
- tabpage_T *tp, *next_tp;
-
- exiting = TRUE;
+ exiting = true;
/* When running in Ex mode an error causes us to exit with a non-zero exit
* code. POSIX requires this, although it's not 100% clear from the
@@ -593,15 +589,17 @@ void getout(int exitval)
set_vim_var_nr(VV_EXITING, exitval);
- /* Position the cursor on the last screen line, below all the text */
- ui_cursor_goto((int)Rows - 1, 0);
+ // Position the cursor on the last screen line, below all the text
+ ui_cursor_goto(Rows - 1, 0);
/* Optionally print hashtable efficiency. */
hash_debug_results();
if (get_vim_var_nr(VV_DYING) <= 1) {
- /* Trigger BufWinLeave for all windows, but only once per buffer. */
- for (tp = first_tabpage; tp != NULL; tp = next_tp) {
+ const tabpage_T *next_tp;
+
+ // Trigger BufWinLeave for all windows, but only once per buffer.
+ for (const tabpage_T *tp = first_tabpage; tp != NULL; tp = next_tp) {
next_tp = tp->tp_next;
FOR_ALL_WINDOWS_IN_TAB(wp, tp) {
if (wp->w_buffer == NULL) {
@@ -658,17 +656,14 @@ void getout(int exitval)
wait_return(FALSE);
}
- /* Position the cursor again, the autocommands may have moved it */
- ui_cursor_goto((int)Rows - 1, 0);
+ // Position the cursor again, the autocommands may have moved it
+ ui_cursor_goto(Rows - 1, 0);
// Apply 'titleold'.
if (p_title && *p_titleold != NUL) {
ui_call_set_title(cstr_as_string((char *)p_titleold));
}
-#if defined(USE_ICONV) && defined(DYNAMIC_ICONV)
- iconv_end();
-#endif
cs_end();
if (garbage_collect_at_exit) {
garbage_collect(false);
@@ -1168,8 +1163,8 @@ scripterror:
if (scriptout != NULL) {
goto scripterror;
}
- if ((scriptout = mch_fopen(argv[0],
- c == 'w' ? APPENDBIN : WRITEBIN)) == NULL) {
+ if ((scriptout = os_fopen(argv[0], c == 'w' ? APPENDBIN : WRITEBIN))
+ == NULL) {
mch_errmsg(_("Cannot open for script output: \""));
mch_errmsg(argv[0]);
mch_errmsg("\"\n");
@@ -1257,8 +1252,9 @@ static void init_params(mparm_T *paramp, int argc, char **argv)
static void init_startuptime(mparm_T *paramp)
{
for (int i = 1; i < paramp->argc; i++) {
- if (STRICMP(paramp->argv[i], "--startuptime") == 0 && i + 1 < paramp->argc) {
- time_fd = mch_fopen(paramp->argv[i + 1], "a");
+ if (STRICMP(paramp->argv[i], "--startuptime") == 0
+ && i + 1 < paramp->argc) {
+ time_fd = os_fopen(paramp->argv[i + 1], "a");
time_start("--- NVIM STARTING ---");
break;
}
@@ -1304,8 +1300,6 @@ static void init_path(const char *exename)
// shipped with Windows package. This also mimics SearchPath().
os_setenv_append_path(exepath);
#endif
-
- init_signs();
}
/// Get filename from command line, if any.
diff --git a/src/nvim/map.c b/src/nvim/map.c
index 90da27cf9f..cdade5ee71 100644
--- a/src/nvim/map.c
+++ b/src/nvim/map.c
@@ -179,7 +179,7 @@ MAP_IMPL(cstr_t, ptr_t, DEFAULT_INITIALIZER)
MAP_IMPL(ptr_t, ptr_t, DEFAULT_INITIALIZER)
MAP_IMPL(uint64_t, ptr_t, DEFAULT_INITIALIZER)
MAP_IMPL(handle_T, ptr_t, DEFAULT_INITIALIZER)
-#define MSGPACK_HANDLER_INITIALIZER { .fn = NULL, .async = false }
+#define MSGPACK_HANDLER_INITIALIZER { .fn = NULL, .fast = false }
MAP_IMPL(String, MsgpackRpcRequestHandler, MSGPACK_HANDLER_INITIALIZER)
#define KVEC_INITIALIZER { .size = 0, .capacity = 0, .items = NULL }
MAP_IMPL(HlEntry, int, DEFAULT_INITIALIZER)
diff --git a/src/nvim/mark.c b/src/nvim/mark.c
index 2f2f2a7d74..9f357575d0 100644
--- a/src/nvim/mark.c
+++ b/src/nvim/mark.c
@@ -656,48 +656,51 @@ show_one_mark(
int c,
char_u *arg,
pos_T *p,
- char_u *name,
- int current /* in current file */
+ char_u *name_arg,
+ int current // in current file
)
{
- static int did_title = FALSE;
- int mustfree = FALSE;
-
- if (c == -1) { /* finish up */
- if (did_title)
- did_title = FALSE;
- else {
- if (arg == NULL)
+ static bool did_title = false;
+ bool mustfree = false;
+ char_u *name = name_arg;
+
+ if (c == -1) { // finish up
+ if (did_title) {
+ did_title = false;
+ } else {
+ if (arg == NULL) {
MSG(_("No marks set"));
- else
+ } else {
EMSG2(_("E283: No marks matching \"%s\""), arg);
+ }
}
- }
- /* don't output anything if 'q' typed at --more-- prompt */
- else if (!got_int
- && (arg == NULL || vim_strchr(arg, c) != NULL)
- && p->lnum != 0) {
- if (!did_title) {
- /* Highlight title */
- MSG_PUTS_TITLE(_("\nmark line col file/text"));
- did_title = TRUE;
+ } else if (!got_int
+ && (arg == NULL || vim_strchr(arg, c) != NULL)
+ && p->lnum != 0) {
+ // don't output anything if 'q' typed at --more-- prompt
+ if (name == NULL && current) {
+ name = mark_line(p, 15);
+ mustfree = true;
}
- msg_putchar('\n');
- if (!got_int) {
- sprintf((char *)IObuff, " %c %6ld %4d ", c, p->lnum, p->col);
- msg_outtrans(IObuff);
- if (name == NULL && current) {
- name = mark_line(p, 15);
- mustfree = TRUE;
+ if (!message_filtered(name)) {
+ if (!did_title) {
+ // Highlight title
+ msg_puts_title(_("\nmark line col file/text"));
+ did_title = true;
}
- if (name != NULL) {
- msg_outtrans_attr(name, current ? HL_ATTR(HLF_D) : 0);
- if (mustfree) {
- xfree(name);
+ msg_putchar('\n');
+ if (!got_int) {
+ snprintf((char *)IObuff, IOSIZE, " %c %6ld %4d ", c, p->lnum, p->col);
+ msg_outtrans(IObuff);
+ if (name != NULL) {
+ msg_outtrans_attr(name, current ? HL_ATTR(HLF_D) : 0);
}
}
+ ui_flush(); // show one line at a time
+ }
+ if (mustfree) {
+ xfree(name);
}
- ui_flush(); /* show one line at a time */
}
}
@@ -786,8 +789,12 @@ void ex_jumps(exarg_T *eap)
for (i = 0; i < curwin->w_jumplistlen && !got_int; ++i) {
if (curwin->w_jumplist[i].fmark.mark.lnum != 0) {
name = fm_getname(&curwin->w_jumplist[i].fmark, 16);
- if (name == NULL) /* file name not available */
+
+ // apply :filter /pat/ or file name not available
+ if (name == NULL || message_filtered(name)) {
+ xfree(name);
continue;
+ }
msg_putchar('\n');
if (got_int) {
@@ -1206,8 +1213,8 @@ void cleanup_jumplist(win_T *wp, bool loadfiles)
// When pointer is below last jump, remove the jump if it matches the current
// line. This avoids useless/phantom jumps. #9805
- if (wp->w_jumplistlen
- && wp->w_jumplistidx == wp->w_jumplistlen) {
+ if (loadfiles // otherwise (i.e.: Shada), last entry should be kept
+ && wp->w_jumplistlen && wp->w_jumplistidx == wp->w_jumplistlen) {
const xfmark_T *fm_last = &wp->w_jumplist[wp->w_jumplistlen - 1];
if (fm_last->fmark.fnum == curbuf->b_fnum
&& fm_last->fmark.mark.lnum == wp->w_cursor.lnum) {
diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c
index 683087bd7b..fae7635d34 100644
--- a/src/nvim/mbyte.c
+++ b/src/nvim/mbyte.c
@@ -456,43 +456,50 @@ static bool intable(const struct interval *table, size_t n_items, int c)
return false;
}
-/*
- * For UTF-8 character "c" return 2 for a double-width character, 1 for others.
- * Returns 4 or 6 for an unprintable character.
- * Is only correct for characters >= 0x80.
- * When p_ambw is "double", return 2 for a character with East Asian Width
- * class 'A'(mbiguous).
- */
+/// For UTF-8 character "c" return 2 for a double-width character, 1 for others.
+/// Returns 4 or 6 for an unprintable character.
+/// Is only correct for characters >= 0x80.
+/// When p_ambw is "double", return 2 for a character with East Asian Width
+/// class 'A'(mbiguous).
+///
+/// @note Tables `doublewidth` and `ambiguous` are generated by
+/// gen_unicode_tables.lua, which must be manually invoked as needed.
int utf_char2cells(int c)
{
if (c >= 0x100) {
#ifdef USE_WCHAR_FUNCTIONS
- /*
- * Assume the library function wcwidth() works better than our own
- * stuff. It should return 1 for ambiguous width chars!
- */
+ //
+ // Assume the library function wcwidth() works better than our own
+ // stuff. It should return 1 for ambiguous width chars!
+ //
int n = wcwidth(c);
- if (n < 0)
- return 6; /* unprintable, displays <xxxx> */
- if (n > 1)
+ if (n < 0) {
+ return 6; // unprintable, displays <xxxx>
+ }
+ if (n > 1) {
return n;
+ }
#else
- if (!utf_printable(c))
- return 6; /* unprintable, displays <xxxx> */
- if (intable(doublewidth, ARRAY_SIZE(doublewidth), c))
+ if (!utf_printable(c)) {
+ return 6; // unprintable, displays <xxxx>
+ }
+ if (intable(doublewidth, ARRAY_SIZE(doublewidth), c)) {
return 2;
+ }
#endif
if (p_emoji && intable(emoji_width, ARRAY_SIZE(emoji_width), c)) {
return 2;
}
+ } else if (c >= 0x80 && !vim_isprintc(c)) {
+ // Characters below 0x100 are influenced by 'isprint' option.
+ return 4; // unprintable, displays <xx>
}
- /* Characters below 0x100 are influenced by 'isprint' option */
- else if (c >= 0x80 && !vim_isprintc(c))
- return 4; /* unprintable, displays <xx> */
- if (c >= 0x80 && *p_ambw == 'd' && intable(ambiguous, ARRAY_SIZE(ambiguous), c))
+ if (c >= 0x80 && *p_ambw == 'd'
+ && intable(ambiguous, ARRAY_SIZE(ambiguous), c)) {
return 2;
+ }
return 1;
}
@@ -1431,6 +1438,64 @@ int utf16_to_utf8(const wchar_t *strw, char **str)
#endif
+/// Measure the length of a string in corresponding UTF-32 and UTF-16 units.
+///
+/// Invalid UTF-8 bytes, or embedded surrogates, count as one code point/unit
+/// each.
+///
+/// The out parameters are incremented. This is used to measure the size of
+/// a buffer region consisting of multiple line segments.
+///
+/// @param s the string
+/// @param len maximum length (an earlier NUL terminates)
+/// @param[out] codepoints incremented with UTF-32 code point size
+/// @param[out] codeunits incremented with UTF-16 code unit size
+void mb_utflen(const char_u *s, size_t len, size_t *codepoints,
+ size_t *codeunits)
+ FUNC_ATTR_NONNULL_ALL
+{
+ size_t count = 0, extra = 0;
+ size_t clen;
+ for (size_t i = 0; i < len && s[i] != NUL; i += clen) {
+ clen = utf_ptr2len_len(s+i, len-i);
+ // NB: gets the byte value of invalid sequence bytes.
+ // we only care whether the char fits in the BMP or not
+ int c = (clen > 1) ? utf_ptr2char(s+i) : s[i];
+ count++;
+ if (c > 0xFFFF) {
+ extra++;
+ }
+ }
+ *codepoints += count;
+ *codeunits += count + extra;
+}
+
+ssize_t mb_utf_index_to_bytes(const char_u *s, size_t len,
+ size_t index, bool use_utf16_units)
+ FUNC_ATTR_NONNULL_ALL
+{
+ size_t count = 0;
+ size_t clen, i;
+ if (index == 0) {
+ return 0;
+ }
+ for (i = 0; i < len && s[i] != NUL; i += clen) {
+ clen = utf_ptr2len_len(s+i, len-i);
+ // NB: gets the byte value of invalid sequence bytes.
+ // we only care whether the char fits in the BMP or not
+ int c = (clen > 1) ? utf_ptr2char(s+i) : s[i];
+ count++;
+ if (use_utf16_units && c > 0xFFFF) {
+ count++;
+ }
+ if (count >= index) {
+ return i+clen;
+ }
+ }
+ return -1;
+}
+
+
/*
* Version of strnicmp() that handles multi-byte characters.
* Needed for Big5, Shift-JIS and UTF-8 encoding. Other DBCS encodings can
@@ -1997,7 +2062,7 @@ enc_locale_copy_enc:
return enc_canonize((char_u *)buf);
}
-# if defined(USE_ICONV)
+# if defined(HAVE_ICONV)
/*
@@ -2018,13 +2083,6 @@ void * my_iconv_open(char_u *to, char_u *from)
if (iconv_working == kBroken)
return (void *)-1; /* detected a broken iconv() previously */
-#ifdef DYNAMIC_ICONV
- // Check if the iconv.dll can be found.
- if (!iconv_enabled(true)) {
- return (void *)-1;
- }
-#endif
-
fd = iconv_open((char *)enc_skip(to), (char *)enc_skip(from));
if (fd != (iconv_t)-1 && iconv_working == kUnknown) {
@@ -2131,152 +2189,7 @@ static char_u *iconv_string(const vimconv_T *const vcp, char_u *str,
return result;
}
-# if defined(DYNAMIC_ICONV)
-// Dynamically load the "iconv.dll" on Win32.
-
-#ifndef DYNAMIC_ICONV // just generating prototypes
-# define HINSTANCE int
-#endif
-static HINSTANCE hIconvDLL = 0;
-static HINSTANCE hMsvcrtDLL = 0;
-
-# ifndef DYNAMIC_ICONV_DLL
-# define DYNAMIC_ICONV_DLL "iconv.dll"
-# define DYNAMIC_ICONV_DLL_ALT "libiconv-2.dll"
-# endif
-# ifndef DYNAMIC_MSVCRT_DLL
-# define DYNAMIC_MSVCRT_DLL "msvcrt.dll"
-# endif
-
-/*
- * Get the address of 'funcname' which is imported by 'hInst' DLL.
- */
-static void * get_iconv_import_func(HINSTANCE hInst,
- const char *funcname)
-{
- PBYTE pImage = (PBYTE)hInst;
- PIMAGE_DOS_HEADER pDOS = (PIMAGE_DOS_HEADER)hInst;
- PIMAGE_NT_HEADERS pPE;
- PIMAGE_IMPORT_DESCRIPTOR pImpDesc;
- PIMAGE_THUNK_DATA pIAT; /* Import Address Table */
- PIMAGE_THUNK_DATA pINT; /* Import Name Table */
- PIMAGE_IMPORT_BY_NAME pImpName;
-
- if (pDOS->e_magic != IMAGE_DOS_SIGNATURE)
- return NULL;
- pPE = (PIMAGE_NT_HEADERS)(pImage + pDOS->e_lfanew);
- if (pPE->Signature != IMAGE_NT_SIGNATURE)
- return NULL;
- pImpDesc = (PIMAGE_IMPORT_DESCRIPTOR)(pImage
- + pPE->OptionalHeader.DataDirectory[
- IMAGE_DIRECTORY_ENTRY_IMPORT]
- .VirtualAddress);
- for (; pImpDesc->FirstThunk; ++pImpDesc) {
- if (!pImpDesc->OriginalFirstThunk)
- continue;
- pIAT = (PIMAGE_THUNK_DATA)(pImage + pImpDesc->FirstThunk);
- pINT = (PIMAGE_THUNK_DATA)(pImage + pImpDesc->OriginalFirstThunk);
- for (; pIAT->u1.Function; ++pIAT, ++pINT) {
- if (IMAGE_SNAP_BY_ORDINAL(pINT->u1.Ordinal))
- continue;
- pImpName = (PIMAGE_IMPORT_BY_NAME)(pImage
- + (UINT_PTR)(pINT->u1.AddressOfData));
- if (strcmp(pImpName->Name, funcname) == 0)
- return (void *)pIAT->u1.Function;
- }
- }
- return NULL;
-}
-
-// Load library "name".
-HINSTANCE vimLoadLib(char *name)
-{
- HINSTANCE dll = NULL;
-
- // NOTE: Do not use mch_dirname() and mch_chdir() here, they may call
- // vimLoadLib() recursively, which causes a stack overflow.
- wchar_t old_dirw[MAXPATHL];
-
- // Path to exe dir.
- char *buf = xstrdup((char *)get_vim_var_str(VV_PROGPATH));
- // ptrdiff_t len = ;
- // assert(len > 0);
- buf[path_tail_with_sep(buf) - buf] = '\0';
-
- if (GetCurrentDirectoryW(MAXPATHL, old_dirw) != 0) {
- // Change directory to where the executable is, both to make
- // sure we find a .dll there and to avoid looking for a .dll
- // in the current directory.
- SetCurrentDirectory((LPCSTR)buf);
- // TODO(justinmk): use uv_dlopen instead. see os_libcall
- dll = LoadLibrary(name);
- SetCurrentDirectoryW(old_dirw);
- }
-
- return dll;
-}
-
-
-/*
- * Try opening the iconv.dll and return TRUE if iconv() can be used.
- */
-bool iconv_enabled(bool verbose)
-{
- if (hIconvDLL != 0 && hMsvcrtDLL != 0)
- return true;
- hIconvDLL = vimLoadLib(DYNAMIC_ICONV_DLL);
- if (hIconvDLL == 0) /* sometimes it's called libiconv.dll */
- hIconvDLL = vimLoadLib(DYNAMIC_ICONV_DLL_ALT);
- if (hIconvDLL != 0)
- hMsvcrtDLL = vimLoadLib(DYNAMIC_MSVCRT_DLL);
- if (hIconvDLL == 0 || hMsvcrtDLL == 0) {
- /* Only give the message when 'verbose' is set, otherwise it might be
- * done whenever a conversion is attempted. */
- if (verbose && p_verbose > 0) {
- verbose_enter();
- EMSG2(_(e_loadlib),
- hIconvDLL == 0 ? DYNAMIC_ICONV_DLL : DYNAMIC_MSVCRT_DLL);
- verbose_leave();
- }
- iconv_end();
- return false;
- }
-
- iconv = (void *)GetProcAddress(hIconvDLL, "libiconv");
- iconv_open = (void *)GetProcAddress(hIconvDLL, "libiconv_open");
- iconv_close = (void *)GetProcAddress(hIconvDLL, "libiconv_close");
- iconvctl = (void *)GetProcAddress(hIconvDLL, "libiconvctl");
- iconv_errno = get_iconv_import_func(hIconvDLL, "_errno");
- if (iconv_errno == NULL)
- iconv_errno = (void *)GetProcAddress(hMsvcrtDLL, "_errno");
- if (iconv == NULL || iconv_open == NULL || iconv_close == NULL
- || iconvctl == NULL || iconv_errno == NULL) {
- iconv_end();
- if (verbose && p_verbose > 0) {
- verbose_enter();
- EMSG2(_(e_loadfunc), "for libiconv");
- verbose_leave();
- }
- return false;
- }
- return true;
-}
-
-void iconv_end(void)
-{
- if (hIconvDLL != 0) {
- // TODO(justinmk): use uv_dlclose instead.
- FreeLibrary(hIconvDLL);
- }
- if (hMsvcrtDLL != 0) {
- FreeLibrary(hMsvcrtDLL);
- }
- hIconvDLL = 0;
- hMsvcrtDLL = 0;
-}
-
-# endif /* DYNAMIC_ICONV */
-# endif /* USE_ICONV */
+# endif // HAVE_ICONV
@@ -2307,10 +2220,11 @@ int convert_setup_ext(vimconv_T *vcp, char_u *from, bool from_unicode_is_utf8,
int from_is_utf8;
int to_is_utf8;
- /* Reset to no conversion. */
-# ifdef USE_ICONV
- if (vcp->vc_type == CONV_ICONV && vcp->vc_fd != (iconv_t)-1)
+ // Reset to no conversion.
+# ifdef HAVE_ICONV
+ if (vcp->vc_type == CONV_ICONV && vcp->vc_fd != (iconv_t)-1) {
iconv_close(vcp->vc_fd);
+ }
# endif
*vcp = (vimconv_T)MBYTE_NONE_CONV;
@@ -2345,9 +2259,9 @@ int convert_setup_ext(vimconv_T *vcp, char_u *from, bool from_unicode_is_utf8,
/* Internal utf-8 -> latin9 conversion. */
vcp->vc_type = CONV_TO_LATIN9;
}
-# ifdef USE_ICONV
- else {
- /* Use iconv() for conversion. */
+# ifdef HAVE_ICONV
+ else { // NOLINT(readability/braces)
+ // Use iconv() for conversion.
vcp->vc_fd = (iconv_t)my_iconv_open(
to_is_utf8 ? (char_u *)"utf-8" : to,
from_is_utf8 ? (char_u *)"utf-8" : from);
@@ -2499,8 +2413,8 @@ char_u * string_convert_ext(const vimconv_T *const vcp, char_u *ptr,
*lenp = (size_t)(d - retval);
break;
-# ifdef USE_ICONV
- case CONV_ICONV: /* conversion with vcp->vc_fd */
+# ifdef HAVE_ICONV
+ case CONV_ICONV: // conversion with vcp->vc_fd
retval = iconv_string(vcp, ptr, len, unconvlenp, lenp);
break;
# endif
diff --git a/src/nvim/mbyte.h b/src/nvim/mbyte.h
index ed48705c6d..536d58be1f 100644
--- a/src/nvim/mbyte.h
+++ b/src/nvim/mbyte.h
@@ -63,7 +63,7 @@ typedef enum {
typedef struct {
int vc_type; ///< Zero or more ConvFlags.
int vc_factor; ///< Maximal expansion factor.
-# ifdef USE_ICONV
+# ifdef HAVE_ICONV
iconv_t vc_fd; ///< Value for CONV_ICONV.
# endif
bool vc_fail; ///< What to do with invalid characters: if true, fail,
diff --git a/src/nvim/memline.c b/src/nvim/memline.c
index a69669f680..34774055c1 100644
--- a/src/nvim/memline.c
+++ b/src/nvim/memline.c
@@ -1,9 +1,9 @@
// 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
-/* for debugging */
-/* #define CHECK(c, s) if (c) EMSG(s) */
-#define CHECK(c, s)
+// for debugging
+// #define CHECK(c, s) do { if (c) EMSG(s); } while (0)
+#define CHECK(c, s) do { } while (0)
/*
* memline.c: Contains the functions for appending, deleting and changing the
@@ -47,6 +47,7 @@
#include "nvim/vim.h"
#include "nvim/memline.h"
#include "nvim/buffer.h"
+#include "nvim/change.h"
#include "nvim/cursor.h"
#include "nvim/eval.h"
#include "nvim/getchar.h"
@@ -1178,7 +1179,7 @@ void ml_recover(void)
/* Recovering an empty file results in two lines and the first line is
* empty. Don't set the modified flag then. */
if (!(curbuf->b_ml.ml_line_count == 2 && *ml_get(1) == NUL)) {
- changed_int();
+ changed_internal();
buf_inc_changedtick(curbuf);
}
} else {
@@ -1188,7 +1189,7 @@ void ml_recover(void)
i = STRCMP(p, ml_get(idx + lnum));
xfree(p);
if (i != 0) {
- changed_int();
+ changed_internal();
buf_inc_changedtick(curbuf);
break;
}
@@ -2383,6 +2384,23 @@ static int ml_append_int(
return OK;
}
+void ml_add_deleted_len(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++;
+ }
+}
+
/*
* Replace line lnum, with buffering, in current buffer.
*
@@ -2403,13 +2421,24 @@ int ml_replace(linenr_T lnum, char_u *line, bool copy)
if (curbuf->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 */
- xfree(curbuf->b_ml.ml_line_ptr); /* free it */
+ 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);
+ readlen = false; // already added the length
+
+ xfree(curbuf->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);
+ }
+
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;
@@ -2491,6 +2520,10 @@ static int ml_delete_int(buf_T *buf, linenr_T lnum, bool message)
else
line_size = ((dp->db_index[idx - 1]) & DB_INDEX_MASK) - line_start;
+ // Line should always have an NL char internally (represented as NUL),
+ // even if 'noeol' is set.
+ assert(line_size >= 1);
+ ml_add_deleted_len((char_u *)dp + line_start, line_size-1);
/*
* special case: If there is only one line in the data block it becomes empty.
@@ -2676,6 +2709,17 @@ void ml_clearmarked(void)
return;
}
+size_t ml_flush_deleted_bytes(buf_T *buf, size_t *codepoints, size_t *codeunits)
+{
+ size_t ret = buf->deleted_bytes;
+ *codepoints = buf->deleted_codepoints;
+ *codeunits = buf->deleted_codeunits;
+ buf->deleted_bytes = 0;
+ buf->deleted_codepoints = 0;
+ buf->deleted_codeunits = 0;
+ return ret;
+}
+
/*
* flush ml_line if necessary
*/
@@ -2704,6 +2748,8 @@ static void ml_flush_line(buf_T *buf)
return;
entered = TRUE;
+ buf->flush_count++;
+
lnum = buf->b_ml.ml_line_lnum;
new_line = buf->b_ml.ml_line_ptr;
diff --git a/src/nvim/memory.c b/src/nvim/memory.c
index dced03f3d5..64aae71433 100644
--- a/src/nvim/memory.c
+++ b/src/nvim/memory.c
@@ -9,6 +9,7 @@
#include <stdbool.h>
#include "nvim/vim.h"
+#include "nvim/context.h"
#include "nvim/eval.h"
#include "nvim/highlight.h"
#include "nvim/memfile.h"
@@ -648,7 +649,7 @@ void free_all_mem(void)
// Free all option values. Must come after closing windows.
free_all_options();
- free_cmdline_buf();
+ free_arshape_buf();
/* Clear registers. */
clear_registers();
@@ -671,6 +672,7 @@ void free_all_mem(void)
eval_clear();
api_vim_free_all_mem();
+ ctx_free_all();
// Free all buffers. Reset 'autochdir' to avoid accessing things that
// were freed already.
diff --git a/src/nvim/message.c b/src/nvim/message.c
index e1164ad326..b043df17d3 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -116,6 +116,7 @@ static const char *msg_ext_kind = NULL;
static Array msg_ext_chunks = ARRAY_DICT_INIT;
static garray_T msg_ext_last_chunk = GA_INIT(sizeof(char), 40);
static sattr_T msg_ext_last_attr = -1;
+static size_t msg_ext_cur_len = 0;
static bool msg_ext_overwrite = false; ///< will overwrite last message
static int msg_ext_visible = 0; ///< number of messages currently visible
@@ -1877,8 +1878,9 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr,
msg_ext_last_attr = attr;
}
// Concat pieces with the same highlight
- ga_concat_len(&msg_ext_last_chunk, (char *)str,
- strnlen((char *)str, maxlen)); // -V781
+ size_t len = strnlen((char *)str, maxlen); // -V781
+ ga_concat_len(&msg_ext_last_chunk, (char *)str, len);
+ msg_ext_cur_len += len;
return;
}
@@ -2323,8 +2325,8 @@ static void msg_puts_printf(const char *str, const ptrdiff_t maxlen)
}
}
- // primitive way to compute the current column
int cw = utf_char2cells(utf_ptr2char((const char_u *)s));
+ // primitive way to compute the current column
if (cmdmsg_rl) {
if (*s == '\r' || *s == '\n') {
msg_col = Columns - 1;
@@ -2567,6 +2569,7 @@ void mch_errmsg(char *str)
}
}
+// Give a message. To be used when the UI is not initialized yet.
void mch_msg(char *str)
{
wchar_t *utf16str;
@@ -2722,6 +2725,7 @@ void msg_ext_ui_flush(void)
}
msg_ext_kind = NULL;
msg_ext_chunks = (Array)ARRAY_DICT_INIT;
+ msg_ext_cur_len = 0;
msg_ext_overwrite = false;
}
}
@@ -2734,6 +2738,7 @@ void msg_ext_flush_showmode(void)
msg_ext_emit_chunk();
ui_call_msg_showmode(msg_ext_chunks);
msg_ext_chunks = (Array)ARRAY_DICT_INIT;
+ msg_ext_cur_len = 0;
}
}
@@ -2940,7 +2945,7 @@ int verbose_open(void)
/* Only give the error message once. */
verbose_did_open = TRUE;
- verbose_fd = mch_fopen((char *)p_vfile, "a");
+ verbose_fd = os_fopen((char *)p_vfile, "a");
if (verbose_fd == NULL) {
EMSG2(_(e_notopen), p_vfile);
return FAIL;
@@ -2970,7 +2975,10 @@ void give_warning(char_u *message, bool hl) FUNC_ATTR_NONNULL_ARG(1)
} else {
keep_msg_attr = 0;
}
- msg_ext_set_kind("wmsg");
+
+ if (msg_ext_kind == NULL) {
+ msg_ext_set_kind("wmsg");
+ }
if (msg_attr((const char *)message, keep_msg_attr) && msg_scrolled == 0) {
set_keep_msg(message, keep_msg_attr);
@@ -2997,6 +3005,14 @@ void msg_advance(int col)
msg_col = col; /* for redirection, may fill it up later */
return;
}
+ if (ui_has(kUIMessages)) {
+ // TODO(bfredl): use byte count as a basic proxy.
+ // later on we might add proper support for formatted messages.
+ while (msg_ext_cur_len < (size_t)col) {
+ msg_putchar(' ');
+ }
+ return;
+ }
if (col >= Columns) /* not enough room */
col = Columns - 1;
if (cmdmsg_rl)
diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c
index 45e03681eb..44e2c7df5f 100644
--- a/src/nvim/misc1.c
+++ b/src/nvim/misc1.c
@@ -65,872 +65,6 @@
static garray_T ga_users = GA_EMPTY_INIT_VALUE;
/*
- * open_line: Add a new line below or above the current line.
- *
- * For VREPLACE mode, we only add a new line when we get to the end of the
- * file, otherwise we just start replacing the next line.
- *
- * Caller must take care of undo. Since VREPLACE may affect any number of
- * lines however, it may call u_save_cursor() again when starting to change a
- * new line.
- * "flags": OPENLINE_DELSPACES delete spaces after cursor
- * OPENLINE_DO_COM format comments
- * OPENLINE_KEEPTRAIL keep trailing spaces
- * OPENLINE_MARKFIX adjust mark positions after the line break
- * OPENLINE_COM_LIST format comments with list or 2nd line indent
- *
- * "second_line_indent": indent for after ^^D in Insert mode or if flag
- * OPENLINE_COM_LIST
- *
- * Return TRUE for success, FALSE for failure
- */
-int
-open_line (
- int dir, /* FORWARD or BACKWARD */
- int flags,
- int second_line_indent
-)
-{
- char_u *next_line = NULL; // copy of the next line
- char_u *p_extra = NULL; // what goes to next line
- colnr_T less_cols = 0; // less columns for mark in new line
- colnr_T less_cols_off = 0; // columns to skip for mark adjust
- pos_T old_cursor; // old cursor position
- colnr_T newcol = 0; // new cursor column
- int newindent = 0; // auto-indent of the new line
- bool trunc_line = false; // truncate current line afterwards
- bool retval = false; // return value
- int extra_len = 0; // length of p_extra string
- int lead_len; // length of comment leader
- char_u *lead_flags; // position in 'comments' for comment leader
- char_u *leader = NULL; // copy of comment leader
- char_u *allocated = NULL; // allocated memory
- char_u *p;
- char_u saved_char = NUL; // init for GCC
- pos_T *pos;
- bool do_si = (!p_paste && curbuf->b_p_si && !curbuf->b_p_cin
- && *curbuf->b_p_inde == NUL);
- bool no_si = false; // reset did_si afterwards
- int first_char = NUL; // init for GCC
- int vreplace_mode;
- bool did_append; // appended a new line
- int saved_pi = curbuf->b_p_pi; // copy of preserveindent setting
-
- // make a copy of the current line so we can mess with it
- char_u *saved_line = vim_strsave(get_cursor_line_ptr());
-
- if (State & VREPLACE_FLAG) {
- /*
- * With VREPLACE we make a copy of the next line, which we will be
- * starting to replace. First make the new line empty and let vim play
- * with the indenting and comment leader to its heart's content. Then
- * we grab what it ended up putting on the new line, put back the
- * original line, and call ins_char() to put each new character onto
- * the line, replacing what was there before and pushing the right
- * stuff onto the replace stack. -- webb.
- */
- if (curwin->w_cursor.lnum < orig_line_count)
- next_line = vim_strsave(ml_get(curwin->w_cursor.lnum + 1));
- else
- next_line = vim_strsave((char_u *)"");
-
- /*
- * In VREPLACE mode, a NL replaces the rest of the line, and starts
- * replacing the next line, so push all of the characters left on the
- * line onto the replace stack. We'll push any other characters that
- * might be replaced at the start of the next line (due to autoindent
- * etc) a bit later.
- */
- replace_push(NUL); /* Call twice because BS over NL expects it */
- replace_push(NUL);
- p = saved_line + curwin->w_cursor.col;
- while (*p != NUL) {
- if (has_mbyte)
- p += replace_push_mb(p);
- else
- replace_push(*p++);
- }
- saved_line[curwin->w_cursor.col] = NUL;
- }
-
- if ((State & INSERT)
- && !(State & VREPLACE_FLAG)
- ) {
- p_extra = saved_line + curwin->w_cursor.col;
- if (do_si) { /* need first char after new line break */
- p = skipwhite(p_extra);
- first_char = *p;
- }
- extra_len = (int)STRLEN(p_extra);
- saved_char = *p_extra;
- *p_extra = NUL;
- }
-
- u_clearline(); // cannot do "U" command when adding lines
- did_si = false;
- ai_col = 0;
-
- /*
- * If we just did an auto-indent, then we didn't type anything on
- * the prior line, and it should be truncated. Do this even if 'ai' is not
- * set because automatically inserting a comment leader also sets did_ai.
- */
- if (dir == FORWARD && did_ai)
- trunc_line = TRUE;
-
- /*
- * If 'autoindent' and/or 'smartindent' is set, try to figure out what
- * indent to use for the new line.
- */
- if (curbuf->b_p_ai
- || do_si
- ) {
- // count white space on current line
- newindent = get_indent_str(saved_line, (int)curbuf->b_p_ts, false);
- if (newindent == 0 && !(flags & OPENLINE_COM_LIST)) {
- newindent = second_line_indent; // for ^^D command in insert mode
- }
-
- /*
- * Do smart indenting.
- * In insert/replace mode (only when dir == FORWARD)
- * we may move some text to the next line. If it starts with '{'
- * don't add an indent. Fixes inserting a NL before '{' in line
- * "if (condition) {"
- */
- if (!trunc_line && do_si && *saved_line != NUL
- && (p_extra == NULL || first_char != '{')) {
- char_u *ptr;
- char_u last_char;
-
- old_cursor = curwin->w_cursor;
- ptr = saved_line;
- if (flags & OPENLINE_DO_COM)
- lead_len = get_leader_len(ptr, NULL, FALSE, TRUE);
- else
- lead_len = 0;
- if (dir == FORWARD) {
- // Skip preprocessor directives, unless they are
- // recognised as comments.
- if (lead_len == 0 && ptr[0] == '#') {
- while (ptr[0] == '#' && curwin->w_cursor.lnum > 1) {
- ptr = ml_get(--curwin->w_cursor.lnum);
- }
- newindent = get_indent();
- }
- if (flags & OPENLINE_DO_COM)
- lead_len = get_leader_len(ptr, NULL, FALSE, TRUE);
- else
- lead_len = 0;
- if (lead_len > 0) {
- /*
- * This case gets the following right:
- * \*
- * * A comment (read '\' as '/').
- * *\
- * #define IN_THE_WAY
- * This should line up here;
- */
- p = skipwhite(ptr);
- if (p[0] == '/' && p[1] == '*')
- p++;
- if (p[0] == '*') {
- for (p++; *p; p++) {
- if (p[0] == '/' && p[-1] == '*') {
- /*
- * End of C comment, indent should line up
- * with the line containing the start of
- * the comment
- */
- curwin->w_cursor.col = (colnr_T)(p - ptr);
- if ((pos = findmatch(NULL, NUL)) != NULL) {
- curwin->w_cursor.lnum = pos->lnum;
- newindent = get_indent();
- }
- }
- }
- }
- } else { /* Not a comment line */
- /* Find last non-blank in line */
- p = ptr + STRLEN(ptr) - 1;
- while (p > ptr && ascii_iswhite(*p))
- --p;
- last_char = *p;
-
- /*
- * find the character just before the '{' or ';'
- */
- if (last_char == '{' || last_char == ';') {
- if (p > ptr)
- --p;
- while (p > ptr && ascii_iswhite(*p))
- --p;
- }
- /*
- * Try to catch lines that are split over multiple
- * lines. eg:
- * if (condition &&
- * condition) {
- * Should line up here!
- * }
- */
- if (*p == ')') {
- curwin->w_cursor.col = (colnr_T)(p - ptr);
- if ((pos = findmatch(NULL, '(')) != NULL) {
- curwin->w_cursor.lnum = pos->lnum;
- newindent = get_indent();
- ptr = get_cursor_line_ptr();
- }
- }
- /*
- * If last character is '{' do indent, without
- * checking for "if" and the like.
- */
- if (last_char == '{') {
- did_si = true; // do indent
- no_si = true; // don't delete it when '{' typed
- }
- /*
- * Look for "if" and the like, use 'cinwords'.
- * Don't do this if the previous line ended in ';' or
- * '}'.
- */
- else if (last_char != ';' && last_char != '}'
- && cin_is_cinword(ptr))
- did_si = true;
- }
- } else { // dir == BACKWARD
- // Skip preprocessor directives, unless they are
- // recognised as comments.
- if (lead_len == 0 && ptr[0] == '#') {
- bool was_backslashed = false;
-
- while ((ptr[0] == '#' || was_backslashed)
- && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) {
- if (*ptr && ptr[STRLEN(ptr) - 1] == '\\') {
- was_backslashed = true;
- } else {
- was_backslashed = false;
- }
- ptr = ml_get(++curwin->w_cursor.lnum);
- }
- if (was_backslashed) {
- newindent = 0; // Got to end of file
- } else {
- newindent = get_indent();
- }
- }
- p = skipwhite(ptr);
- if (*p == '}') { // if line starts with '}': do indent
- did_si = true;
- } else { // can delete indent when '{' typed
- can_si_back = true;
- }
- }
- curwin->w_cursor = old_cursor;
- }
- if (do_si) {
- can_si = true;
- }
-
- did_ai = true;
- }
-
- /*
- * Find out if the current line starts with a comment leader.
- * This may then be inserted in front of the new line.
- */
- end_comment_pending = NUL;
- if (flags & OPENLINE_DO_COM)
- lead_len = get_leader_len(saved_line, &lead_flags, dir == BACKWARD, TRUE);
- else
- lead_len = 0;
- if (lead_len > 0) {
- char_u *lead_repl = NULL; /* replaces comment leader */
- int lead_repl_len = 0; /* length of *lead_repl */
- char_u lead_middle[COM_MAX_LEN]; /* middle-comment string */
- char_u lead_end[COM_MAX_LEN]; /* end-comment string */
- char_u *comment_end = NULL; /* where lead_end has been found */
- int extra_space = FALSE; /* append extra space */
- int current_flag;
- int require_blank = FALSE; /* requires blank after middle */
- char_u *p2;
-
- /*
- * If the comment leader has the start, middle or end flag, it may not
- * be used or may be replaced with the middle leader.
- */
- for (p = lead_flags; *p && *p != ':'; ++p) {
- if (*p == COM_BLANK) {
- require_blank = TRUE;
- continue;
- }
- if (*p == COM_START || *p == COM_MIDDLE) {
- current_flag = *p;
- if (*p == COM_START) {
- /*
- * Doing "O" on a start of comment does not insert leader.
- */
- if (dir == BACKWARD) {
- lead_len = 0;
- break;
- }
-
- /* find start of middle part */
- (void)copy_option_part(&p, lead_middle, COM_MAX_LEN, ",");
- require_blank = FALSE;
- }
-
- /*
- * Isolate the strings of the middle and end leader.
- */
- while (*p && p[-1] != ':') { /* find end of middle flags */
- if (*p == COM_BLANK)
- require_blank = TRUE;
- ++p;
- }
- (void)copy_option_part(&p, lead_middle, COM_MAX_LEN, ",");
-
- while (*p && p[-1] != ':') { /* find end of end flags */
- /* Check whether we allow automatic ending of comments */
- if (*p == COM_AUTO_END)
- end_comment_pending = -1; /* means we want to set it */
- ++p;
- }
- size_t n = copy_option_part(&p, lead_end, COM_MAX_LEN, ",");
-
- if (end_comment_pending == -1) /* we can set it now */
- end_comment_pending = lead_end[n - 1];
-
- /*
- * If the end of the comment is in the same line, don't use
- * the comment leader.
- */
- if (dir == FORWARD) {
- for (p = saved_line + lead_len; *p; ++p)
- if (STRNCMP(p, lead_end, n) == 0) {
- comment_end = p;
- lead_len = 0;
- break;
- }
- }
-
- /*
- * Doing "o" on a start of comment inserts the middle leader.
- */
- if (lead_len > 0) {
- if (current_flag == COM_START) {
- lead_repl = lead_middle;
- lead_repl_len = (int)STRLEN(lead_middle);
- }
-
- /*
- * If we have hit RETURN immediately after the start
- * comment leader, then put a space after the middle
- * comment leader on the next line.
- */
- if (!ascii_iswhite(saved_line[lead_len - 1])
- && ((p_extra != NULL
- && (int)curwin->w_cursor.col == lead_len)
- || (p_extra == NULL
- && saved_line[lead_len] == NUL)
- || require_blank))
- extra_space = TRUE;
- }
- break;
- }
- if (*p == COM_END) {
- /*
- * Doing "o" on the end of a comment does not insert leader.
- * Remember where the end is, might want to use it to find the
- * start (for C-comments).
- */
- if (dir == FORWARD) {
- comment_end = skipwhite(saved_line);
- lead_len = 0;
- break;
- }
-
- /*
- * Doing "O" on the end of a comment inserts the middle leader.
- * Find the string for the middle leader, searching backwards.
- */
- while (p > curbuf->b_p_com && *p != ',')
- --p;
- for (lead_repl = p; lead_repl > curbuf->b_p_com
- && lead_repl[-1] != ':'; --lead_repl)
- ;
- lead_repl_len = (int)(p - lead_repl);
-
- /* We can probably always add an extra space when doing "O" on
- * the comment-end */
- extra_space = TRUE;
-
- /* Check whether we allow automatic ending of comments */
- for (p2 = p; *p2 && *p2 != ':'; p2++) {
- if (*p2 == COM_AUTO_END)
- end_comment_pending = -1; /* means we want to set it */
- }
- if (end_comment_pending == -1) {
- /* Find last character in end-comment string */
- while (*p2 && *p2 != ',')
- p2++;
- end_comment_pending = p2[-1];
- }
- break;
- }
- if (*p == COM_FIRST) {
- /*
- * Comment leader for first line only: Don't repeat leader
- * when using "O", blank out leader when using "o".
- */
- if (dir == BACKWARD)
- lead_len = 0;
- else {
- lead_repl = (char_u *)"";
- lead_repl_len = 0;
- }
- break;
- }
- }
- if (lead_len > 0) {
- // allocate buffer (may concatenate p_extra later)
- leader = xmalloc((size_t)(lead_len + lead_repl_len + extra_space
- + extra_len + (second_line_indent > 0
- ? second_line_indent : 0) + 1));
- allocated = leader; // remember to free it later
-
- STRLCPY(leader, saved_line, lead_len + 1);
-
- /*
- * Replace leader with lead_repl, right or left adjusted
- */
- if (lead_repl != NULL) {
- int c = 0;
- int off = 0;
-
- for (p = lead_flags; *p != NUL && *p != ':'; ) {
- if (*p == COM_RIGHT || *p == COM_LEFT)
- c = *p++;
- else if (ascii_isdigit(*p) || *p == '-')
- off = getdigits_int(&p);
- else
- ++p;
- }
- if (c == COM_RIGHT) { /* right adjusted leader */
- /* find last non-white in the leader to line up with */
- for (p = leader + lead_len - 1; p > leader
- && ascii_iswhite(*p); --p)
- ;
- ++p;
-
- /* Compute the length of the replaced characters in
- * screen characters, not bytes. */
- {
- int repl_size = vim_strnsize(lead_repl,
- lead_repl_len);
- int old_size = 0;
- char_u *endp = p;
- int l;
-
- while (old_size < repl_size && p > leader) {
- MB_PTR_BACK(leader, p);
- old_size += ptr2cells(p);
- }
- l = lead_repl_len - (int)(endp - p);
- if (l != 0)
- memmove(endp + l, endp,
- (size_t)((leader + lead_len) - endp));
- lead_len += l;
- }
- memmove(p, lead_repl, (size_t)lead_repl_len);
- if (p + lead_repl_len > leader + lead_len)
- p[lead_repl_len] = NUL;
-
- /* blank-out any other chars from the old leader. */
- while (--p >= leader) {
- int l = utf_head_off(leader, p);
-
- if (l > 1) {
- p -= l;
- if (ptr2cells(p) > 1) {
- p[1] = ' ';
- --l;
- }
- memmove(p + 1, p + l + 1,
- (size_t)((leader + lead_len) - (p + l + 1)));
- lead_len -= l;
- *p = ' ';
- } else if (!ascii_iswhite(*p))
- *p = ' ';
- }
- } else { /* left adjusted leader */
- p = skipwhite(leader);
- /* Compute the length of the replaced characters in
- * screen characters, not bytes. Move the part that is
- * not to be overwritten. */
- {
- int repl_size = vim_strnsize(lead_repl,
- lead_repl_len);
- int i;
- int l;
-
- for (i = 0; i < lead_len && p[i] != NUL; i += l) {
- l = (*mb_ptr2len)(p + i);
- if (vim_strnsize(p, i + l) > repl_size)
- break;
- }
- if (i != lead_repl_len) {
- memmove(p + lead_repl_len, p + i,
- (size_t)(lead_len - i - (p - leader)));
- lead_len += lead_repl_len - i;
- }
- }
- memmove(p, lead_repl, (size_t)lead_repl_len);
-
- /* Replace any remaining non-white chars in the old
- * leader by spaces. Keep Tabs, the indent must
- * remain the same. */
- for (p += lead_repl_len; p < leader + lead_len; ++p)
- if (!ascii_iswhite(*p)) {
- /* Don't put a space before a TAB. */
- if (p + 1 < leader + lead_len && p[1] == TAB) {
- lead_len--;
- memmove(p, p + 1, (size_t)(leader + lead_len - p));
- } else {
- int l = (*mb_ptr2len)(p);
-
- if (l > 1) {
- if (ptr2cells(p) > 1) {
- /* Replace a double-wide char with
- * two spaces */
- --l;
- *p++ = ' ';
- }
- memmove(p + 1, p + l, (size_t)(leader + lead_len - p));
- lead_len -= l - 1;
- }
- *p = ' ';
- }
- }
- *p = NUL;
- }
-
- /* Recompute the indent, it may have changed. */
- if (curbuf->b_p_ai
- || do_si
- )
- newindent = get_indent_str(leader, (int)curbuf->b_p_ts, false);
-
- /* Add the indent offset */
- if (newindent + off < 0) {
- off = -newindent;
- newindent = 0;
- } else
- newindent += off;
-
- /* Correct trailing spaces for the shift, so that
- * alignment remains equal. */
- while (off > 0 && lead_len > 0
- && leader[lead_len - 1] == ' ') {
- /* Don't do it when there is a tab before the space */
- if (vim_strchr(skipwhite(leader), '\t') != NULL)
- break;
- --lead_len;
- --off;
- }
-
- /* If the leader ends in white space, don't add an
- * extra space */
- if (lead_len > 0 && ascii_iswhite(leader[lead_len - 1]))
- extra_space = FALSE;
- leader[lead_len] = NUL;
- }
-
- if (extra_space) {
- leader[lead_len++] = ' ';
- leader[lead_len] = NUL;
- }
-
- newcol = lead_len;
-
- /*
- * if a new indent will be set below, remove the indent that
- * is in the comment leader
- */
- if (newindent
- || did_si
- ) {
- while (lead_len && ascii_iswhite(*leader)) {
- --lead_len;
- --newcol;
- ++leader;
- }
- }
-
- did_si = can_si = false;
- } else if (comment_end != NULL) {
- // We have finished a comment, so we don't use the leader.
- // If this was a C-comment and 'ai' or 'si' is set do a normal
- // indent to align with the line containing the start of the
- // comment.
- if (comment_end[0] == '*' && comment_end[1] == '/'
- && (curbuf->b_p_ai || do_si)) {
- old_cursor = curwin->w_cursor;
- curwin->w_cursor.col = (colnr_T)(comment_end - saved_line);
- if ((pos = findmatch(NULL, NUL)) != NULL) {
- curwin->w_cursor.lnum = pos->lnum;
- newindent = get_indent();
- }
- curwin->w_cursor = old_cursor;
- }
- }
- }
-
- /* (State == INSERT || State == REPLACE), only when dir == FORWARD */
- if (p_extra != NULL) {
- *p_extra = saved_char; /* restore char that NUL replaced */
-
- /*
- * When 'ai' set or "flags" has OPENLINE_DELSPACES, skip to the first
- * non-blank.
- *
- * When in REPLACE mode, put the deleted blanks on the replace stack,
- * preceded by a NUL, so they can be put back when a BS is entered.
- */
- if (REPLACE_NORMAL(State))
- replace_push(NUL); /* end of extra blanks */
- if (curbuf->b_p_ai || (flags & OPENLINE_DELSPACES)) {
- while ((*p_extra == ' ' || *p_extra == '\t')
- && !utf_iscomposing(utf_ptr2char(p_extra + 1))) {
- if (REPLACE_NORMAL(State)) {
- replace_push(*p_extra);
- }
- p_extra++;
- less_cols_off++;
- }
- }
-
- /* columns for marks adjusted for removed columns */
- less_cols = (int)(p_extra - saved_line);
- }
-
- if (p_extra == NULL)
- p_extra = (char_u *)""; /* append empty line */
-
- /* concatenate leader and p_extra, if there is a leader */
- if (lead_len > 0) {
- if (flags & OPENLINE_COM_LIST && second_line_indent > 0) {
- int i;
- int padding = second_line_indent
- - (newindent + (int)STRLEN(leader));
-
- /* Here whitespace is inserted after the comment char.
- * Below, set_indent(newindent, SIN_INSERT) will insert the
- * whitespace needed before the comment char. */
- for (i = 0; i < padding; i++) {
- STRCAT(leader, " ");
- less_cols--;
- newcol++;
- }
- }
- STRCAT(leader, p_extra);
- p_extra = leader;
- did_ai = true; // So truncating blanks works with comments
- less_cols -= lead_len;
- } else
- end_comment_pending = NUL; /* turns out there was no leader */
-
- old_cursor = curwin->w_cursor;
- if (dir == BACKWARD)
- --curwin->w_cursor.lnum;
- if (!(State & VREPLACE_FLAG) || old_cursor.lnum >= orig_line_count) {
- if (ml_append(curwin->w_cursor.lnum, p_extra, (colnr_T)0, false) == FAIL) {
- goto theend;
- }
- // Postpone calling changed_lines(), because it would mess up folding
- // with markers.
- // Skip mark_adjust when adding a line after the last one, there can't
- // be marks there. But still needed in diff mode.
- if (curwin->w_cursor.lnum + 1 < curbuf->b_ml.ml_line_count
- || curwin->w_p_diff) {
- mark_adjust(curwin->w_cursor.lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, false);
- }
- did_append = true;
- } else {
- /*
- * In VREPLACE mode we are starting to replace the next line.
- */
- curwin->w_cursor.lnum++;
- if (curwin->w_cursor.lnum >= Insstart.lnum + vr_lines_changed) {
- /* In case we NL to a new line, BS to the previous one, and NL
- * again, we don't want to save the new line for undo twice.
- */
- (void)u_save_cursor(); /* errors are ignored! */
- vr_lines_changed++;
- }
- ml_replace(curwin->w_cursor.lnum, p_extra, true);
- changed_bytes(curwin->w_cursor.lnum, 0);
- curwin->w_cursor.lnum--;
- did_append = FALSE;
- }
-
- if (newindent
- || did_si
- ) {
- ++curwin->w_cursor.lnum;
- if (did_si) {
- int sw = get_sw_value(curbuf);
-
- if (p_sr)
- newindent -= newindent % sw;
- newindent += sw;
- }
- /* Copy the indent */
- if (curbuf->b_p_ci) {
- (void)copy_indent(newindent, saved_line);
-
- /*
- * Set the 'preserveindent' option so that any further screwing
- * with the line doesn't entirely destroy our efforts to preserve
- * it. It gets restored at the function end.
- */
- curbuf->b_p_pi = TRUE;
- } else
- (void)set_indent(newindent, SIN_INSERT);
- less_cols -= curwin->w_cursor.col;
-
- ai_col = curwin->w_cursor.col;
-
- /*
- * In REPLACE mode, for each character in the new indent, there must
- * be a NUL on the replace stack, for when it is deleted with BS
- */
- if (REPLACE_NORMAL(State)) {
- for (colnr_T n = 0; n < curwin->w_cursor.col; n++) {
- replace_push(NUL);
- }
- }
- newcol += curwin->w_cursor.col;
- if (no_si) {
- did_si = false;
- }
- }
-
- /*
- * In REPLACE mode, for each character in the extra leader, there must be
- * a NUL on the replace stack, for when it is deleted with BS.
- */
- if (REPLACE_NORMAL(State))
- while (lead_len-- > 0)
- replace_push(NUL);
-
- curwin->w_cursor = old_cursor;
-
- if (dir == FORWARD) {
- if (trunc_line || (State & INSERT)) {
- // truncate current line at cursor
- saved_line[curwin->w_cursor.col] = NUL;
- // Remove trailing white space, unless OPENLINE_KEEPTRAIL used.
- if (trunc_line && !(flags & OPENLINE_KEEPTRAIL)) {
- truncate_spaces(saved_line);
- }
- ml_replace(curwin->w_cursor.lnum, saved_line, false);
- saved_line = NULL;
- if (did_append) {
- changed_lines(curwin->w_cursor.lnum, curwin->w_cursor.col,
- curwin->w_cursor.lnum + 1, 1L, true);
- did_append = false;
-
- /* Move marks after the line break to the new line. */
- if (flags & OPENLINE_MARKFIX)
- mark_col_adjust(curwin->w_cursor.lnum,
- curwin->w_cursor.col + less_cols_off,
- 1L, (long)-less_cols, 0);
- } else {
- changed_bytes(curwin->w_cursor.lnum, curwin->w_cursor.col);
- }
- }
-
- /*
- * Put the cursor on the new line. Careful: the scrollup() above may
- * have moved w_cursor, we must use old_cursor.
- */
- curwin->w_cursor.lnum = old_cursor.lnum + 1;
- }
- if (did_append) {
- changed_lines(curwin->w_cursor.lnum, 0, curwin->w_cursor.lnum, 1L, true);
- }
-
- curwin->w_cursor.col = newcol;
- curwin->w_cursor.coladd = 0;
-
- /*
- * In VREPLACE mode, we are handling the replace stack ourselves, so stop
- * fixthisline() from doing it (via change_indent()) by telling it we're in
- * normal INSERT mode.
- */
- if (State & VREPLACE_FLAG) {
- vreplace_mode = State; /* So we know to put things right later */
- State = INSERT;
- } else
- vreplace_mode = 0;
- /*
- * May do lisp indenting.
- */
- if (!p_paste
- && leader == NULL
- && curbuf->b_p_lisp
- && curbuf->b_p_ai) {
- fixthisline(get_lisp_indent);
- ai_col = (colnr_T)getwhitecols_curline();
- }
- /*
- * May do indenting after opening a new line.
- */
- if (!p_paste
- && (curbuf->b_p_cin
- || *curbuf->b_p_inde != NUL
- )
- && in_cinkeys(dir == FORWARD
- ? KEY_OPEN_FORW
- : KEY_OPEN_BACK, ' ', linewhite(curwin->w_cursor.lnum))) {
- do_c_expr_indent();
- ai_col = (colnr_T)getwhitecols_curline();
- }
- if (vreplace_mode != 0)
- State = vreplace_mode;
-
- /*
- * Finally, VREPLACE gets the stuff on the new line, then puts back the
- * original line, and inserts the new stuff char by char, pushing old stuff
- * onto the replace stack (via ins_char()).
- */
- if (State & VREPLACE_FLAG) {
- /* Put new line in p_extra */
- p_extra = vim_strsave(get_cursor_line_ptr());
-
- // Put back original line
- ml_replace(curwin->w_cursor.lnum, next_line, false);
-
- /* Insert new stuff into line again */
- curwin->w_cursor.col = 0;
- curwin->w_cursor.coladd = 0;
- ins_bytes(p_extra); /* will call changed_bytes() */
- xfree(p_extra);
- next_line = NULL;
- }
-
- retval = true; // success!
-theend:
- curbuf->b_p_pi = saved_pi;
- xfree(saved_line);
- xfree(next_line);
- xfree(allocated);
- return retval;
-}
-
-/*
* get_leader_len() returns the length in bytes of the prefix of the given
* string which introduces a comment. If this string is not a comment then
* 0 is returned.
@@ -1373,398 +507,8 @@ int plines_m_win(win_T *wp, linenr_T first, linenr_T last)
return count;
}
-/*
- * Insert string "p" at the cursor position. Stops at a NUL byte.
- * Handles Replace mode and multi-byte characters.
- */
-void ins_bytes(char_u *p)
-{
- ins_bytes_len(p, STRLEN(p));
-}
-
-/// Insert string "p" with length "len" at the cursor position.
-/// Handles Replace mode and multi-byte characters.
-void ins_bytes_len(char_u *p, size_t len)
-{
- if (has_mbyte) {
- size_t n;
- for (size_t i = 0; i < len; i += n) {
- if (enc_utf8) {
- // avoid reading past p[len]
- n = (size_t)utfc_ptr2len_len(p + i, (int)(len - i));
- } else {
- n = (size_t)(*mb_ptr2len)(p + i);
- }
- ins_char_bytes(p + i, n);
- }
- } else {
- for (size_t i = 0; i < len; i++) {
- ins_char(p[i]);
- }
- }
-}
-
-/// Insert or replace a single character at the cursor position.
-/// When in REPLACE or VREPLACE mode, replace any existing character.
-/// Caller must have prepared for undo.
-/// For multi-byte characters we get the whole character, the caller must
-/// convert bytes to a character.
-void ins_char(int c)
-{
- char_u buf[MB_MAXBYTES + 1];
- size_t n = (size_t)utf_char2bytes(c, buf);
-
- // When "c" is 0x100, 0x200, etc. we don't want to insert a NUL byte.
- // Happens for CTRL-Vu9900.
- if (buf[0] == 0) {
- buf[0] = '\n';
- }
- ins_char_bytes(buf, n);
-}
-
-void ins_char_bytes(char_u *buf, size_t charlen)
-{
- // Break tabs if needed.
- if (virtual_active() && curwin->w_cursor.coladd > 0) {
- coladvance_force(getviscol());
- }
-
- size_t col = (size_t)curwin->w_cursor.col;
- linenr_T lnum = curwin->w_cursor.lnum;
- char_u *oldp = ml_get(lnum);
- size_t linelen = STRLEN(oldp) + 1; // length of old line including NUL
-
- // The lengths default to the values for when not replacing.
- size_t oldlen = 0; // nr of bytes inserted
- size_t newlen = charlen; // nr of bytes deleted (0 when not replacing)
-
- if (State & REPLACE_FLAG) {
- if (State & VREPLACE_FLAG) {
- // Disable 'list' temporarily, unless 'cpo' contains the 'L' flag.
- // Returns the old value of list, so when finished,
- // curwin->w_p_list should be set back to this.
- int old_list = curwin->w_p_list;
- if (old_list && vim_strchr(p_cpo, CPO_LISTWM) == NULL) {
- curwin->w_p_list = false;
- }
- // In virtual replace mode each character may replace one or more
- // characters (zero if it's a TAB). Count the number of bytes to
- // be deleted to make room for the new character, counting screen
- // cells. May result in adding spaces to fill a gap.
- colnr_T vcol;
- getvcol(curwin, &curwin->w_cursor, NULL, &vcol, NULL);
- colnr_T new_vcol = vcol + chartabsize(buf, vcol);
- while (oldp[col + oldlen] != NUL && vcol < new_vcol) {
- vcol += chartabsize(oldp + col + oldlen, vcol);
- // Don't need to remove a TAB that takes us to the right
- // position.
- if (vcol > new_vcol && oldp[col + oldlen] == TAB) {
- break;
- }
- oldlen += (size_t)(*mb_ptr2len)(oldp + col + oldlen);
- // Deleted a bit too much, insert spaces.
- if (vcol > new_vcol) {
- newlen += (size_t)(vcol - new_vcol);
- }
- }
- curwin->w_p_list = old_list;
- } else if (oldp[col] != NUL) {
- // normal replace
- oldlen = (size_t)(*mb_ptr2len)(oldp + col);
- }
-
-
- /* Push the replaced bytes onto the replace stack, so that they can be
- * put back when BS is used. The bytes of a multi-byte character are
- * done the other way around, so that the first byte is popped off
- * first (it tells the byte length of the character). */
- replace_push(NUL);
- for (size_t i = 0; i < oldlen; i++) {
- if (has_mbyte) {
- i += (size_t)replace_push_mb(oldp + col + i) - 1;
- } else {
- replace_push(oldp[col + i]);
- }
- }
- }
-
- char_u *newp = xmalloc((size_t)(linelen + newlen - oldlen));
-
- // Copy bytes before the cursor.
- if (col > 0) {
- memmove(newp, oldp, (size_t)col);
- }
-
- // Copy bytes after the changed character(s).
- char_u *p = newp + col;
- if (linelen > col + oldlen) {
- memmove(p + newlen, oldp + col + oldlen,
- (size_t)(linelen - col - oldlen));
- }
-
- // Insert or overwrite the new character.
- memmove(p, buf, charlen);
-
- // Fill with spaces when necessary.
- for (size_t i = charlen; i < newlen; i++) {
- p[i] = ' ';
- }
-
- // Replace the line in the buffer.
- ml_replace(lnum, newp, false);
-
- // mark the buffer as changed and prepare for displaying
- changed_bytes(lnum, (colnr_T)col);
-
- /*
- * If we're in Insert or Replace mode and 'showmatch' is set, then briefly
- * show the match for right parens and braces.
- */
- if (p_sm && (State & INSERT)
- && msg_silent == 0
- && !ins_compl_active()
- ) {
- showmatch(utf_ptr2char(buf));
- }
-
- if (!p_ri || (State & REPLACE_FLAG)) {
- // Normal insert: move cursor right
- curwin->w_cursor.col += (int)charlen;
- }
- /*
- * TODO: should try to update w_row here, to avoid recomputing it later.
- */
-}
-
-/*
- * Insert a string at the cursor position.
- * Note: Does NOT handle Replace mode.
- * Caller must have prepared for undo.
- */
-void ins_str(char_u *s)
-{
- char_u *oldp, *newp;
- int newlen = (int)STRLEN(s);
- int oldlen;
- colnr_T col;
- linenr_T lnum = curwin->w_cursor.lnum;
-
- if (virtual_active() && curwin->w_cursor.coladd > 0)
- coladvance_force(getviscol());
-
- col = curwin->w_cursor.col;
- oldp = ml_get(lnum);
- oldlen = (int)STRLEN(oldp);
-
- newp = (char_u *) xmalloc((size_t)(oldlen + newlen + 1));
- if (col > 0)
- memmove(newp, oldp, (size_t)col);
- memmove(newp + col, s, (size_t)newlen);
- memmove(newp + col + newlen, oldp + col, (size_t)(oldlen - col + 1));
- ml_replace(lnum, newp, false);
- changed_bytes(lnum, col);
- curwin->w_cursor.col += newlen;
-}
-
-// Delete one character under the cursor.
-// If "fixpos" is true, don't leave the cursor on the NUL after the line.
-// Caller must have prepared for undo.
-//
-// return FAIL for failure, OK otherwise
-int del_char(bool fixpos)
-{
- if (has_mbyte) {
- /* Make sure the cursor is at the start of a character. */
- mb_adjust_cursor();
- if (*get_cursor_pos_ptr() == NUL)
- return FAIL;
- return del_chars(1L, fixpos);
- }
- return del_bytes(1, fixpos, true);
-}
-
-/*
- * Like del_bytes(), but delete characters instead of bytes.
- */
-int del_chars(long count, int fixpos)
-{
- int bytes = 0;
- long i;
- char_u *p;
- int l;
-
- p = get_cursor_pos_ptr();
- for (i = 0; i < count && *p != NUL; ++i) {
- l = (*mb_ptr2len)(p);
- bytes += l;
- p += l;
- }
- return del_bytes(bytes, fixpos, TRUE);
-}
-
-/// Delete "count" bytes under the cursor.
-/// If "fixpos" is true, don't leave the cursor on the NUL after the line.
-/// Caller must have prepared for undo.
-///
-/// @param count number of bytes to be deleted
-/// @param fixpos_arg leave the cursor on the NUL after the line
-/// @param use_delcombine 'delcombine' option applies
-///
-/// @return FAIL for failure, OK otherwise
-int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine)
-{
- linenr_T lnum = curwin->w_cursor.lnum;
- colnr_T col = curwin->w_cursor.col;
- bool fixpos = fixpos_arg;
- char_u *oldp = ml_get(lnum);
- colnr_T oldlen = (colnr_T)STRLEN(oldp);
-
- // Can't do anything when the cursor is on the NUL after the line.
- if (col >= oldlen) {
- return FAIL;
- }
- // If "count" is zero there is nothing to do.
- if (count == 0) {
- return OK;
- }
- // If "count" is negative the caller must be doing something wrong.
- if (count < 1) {
- IEMSGN("E950: Invalid count for del_bytes(): %ld", count);
- return FAIL;
- }
-
- /* If 'delcombine' is set and deleting (less than) one character, only
- * delete the last combining character. */
- if (p_deco && use_delcombine && enc_utf8
- && utfc_ptr2len(oldp + col) >= count) {
- int cc[MAX_MCO];
- int n;
-
- (void)utfc_ptr2char(oldp + col, cc);
- if (cc[0] != NUL) {
- /* Find the last composing char, there can be several. */
- n = col;
- do {
- col = n;
- count = utf_ptr2len(oldp + n);
- n += count;
- } while (UTF_COMPOSINGLIKE(oldp + col, oldp + n));
- fixpos = false;
- }
- }
-
- // When count is too big, reduce it.
- int movelen = oldlen - col - count + 1; // includes trailing NUL
- if (movelen <= 1) {
- /*
- * If we just took off the last character of a non-blank line, and
- * fixpos is TRUE, we don't want to end up positioned at the NUL,
- * unless "restart_edit" is set or 'virtualedit' contains "onemore".
- */
- if (col > 0 && fixpos && restart_edit == 0
- && (ve_flags & VE_ONEMORE) == 0
- ) {
- --curwin->w_cursor.col;
- curwin->w_cursor.coladd = 0;
- curwin->w_cursor.col -= utf_head_off(oldp, oldp + curwin->w_cursor.col);
- }
- count = oldlen - col;
- movelen = 1;
- }
-
- // If the old line has been allocated the deletion can be done in the
- // existing line. Otherwise a new line has to be allocated.
- bool was_alloced = ml_line_alloced(); // check if oldp was allocated
- char_u *newp;
- if (was_alloced) {
- newp = oldp; // use same allocated memory
- } else { // need to allocate a new line
- newp = xmalloc((size_t)(oldlen + 1 - count));
- memmove(newp, oldp, (size_t)col);
- }
- memmove(newp + col, oldp + col + count, (size_t)movelen);
- if (!was_alloced) {
- ml_replace(lnum, newp, false);
- }
-
- /* mark the buffer as changed and prepare for displaying */
- changed_bytes(lnum, curwin->w_cursor.col);
-
- return OK;
-}
-
-/*
- * Delete from cursor to end of line.
- * Caller must have prepared for undo.
- */
-void
-truncate_line (
- int fixpos /* if TRUE fix the cursor position when done */
-)
-{
- char_u *newp;
- linenr_T lnum = curwin->w_cursor.lnum;
- colnr_T col = curwin->w_cursor.col;
-
- if (col == 0) {
- newp = vim_strsave((char_u *)"");
- } else {
- newp = vim_strnsave(ml_get(lnum), (size_t)col);
- }
- ml_replace(lnum, newp, false);
-
- /* mark the buffer as changed and prepare for displaying */
- changed_bytes(lnum, curwin->w_cursor.col);
-
- /*
- * If "fixpos" is TRUE we don't want to end up positioned at the NUL.
- */
- if (fixpos && curwin->w_cursor.col > 0)
- --curwin->w_cursor.col;
-}
-
-/*
- * Delete "nlines" lines at the cursor.
- * Saves the lines for undo first if "undo" is TRUE.
- */
-void
-del_lines (
- long nlines, /* number of lines to delete */
- int undo /* if TRUE, prepare for undo */
-)
-{
- long n;
- linenr_T first = curwin->w_cursor.lnum;
-
- if (nlines <= 0)
- return;
-
- /* save the deleted lines for undo */
- if (undo && u_savedel(first, nlines) == FAIL)
- return;
-
- for (n = 0; n < nlines; ) {
- if (curbuf->b_ml.ml_flags & ML_EMPTY) /* nothing to delete */
- break;
-
- ml_delete(first, true);
- n++;
-
- /* If we delete the last line in the file, stop */
- if (first > curbuf->b_ml.ml_line_count)
- break;
- }
-
- /* Correct the cursor position before calling deleted_lines_mark(), it may
- * trigger a callback to display the cursor. */
- curwin->w_cursor.col = 0;
- check_cursor_lnum();
-
- /* adjust marks, mark the buffer as changed and prepare for displaying */
- deleted_lines_mark(first, n);
-}
-
int gchar_pos(pos_T *pos)
+ FUNC_ATTR_NONNULL_ARG(1)
{
// When searching columns is sometimes put at the end of a line.
if (pos->col == MAXCOL) {
@@ -1774,430 +518,6 @@ int gchar_pos(pos_T *pos)
}
/*
- * Call this function when something in the current buffer is changed.
- *
- * Most often called through changed_bytes() and changed_lines(), which also
- * mark the area of the display to be redrawn.
- *
- * Careful: may trigger autocommands that reload the buffer.
- */
-void changed(void)
-{
-
- if (!curbuf->b_changed) {
- int save_msg_scroll = msg_scroll;
-
- /* Give a warning about changing a read-only file. This may also
- * check-out the file, thus change "curbuf"! */
- change_warning(0);
-
- /* Create a swap file if that is wanted.
- * Don't do this for "nofile" and "nowrite" buffer types. */
- if (curbuf->b_may_swap
- && !bt_dontwrite(curbuf)
- ) {
- int save_need_wait_return = need_wait_return;
-
- need_wait_return = false;
- ml_open_file(curbuf);
-
- /* The ml_open_file() can cause an ATTENTION message.
- * Wait two seconds, to make sure the user reads this unexpected
- * message. Since we could be anywhere, call wait_return() now,
- * and don't let the emsg() set msg_scroll. */
- if (need_wait_return && emsg_silent == 0) {
- ui_flush();
- os_delay(2000L, true);
- wait_return(TRUE);
- msg_scroll = save_msg_scroll;
- } else {
- need_wait_return = save_need_wait_return;
- }
- }
- changed_int();
- }
- buf_inc_changedtick(curbuf);
-
- // If a pattern is highlighted, the position may now be invalid.
- highlight_match = false;
-}
-
-/*
- * Internal part of changed(), no user interaction.
- */
-void changed_int(void)
-{
- curbuf->b_changed = true;
- ml_setflags(curbuf);
- check_status(curbuf);
- redraw_tabline = TRUE;
- need_maketitle = TRUE; /* set window title later */
-}
-
-
-/*
- * Changed bytes within a single line for the current buffer.
- * - marks the windows on this buffer to be redisplayed
- * - marks the buffer changed by calling changed()
- * - invalidates cached values
- * Careful: may trigger autocommands that reload the buffer.
- */
-void changed_bytes(linenr_T lnum, colnr_T col)
-{
- changedOneline(curbuf, lnum);
- changed_common(lnum, col, lnum + 1, 0L);
- // notify any channels that are watching
- buf_updates_send_changes(curbuf, lnum, 1, 1, true);
-
- /* Diff highlighting in other diff windows may need to be updated too. */
- if (curwin->w_p_diff) {
- linenr_T wlnum;
-
- FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- if (wp->w_p_diff && wp != curwin) {
- redraw_win_later(wp, VALID);
- wlnum = diff_lnum_win(lnum, wp);
- if (wlnum > 0)
- changedOneline(wp->w_buffer, wlnum);
- }
- }
- }
-}
-
-static void changedOneline(buf_T *buf, linenr_T lnum)
-{
- if (buf->b_mod_set) {
- /* find the maximum area that must be redisplayed */
- if (lnum < buf->b_mod_top)
- buf->b_mod_top = lnum;
- else if (lnum >= buf->b_mod_bot)
- buf->b_mod_bot = lnum + 1;
- } else {
- /* set the area that must be redisplayed to one line */
- buf->b_mod_set = true;
- buf->b_mod_top = lnum;
- buf->b_mod_bot = lnum + 1;
- buf->b_mod_xlines = 0;
- }
-}
-
-/*
- * Appended "count" lines below line "lnum" in the current buffer.
- * Must be called AFTER the change and after mark_adjust().
- * Takes care of marking the buffer to be redrawn and sets the changed flag.
- */
-void appended_lines(linenr_T lnum, long count)
-{
- changed_lines(lnum + 1, 0, lnum + 1, count, true);
-}
-
-/*
- * Like appended_lines(), but adjust marks first.
- */
-void appended_lines_mark(linenr_T lnum, long count)
-{
- // Skip mark_adjust when adding a line after the last one, there can't
- // be marks there. But it's still needed in diff mode.
- if (lnum + count < curbuf->b_ml.ml_line_count || curwin->w_p_diff) {
- mark_adjust(lnum + 1, (linenr_T)MAXLNUM, count, 0L, false);
- }
- changed_lines(lnum + 1, 0, lnum + 1, count, true);
-}
-
-/*
- * Deleted "count" lines at line "lnum" in the current buffer.
- * Must be called AFTER the change and after mark_adjust().
- * Takes care of marking the buffer to be redrawn and sets the changed flag.
- */
-void deleted_lines(linenr_T lnum, long count)
-{
- changed_lines(lnum, 0, lnum + count, -count, true);
-}
-
-/*
- * Like deleted_lines(), but adjust marks first.
- * Make sure the cursor is on a valid line before calling, a GUI callback may
- * be triggered to display the cursor.
- */
-void deleted_lines_mark(linenr_T lnum, long count)
-{
- mark_adjust(lnum, (linenr_T)(lnum + count - 1), (long)MAXLNUM, -count, false);
- changed_lines(lnum, 0, lnum + count, -count, true);
-}
-
-/*
- * Changed lines for the current buffer.
- * Must be called AFTER the change and after mark_adjust().
- * - mark the buffer changed by calling changed()
- * - mark the windows on this buffer to be redisplayed
- * - invalidate cached values
- * "lnum" is the first line that needs displaying, "lnume" the first line
- * below the changed lines (BEFORE the change).
- * When only inserting lines, "lnum" and "lnume" are equal.
- * Takes care of calling changed() and updating b_mod_*.
- * Careful: may trigger autocommands that reload the buffer.
- */
-void
-changed_lines(
- linenr_T lnum, // first line with change
- colnr_T col, // column in first line with change
- linenr_T lnume, // line below last changed line
- long xtra, // number of extra lines (negative when deleting)
- bool do_buf_event // some callers like undo/redo call changed_lines()
- // and then increment changedtick *again*. This flag
- // allows these callers to send the nvim_buf_lines_event
- // events after they're done modifying changedtick.
-)
-{
- changed_lines_buf(curbuf, lnum, lnume, xtra);
-
- if (xtra == 0 && curwin->w_p_diff && !diff_internal()) {
- // When the number of lines doesn't change then mark_adjust() isn't
- // called and other diff buffers still need to be marked for
- // displaying.
- linenr_T wlnum;
-
- FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- if (wp->w_p_diff && wp != curwin) {
- redraw_win_later(wp, VALID);
- wlnum = diff_lnum_win(lnum, wp);
- if (wlnum > 0) {
- changed_lines_buf(wp->w_buffer, wlnum,
- lnume - lnum + wlnum, 0L);
- }
- }
- }
- }
-
- changed_common(lnum, col, lnume, xtra);
-
- if (do_buf_event) {
- int64_t num_added = (int64_t)(lnume + xtra - lnum);
- int64_t num_removed = lnume - lnum;
- buf_updates_send_changes(curbuf, lnum, num_added, num_removed, true);
- }
-}
-
-/// Mark line range in buffer as changed.
-///
-/// @param buf the buffer where lines were changed
-/// @param lnum first line with change
-/// @param lnume line below last changed line
-/// @param xtra number of extra lines (negative when deleting)
-void changed_lines_buf(buf_T *buf, linenr_T lnum, linenr_T lnume, long xtra)
-{
- if (buf->b_mod_set) {
- /* find the maximum area that must be redisplayed */
- if (lnum < buf->b_mod_top)
- buf->b_mod_top = lnum;
- if (lnum < buf->b_mod_bot) {
- /* adjust old bot position for xtra lines */
- buf->b_mod_bot += xtra;
- if (buf->b_mod_bot < lnum)
- buf->b_mod_bot = lnum;
- }
- if (lnume + xtra > buf->b_mod_bot)
- buf->b_mod_bot = lnume + xtra;
- buf->b_mod_xlines += xtra;
- } else {
- /* set the area that must be redisplayed */
- buf->b_mod_set = true;
- buf->b_mod_top = lnum;
- buf->b_mod_bot = lnume + xtra;
- buf->b_mod_xlines = xtra;
- }
-}
-
-/*
- * Common code for when a change is was made.
- * See changed_lines() for the arguments.
- * Careful: may trigger autocommands that reload the buffer.
- */
-static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, long xtra)
-{
- int i;
- int cols;
- pos_T *p;
- int add;
-
- /* mark the buffer as modified */
- changed();
-
- if (curwin->w_p_diff && diff_internal()) {
- curtab->tp_diff_update = true;
- }
-
- /* set the '. mark */
- if (!cmdmod.keepjumps) {
- RESET_FMARK(&curbuf->b_last_change, ((pos_T) {lnum, col, 0}), 0);
-
- /* Create a new entry if a new undo-able change was started or we
- * don't have an entry yet. */
- if (curbuf->b_new_change || curbuf->b_changelistlen == 0) {
- if (curbuf->b_changelistlen == 0)
- add = TRUE;
- else {
- /* Don't create a new entry when the line number is the same
- * as the last one and the column is not too far away. Avoids
- * creating many entries for typing "xxxxx". */
- p = &curbuf->b_changelist[curbuf->b_changelistlen - 1].mark;
- if (p->lnum != lnum)
- add = TRUE;
- else {
- cols = comp_textwidth(FALSE);
- if (cols == 0)
- cols = 79;
- add = (p->col + cols < col || col + cols < p->col);
- }
- }
- if (add) {
- /* This is the first of a new sequence of undo-able changes
- * and it's at some distance of the last change. Use a new
- * position in the changelist. */
- curbuf->b_new_change = false;
-
- if (curbuf->b_changelistlen == JUMPLISTSIZE) {
- /* changelist is full: remove oldest entry */
- curbuf->b_changelistlen = JUMPLISTSIZE - 1;
- memmove(curbuf->b_changelist, curbuf->b_changelist + 1,
- sizeof(curbuf->b_changelist[0]) * (JUMPLISTSIZE - 1));
- FOR_ALL_TAB_WINDOWS(tp, wp) {
- /* Correct position in changelist for other windows on
- * this buffer. */
- if (wp->w_buffer == curbuf && wp->w_changelistidx > 0) {
- --wp->w_changelistidx;
- }
- }
- }
- FOR_ALL_TAB_WINDOWS(tp, wp) {
- /* For other windows, if the position in the changelist is
- * at the end it stays at the end. */
- if (wp->w_buffer == curbuf
- && wp->w_changelistidx == curbuf->b_changelistlen) {
- ++wp->w_changelistidx;
- }
- }
- ++curbuf->b_changelistlen;
- }
- }
- curbuf->b_changelist[curbuf->b_changelistlen - 1] =
- curbuf->b_last_change;
- /* The current window is always after the last change, so that "g,"
- * takes you back to it. */
- curwin->w_changelistidx = curbuf->b_changelistlen;
- }
-
- FOR_ALL_TAB_WINDOWS(tp, wp) {
- if (wp->w_buffer == curbuf) {
- /* Mark this window to be redrawn later. */
- if (wp->w_redr_type < VALID)
- wp->w_redr_type = VALID;
-
- /* Check if a change in the buffer has invalidated the cached
- * values for the cursor. */
- /*
- * Update the folds for this window. Can't postpone this, because
- * a following operator might work on the whole fold: ">>dd".
- */
- foldUpdate(wp, lnum, lnume + xtra - 1);
-
- /* The change may cause lines above or below the change to become
- * included in a fold. Set lnum/lnume to the first/last line that
- * might be displayed differently.
- * Set w_cline_folded here as an efficient way to update it when
- * inserting lines just above a closed fold. */
- bool folded = hasFoldingWin(wp, lnum, &lnum, NULL, false, NULL);
- if (wp->w_cursor.lnum == lnum)
- wp->w_cline_folded = folded;
- folded = hasFoldingWin(wp, lnume, NULL, &lnume, false, NULL);
- if (wp->w_cursor.lnum == lnume)
- wp->w_cline_folded = folded;
-
- /* If the changed line is in a range of previously folded lines,
- * compare with the first line in that range. */
- if (wp->w_cursor.lnum <= lnum) {
- i = find_wl_entry(wp, lnum);
- if (i >= 0 && wp->w_cursor.lnum > wp->w_lines[i].wl_lnum)
- changed_line_abv_curs_win(wp);
- }
-
- if (wp->w_cursor.lnum > lnum)
- changed_line_abv_curs_win(wp);
- else if (wp->w_cursor.lnum == lnum && wp->w_cursor.col >= col)
- changed_cline_bef_curs_win(wp);
- if (wp->w_botline >= lnum) {
- /* Assume that botline doesn't change (inserted lines make
- * other lines scroll down below botline). */
- approximate_botline_win(wp);
- }
-
- /* Check if any w_lines[] entries have become invalid.
- * For entries below the change: Correct the lnums for
- * inserted/deleted lines. Makes it possible to stop displaying
- * after the change. */
- for (i = 0; i < wp->w_lines_valid; ++i)
- if (wp->w_lines[i].wl_valid) {
- if (wp->w_lines[i].wl_lnum >= lnum) {
- if (wp->w_lines[i].wl_lnum < lnume) {
- /* line included in change */
- wp->w_lines[i].wl_valid = FALSE;
- } else if (xtra != 0) {
- /* line below change */
- wp->w_lines[i].wl_lnum += xtra;
- wp->w_lines[i].wl_lastlnum += xtra;
- }
- } else if (wp->w_lines[i].wl_lastlnum >= lnum) {
- /* change somewhere inside this range of folded lines,
- * may need to be redrawn */
- wp->w_lines[i].wl_valid = FALSE;
- }
- }
-
- /* Take care of side effects for setting w_topline when folds have
- * changed. Esp. when the buffer was changed in another window. */
- if (hasAnyFolding(wp))
- set_topline(wp, wp->w_topline);
-
- // relative numbering may require updating more
- if (wp->w_p_rnu) {
- redraw_win_later(wp, SOME_VALID);
- }
- }
- }
-
- /* Call update_screen() later, which checks out what needs to be redrawn,
- * since it notices b_mod_set and then uses b_mod_*. */
- if (must_redraw < VALID)
- must_redraw = VALID;
-
- /* when the cursor line is changed always trigger CursorMoved */
- if (lnum <= curwin->w_cursor.lnum
- && lnume + (xtra < 0 ? -xtra : xtra) > curwin->w_cursor.lnum)
- curwin->w_last_cursormoved.lnum = 0;
-}
-
-/*
- * unchanged() is called when the changed flag must be reset for buffer 'buf'
- */
-void
-unchanged (
- buf_T *buf,
- int ff /* also reset 'fileformat' */
-)
-{
- if (buf->b_changed || (ff && file_ff_differs(buf, false))) {
- buf->b_changed = false;
- ml_setflags(buf);
- if (ff)
- save_file_ff(buf);
- check_status(buf);
- redraw_tabline = TRUE;
- need_maketitle = TRUE; /* set window title later */
- }
- buf_inc_changedtick(buf);
-}
-
-/*
* check_status: called when the status bars for the buffer 'buf'
* need to be updated
*/
@@ -2213,55 +533,6 @@ void check_status(buf_T *buf)
}
}
-/*
- * If the file is readonly, give a warning message with the first change.
- * Don't do this for autocommands.
- * Don't use emsg(), because it flushes the macro buffer.
- * If we have undone all changes b_changed will be false, but "b_did_warn"
- * will be true.
- * Careful: may trigger autocommands that reload the buffer.
- */
-void
-change_warning (
- int col /* column for message; non-zero when in insert
- mode and 'showmode' is on */
-)
-{
- static char *w_readonly = N_("W10: Warning: Changing a readonly file");
-
- if (curbuf->b_did_warn == false
- && curbufIsChanged() == 0
- && !autocmd_busy
- && curbuf->b_p_ro) {
- ++curbuf_lock;
- apply_autocmds(EVENT_FILECHANGEDRO, NULL, NULL, FALSE, curbuf);
- --curbuf_lock;
- if (!curbuf->b_p_ro)
- return;
- /*
- * Do what msg() does, but with a column offset if the warning should
- * be after the mode message.
- */
- msg_start();
- if (msg_row == Rows - 1)
- msg_col = col;
- msg_source(HL_ATTR(HLF_W));
- msg_ext_set_kind("wmsg");
- MSG_PUTS_ATTR(_(w_readonly), HL_ATTR(HLF_W) | MSG_HIST);
- set_vim_var_string(VV_WARNINGMSG, _(w_readonly), -1);
- msg_clr_eos();
- (void)msg_end();
- if (msg_silent == 0 && !silent_mode && ui_active()) {
- ui_flush();
- os_delay(1000L, true); /* give the user time to think about it */
- }
- curbuf->b_did_warn = true;
- redraw_cmdline = FALSE; /* don't redraw and erase the message */
- if (msg_row < Rows - 1)
- showmode();
- }
-}
-
/// Ask for a reply from the user, 'y' or 'n'
///
/// No other characters are accepted, the message is repeated until a valid
@@ -2503,18 +774,24 @@ int prompt_for_number(int *mouse_used)
cmdline_row = 0;
save_State = State;
State = ASKMORE; // prevents a screen update when using a timer
+ // May show different mouse shape.
+ setmouse();
i = get_number(TRUE, mouse_used);
if (KeyTyped) {
- /* don't call wait_return() now */
- /* msg_putchar('\n'); */
- cmdline_row = msg_row - 1;
- need_wait_return = FALSE;
- msg_didany = FALSE;
- msg_didout = FALSE;
- } else
+ // don't call wait_return() now
+ if (msg_row > 0) {
+ cmdline_row = msg_row - 1;
+ }
+ need_wait_return = false;
+ msg_didany = false;
+ msg_didout = false;
+ } else {
cmdline_row = save_cmdline_row;
+ }
State = save_State;
+ // May need to restore mouse shape.
+ setmouse();
return i;
}
@@ -2720,7 +997,7 @@ void preserve_exit(void)
*/
#ifndef BREAKCHECK_SKIP
-# define BREAKCHECK_SKIP 32
+# define BREAKCHECK_SKIP 1000
#endif
static int breakcheck_count = 0;
@@ -2821,7 +1098,7 @@ char_u *get_cmd_output(char_u *cmd, char_u *infile, ShellOpts flags,
xfree(command);
// read the names from the file into memory
- FILE *fd = mch_fopen((char *)tempname, READBIN);
+ FILE *fd = os_fopen((char *)tempname, READBIN);
if (fd == NULL) {
EMSG2(_(e_notopen), tempname);
diff --git a/src/nvim/move.c b/src/nvim/move.c
index e076543614..4a87f82eb7 100644
--- a/src/nvim/move.c
+++ b/src/nvim/move.c
@@ -103,7 +103,8 @@ void reset_cursorline(void)
}
// Redraw when w_cline_row changes and 'relativenumber' or 'cursorline' is set.
-static void redraw_for_cursorline(win_T *wp)
+void redraw_for_cursorline(win_T *wp)
+ FUNC_ATTR_NONNULL_ALL
{
if ((wp->w_p_rnu || win_cursorline_standout(wp))
&& (wp->w_valid & VALID_CROW) == 0
@@ -119,11 +120,9 @@ static void redraw_for_cursorline(win_T *wp)
// the current window.
redrawWinline(wp, wp->w_last_cursorline);
redrawWinline(wp, wp->w_cursor.lnum);
- redraw_win_later(wp, VALID);
} else {
redraw_win_later(wp, SOME_VALID);
}
- wp->w_last_cursorline = wp->w_cursor.lnum;
}
}
}
@@ -847,11 +846,11 @@ void curs_columns(
prev_skipcol = curwin->w_skipcol;
- int p_lines = 0;
+ int plines = 0;
if ((curwin->w_wrow >= curwin->w_height_inner
|| ((prev_skipcol > 0
|| curwin->w_wrow + p_so >= curwin->w_height_inner)
- && (p_lines =
+ && (plines =
plines_win_nofill(curwin, curwin->w_cursor.lnum, false)) - 1
>= curwin->w_height_inner))
&& curwin->w_height_inner != 0
@@ -870,20 +869,21 @@ void curs_columns(
extra = 1;
/* Compute last display line of the buffer line that we want at the
* bottom of the window. */
- if (p_lines == 0)
- p_lines = plines_win(curwin, curwin->w_cursor.lnum, false);
- --p_lines;
- if (p_lines > curwin->w_wrow + p_so) {
+ 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;
+ } else {
+ n = plines;
}
- else
- n = p_lines;
if ((colnr_T)n >= curwin->w_height_inner + curwin->w_skipcol / width) {
extra += 2;
}
- if (extra == 3 || p_lines < p_so * 2) {
+ if (extra == 3 || plines < p_so * 2) {
// not enough room for 'scrolloff', put cursor in the middle
n = curwin->w_virtcol / width;
if (n > curwin->w_height_inner / 2) {
@@ -892,8 +892,8 @@ void curs_columns(
n = 0;
}
// don't skip more than necessary
- if (n > p_lines - curwin->w_height_inner + 1) {
- n = p_lines - curwin->w_height_inner + 1;
+ if (n > plines - curwin->w_height_inner + 1) {
+ n = plines - curwin->w_height_inner + 1;
}
curwin->w_skipcol = n * width;
} else if (extra == 1) {
@@ -1245,12 +1245,12 @@ static void botline_forw(lineoff_T *lp)
} else {
++lp->lnum;
lp->fill = 0;
- if (lp->lnum > curbuf->b_ml.ml_line_count)
+ if (lp->lnum > curbuf->b_ml.ml_line_count) {
lp->height = MAXCOL;
- else if (hasFolding(lp->lnum, NULL, &lp->lnum))
- /* Add a closed fold */
+ } else if (hasFolding(lp->lnum, NULL, &lp->lnum)) {
+ // Add a closed fold
lp->height = 1;
- else {
+ } else {
lp->height = plines_nofill(lp->lnum);
}
}
diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c
index c1ad3bd829..81c9f1e3f4 100644
--- a/src/nvim/msgpack_rpc/channel.c
+++ b/src/nvim/msgpack_rpc/channel.c
@@ -344,7 +344,7 @@ static void handle_request(Channel *channel, msgpack_object *request)
evdata->args = args;
evdata->request_id = request_id;
channel_incref(channel);
- if (handler.async) {
+ if (handler.fast) {
bool is_get_mode = handler.fn == handle_nvim_get_mode;
if (is_get_mode && !input_blocking()) {
diff --git a/src/nvim/msgpack_rpc/helpers.c b/src/nvim/msgpack_rpc/helpers.c
index 3f768dcc0c..18b0bf3c16 100644
--- a/src/nvim/msgpack_rpc/helpers.c
+++ b/src/nvim/msgpack_rpc/helpers.c
@@ -384,10 +384,7 @@ void msgpack_rpc_from_object(const Object result, msgpack_packer *const res)
&& kObjectTypeTabpage == kObjectTypeWindow + 1,
"Buffer, window and tabpage enum items are in order");
switch (cur.aobj->type) {
- case kObjectTypeNil: {
- msgpack_pack_nil(res);
- break;
- }
+ case kObjectTypeNil:
case kObjectTypeLuaRef: {
// TODO(bfredl): could also be an error. Though kObjectTypeLuaRef
// should only appear when the caller has opted in to handle references,
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index af2d24e9db..73841cf449 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -18,6 +18,7 @@
#include "nvim/ascii.h"
#include "nvim/normal.h"
#include "nvim/buffer.h"
+#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/diff.h"
@@ -1376,9 +1377,8 @@ static void set_vcount_ca(cmdarg_T *cap, bool *set_prevcount)
*set_prevcount = false; /* only set v:prevcount once */
}
-/*
- * Handle an operator after visual mode or when the movement is finished
- */
+// Handle an operator after Visual mode or when the movement is finished.
+// "gui_yank" is true when yanking text for the clipboard.
void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
{
oparg_T *oap = cap->oap;
@@ -1402,8 +1402,12 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
* If an operation is pending, handle it...
*/
if ((finish_op
- || VIsual_active
- ) && oap->op_type != OP_NOP) {
+ || VIsual_active)
+ && oap->op_type != OP_NOP) {
+ // Yank can be redone when 'y' is in 'cpoptions', but not when yanking
+ // for the clipboard.
+ const bool redo_yank = vim_strchr(p_cpo, CPO_YANK) != NULL && !gui_yank;
+
// Avoid a problem with unwanted linebreaks in block mode
if (curwin->w_p_lbr) {
curwin->w_valid &= ~VALID_VIRTCOL;
@@ -1433,9 +1437,9 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
VIsual_reselect = false;
}
- /* Only redo yank when 'y' flag is in 'cpoptions'. */
- /* Never redo "zf" (define fold). */
- if ((vim_strchr(p_cpo, CPO_YANK) != NULL || oap->op_type != OP_YANK)
+ // Only redo yank when 'y' flag is in 'cpoptions'.
+ // Never redo "zf" (define fold).
+ if ((redo_yank || oap->op_type != OP_YANK)
&& ((!VIsual_active || oap->motion_force)
// Also redo Operator-pending Visual mode mappings.
|| (cap->cmdchar == ':' && oap->op_type != OP_COLON))
@@ -1608,8 +1612,8 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
resel_VIsual_line_count = oap->line_count;
}
- /* can't redo yank (unless 'y' is in 'cpoptions') and ":" */
- if ((vim_strchr(p_cpo, CPO_YANK) != NULL || oap->op_type != OP_YANK)
+ // can't redo yank (unless 'y' is in 'cpoptions') and ":"
+ if ((redo_yank || oap->op_type != OP_YANK)
&& oap->op_type != OP_COLON
&& oap->op_type != OP_FOLD
&& oap->op_type != OP_FOLDOPEN
@@ -1814,7 +1818,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
}
} else {
curwin->w_p_lbr = lbr_saved;
- (void)op_yank(oap, !gui_yank);
+ (void)op_yank(oap, !gui_yank, false);
}
check_cursor_col();
break;
@@ -2093,13 +2097,15 @@ static void op_function(oparg_T *oap)
decl(&curbuf->b_op_end);
}
- const char_u *const argv[1] = {
- (const char_u *)(((const char *const[]) {
+ typval_T argv[2];
+ argv[0].v_type = VAR_STRING;
+ argv[1].v_type = VAR_UNKNOWN;
+ argv[0].vval.v_string =
+ (char_u *)(((const char *const[]) {
[kMTBlockWise] = "block",
[kMTLineWise] = "line",
[kMTCharWise] = "char",
- })[oap->motion_type]),
- };
+ })[oap->motion_type]);
// Reset virtual_op so that 'virtualedit' can be changed in the
// function.
@@ -3386,8 +3392,8 @@ bool add_to_showcmd(int c)
void add_to_showcmd_c(int c)
{
- if (!add_to_showcmd(c))
- setcursor();
+ add_to_showcmd(c);
+ setcursor();
}
/*
@@ -3448,18 +3454,18 @@ static void display_showcmd(void)
return;
}
+ int showcmd_row = Rows - 1;
+ grid_puts_line_start(&default_grid, showcmd_row);
+
if (!showcmd_is_clear) {
- grid_puts(&default_grid, showcmd_buf, (int)Rows - 1, sc_col, 0);
+ grid_puts(&default_grid, showcmd_buf, showcmd_row, sc_col, 0);
}
- /*
- * clear the rest of an old message by outputting up to SHOWCMD_COLS
- * spaces
- */
- grid_puts(&default_grid, (char_u *)" " + len, (int)Rows - 1,
+ // clear the rest of an old message by outputting up to SHOWCMD_COLS spaces
+ grid_puts(&default_grid, (char_u *)" " + len, showcmd_row,
sc_col + len, 0);
- setcursor(); /* put cursor back where it belongs */
+ grid_puts_line_flush(false);
}
/*
@@ -4035,6 +4041,9 @@ static void nv_mousescroll(cmdarg_T *cap)
} else {
mouse_scroll_horiz(cap->arg);
}
+ if (curwin != old_curwin && curwin->w_p_cul) {
+ redraw_for_cursorline(curwin);
+ }
curwin->w_redr_status = true;
@@ -5227,12 +5236,8 @@ static void nv_down(cmdarg_T *cap)
cap->arg = FORWARD;
nv_page(cap);
} else if (bt_quickfix(curbuf) && cap->cmdchar == CAR) {
- // In a quickfix window a <CR> jumps to the error under the cursor.
- if (curwin->w_llist_ref == NULL) {
- do_cmdline_cmd(".cc"); // quickfix window
- } else {
- do_cmdline_cmd(".ll"); // location list window
- }
+ // Quickfix window only: view the result under the cursor.
+ qf_view_result(false);
} else {
// In the cmdline window a <CR> executes the command.
if (cmdwin_type != 0 && cap->cmdchar == CAR) {
@@ -7463,8 +7468,12 @@ static void nv_esc(cmdarg_T *cap)
&& cmdwin_type == 0
&& !VIsual_active
&& no_reason) {
- MSG(_("Type :qa! and press <Enter> to abandon all changes"
- " and exit Nvim"));
+ if (anyBufIsChanged()) {
+ MSG(_("Type :qa! and press <Enter> to abandon all changes"
+ " and exit Nvim"));
+ } else {
+ MSG(_("Type :qa and press <Enter> to exit Nvim"));
+ }
}
/* Don't reset "restart_edit" when 'insertmode' is set, it won't be
@@ -7987,8 +7996,8 @@ static void nv_event(cmdarg_T *cap)
multiqueue_process_events(main_loop.events);
finish_op = false;
if (may_restart) {
- // Tricky: if restart_edit was set before the handler we are in ctrl-o mode
- // but if not, the event should be allow to trigger :startinsert
+ // Tricky: if restart_edit was set before the handler we are in ctrl-o mode,
+ // but if not, the event should be allowed to trigger :startinsert.
cap->retval |= CA_COMMAND_BUSY; // don't call edit() now
}
}
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index 2a3b7beb8e..4f1709bb1f 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -15,6 +15,7 @@
#include "nvim/ascii.h"
#include "nvim/ops.h"
#include "nvim/buffer.h"
+#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/assert.h"
@@ -379,8 +380,10 @@ static void shift_block(oparg_T *oap, int amount)
/* if we're splitting a TAB, allow for it */
bd.textcol -= bd.pre_whitesp_c - (bd.startspaces != 0);
const int len = (int)STRLEN(bd.textstart) + 1;
- newp = (char_u *)xmalloc((size_t)(bd.textcol + i + j + len));
- memset(newp, NUL, (size_t)(bd.textcol + i + j + len));
+ int col = bd.textcol + i +j + len;
+ assert(col >= 0);
+ newp = (char_u *)xmalloc((size_t)col);
+ memset(newp, NUL, (size_t)col);
memmove(newp, oldp, (size_t)bd.textcol);
memset(newp + bd.textcol, TAB, (size_t)i);
memset(newp + bd.textcol + i, ' ', (size_t)j);
@@ -1398,9 +1401,11 @@ int op_delete(oparg_T *oap)
*/
if (oap->regname != '_') {
yankreg_T *reg = NULL;
+ int did_yank = false;
if (oap->regname != 0) {
- //yank without message
- if (!op_yank(oap, false)) {
+ // yank without message
+ did_yank = op_yank(oap, false, true);
+ if (!did_yank) {
// op_yank failed, don't do anything
return OK;
}
@@ -1421,6 +1426,7 @@ int op_delete(oparg_T *oap)
y_regs[1].y_array = NULL; // set register "1 to empty
reg = &y_regs[1];
op_yank_reg(oap, false, reg, false);
+ did_yank = true;
}
/* Yank into small delete register when no named register specified
@@ -1429,13 +1435,14 @@ int op_delete(oparg_T *oap)
&& oap->line_count == 1) {
reg = get_yank_register('-', YREG_YANK);
op_yank_reg(oap, false, reg, false);
+ did_yank = true;
}
- if (oap->regname == 0) {
+ if (did_yank || oap->regname == 0) {
if (reg == NULL) {
abort();
}
- set_clipboard(0, reg);
+ set_clipboard(oap->regname, reg);
do_autocmd_textyankpost(oap, reg);
}
@@ -1471,7 +1478,8 @@ int op_delete(oparg_T *oap)
// copy up to deleted part
memmove(newp, oldp, (size_t)bd.textcol);
// insert spaces
- memset(newp + bd.textcol, ' ', (size_t)(bd.startspaces + bd.endspaces));
+ memset(newp + bd.textcol, ' ', (size_t)bd.startspaces +
+ (size_t)bd.endspaces);
// copy the part after the deleted part
oldp += bd.textcol + bd.textlen;
STRMOVE(newp + bd.textcol + bd.startspaces + bd.endspaces, oldp);
@@ -1638,12 +1646,25 @@ static void mb_adjust_opend(oparg_T *oap)
/*
* Put character 'c' at position 'lp'
*/
-static inline void pchar(pos_T lp, int c)
+static inline void pbyte(pos_T lp, int c)
{
assert(c <= UCHAR_MAX);
*(ml_get_buf(curbuf, lp.lnum, true) + lp.col) = (char_u)c;
}
+// Replace the character under the cursor with "c".
+// This takes care of multi-byte characters.
+static void replace_character(int c)
+{
+ const int n = State;
+
+ State = REPLACE;
+ ins_char(c);
+ State = n;
+ // Backup to the replaced character.
+ dec_cursor();
+}
+
/*
* Replace a whole area with one character.
*/
@@ -1730,7 +1751,7 @@ int op_replace(oparg_T *oap, int c)
oldp = get_cursor_line_ptr();
oldlen = (int)STRLEN(oldp);
- size_t newp_size = (size_t)(bd.textcol + bd.startspaces);
+ size_t newp_size = (size_t)bd.textcol + (size_t)bd.startspaces;
if (had_ctrl_v_cr || (c != '\r' && c != '\n')) {
newp_size += (size_t)numc;
if (!bd.is_short) {
@@ -1747,6 +1768,8 @@ int op_replace(oparg_T *oap, int c)
// insert replacement chars CHECK FOR ALLOCATED SPACE
// REPLACE_CR_NCHAR/REPLACE_NL_NCHAR is used for entering CR literally.
size_t after_p_len = 0;
+ int col = oldlen - bd.textcol - bd.textlen + 1;
+ assert(col >= 0);
if (had_ctrl_v_cr || (c != '\r' && c != '\n')) {
// strlen(newp) at this point
int newp_len = bd.textcol + bd.startspaces;
@@ -1758,12 +1781,11 @@ int op_replace(oparg_T *oap, int c)
memset(newp + newp_len, ' ', (size_t)bd.endspaces);
newp_len += bd.endspaces;
// copy the part after the changed part
- memmove(newp + newp_len, oldp,
- (size_t)(oldlen - bd.textcol - bd.textlen + 1));
+ memmove(newp + newp_len, oldp, (size_t)col);
}
} else {
// Replacing with \r or \n means splitting the line.
- after_p_len = (size_t)(oldlen - bd.textcol - bd.textlen + 1);
+ after_p_len = (size_t)col;
after_p = (char_u *)xmalloc(after_p_len);
memmove(after_p, oldp, after_p_len);
}
@@ -1795,12 +1817,7 @@ int op_replace(oparg_T *oap, int c)
* with a multi-byte and the other way around. */
if (curwin->w_cursor.lnum == oap->end.lnum)
oap->end.col += (*mb_char2len)(c) - (*mb_char2len)(n);
- n = State;
- State = REPLACE;
- ins_char(c);
- State = n;
- /* Backup to the replaced character. */
- dec_cursor();
+ replace_character(c);
} else {
if (n == TAB) {
int end_vcol = 0;
@@ -1815,7 +1832,7 @@ int op_replace(oparg_T *oap, int c)
if (curwin->w_cursor.lnum == oap->end.lnum)
getvpos(&oap->end, end_vcol);
}
- pchar(curwin->w_cursor, c);
+ pbyte(curwin->w_cursor, c);
}
} else if (virtual_op && curwin->w_cursor.lnum == oap->end.lnum) {
int virtcols = oap->end.coladd;
@@ -1830,9 +1847,14 @@ int op_replace(oparg_T *oap, int c)
coladvance_force(getviscol2(oap->end.col, oap->end.coladd) + 1);
curwin->w_cursor.col -= (virtcols + 1);
for (; virtcols >= 0; virtcols--) {
- pchar(curwin->w_cursor, c);
- if (inc(&curwin->w_cursor) == -1)
+ if (utf_char2len(c) > 1) {
+ replace_character(c);
+ } else {
+ pbyte(curwin->w_cursor, c);
+ }
+ if (inc(&curwin->w_cursor) == -1) {
break;
+ }
}
}
@@ -1953,23 +1975,20 @@ static int swapchars(int op_type, pos_T *pos, int length)
return did_change;
}
-/*
- * If op_type == OP_UPPER: make uppercase,
- * if op_type == OP_LOWER: make lowercase,
- * if op_type == OP_ROT13: do rot13 encoding,
- * else swap case of character at 'pos'
- * returns TRUE when something actually changed.
- */
-int swapchar(int op_type, pos_T *pos)
+// If op_type == OP_UPPER: make uppercase,
+// if op_type == OP_LOWER: make lowercase,
+// if op_type == OP_ROT13: do rot13 encoding,
+// else swap case of character at 'pos'
+// returns true when something actually changed.
+bool swapchar(int op_type, pos_T *pos)
+ FUNC_ATTR_NONNULL_ARG(2)
{
- int c;
- int nc;
-
- c = gchar_pos(pos);
+ const int c = gchar_pos(pos);
- /* Only do rot13 encoding for ASCII characters. */
- if (c >= 0x80 && op_type == OP_ROT13)
- return FALSE;
+ // Only do rot13 encoding for ASCII characters.
+ if (c >= 0x80 && op_type == OP_ROT13) {
+ return false;
+ }
if (op_type == OP_UPPER && c == 0xdf) {
pos_T sp = curwin->w_cursor;
@@ -1983,7 +2002,7 @@ int swapchar(int op_type, pos_T *pos)
inc(pos);
}
- nc = c;
+ int nc = c;
if (mb_islower(c)) {
if (op_type == OP_ROT13) {
nc = ROT13(c, 'a');
@@ -1998,7 +2017,7 @@ int swapchar(int op_type, pos_T *pos)
}
}
if (nc != c) {
- if (enc_utf8 && (c >= 0x80 || nc >= 0x80)) {
+ if (c >= 0x80 || nc >= 0x80) {
pos_T sp = curwin->w_cursor;
curwin->w_cursor = *pos;
@@ -2006,11 +2025,12 @@ int swapchar(int op_type, pos_T *pos)
del_bytes(utf_ptr2len(get_cursor_pos_ptr()), FALSE, FALSE);
ins_char(nc);
curwin->w_cursor = sp;
- } else
- pchar(*pos, nc);
- return TRUE;
+ } else {
+ pbyte(*pos, nc);
+ }
+ return true;
}
- return FALSE;
+ return false;
}
/*
@@ -2361,8 +2381,9 @@ void free_register(yankreg_T *reg)
///
/// @param oap operator arguments
/// @param message show message when more than `&report` lines are yanked.
+/// @param deleting whether the function was called from a delete operation.
/// @returns whether the operation register was writable.
-bool op_yank(oparg_T *oap, bool message)
+bool op_yank(oparg_T *oap, bool message, int deleting)
FUNC_ATTR_NONNULL_ALL
{
// check for read-only register
@@ -2376,8 +2397,11 @@ bool op_yank(oparg_T *oap, bool message)
yankreg_T *reg = get_yank_register(oap->regname, YREG_YANK);
op_yank_reg(oap, message, reg, is_append_register(oap->regname));
- set_clipboard(oap->regname, reg);
- do_autocmd_textyankpost(oap, reg);
+ // op_delete will set_clipboard and do_autocmd
+ if (!deleting) {
+ set_clipboard(oap->regname, reg);
+ do_autocmd_textyankpost(oap, reg);
+ }
return true;
}
@@ -2496,9 +2520,9 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append)
endcol = (colnr_T)STRLEN(p);
if (startcol > endcol
|| is_oneChar
- )
+ ) {
bd.textlen = 0;
- else {
+ } else {
bd.textlen = endcol - startcol + oap->inclusive;
}
bd.textstart = p + startcol;
@@ -2591,8 +2615,9 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append)
static void yank_copy_line(yankreg_T *reg, struct block_def *bd, size_t y_idx)
{
- char_u *pnew = xmallocz((size_t)(bd->startspaces + bd->endspaces
- + bd->textlen));
+ int size = bd->startspaces + bd->endspaces + bd->textlen;
+ assert(size >= 0);
+ char_u *pnew = xmallocz((size_t)size);
reg->y_array[y_idx] = pnew;
memset(pnew, ' ', (size_t)bd->startspaces);
pnew += bd->startspaces;
@@ -3074,8 +3099,9 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
memset(ptr, ' ', (size_t)bd.endspaces);
ptr += bd.endspaces;
// move the text after the cursor to the end of the line.
- memmove(ptr, oldp + bd.textcol + delcount,
- (size_t)((int)oldlen - bd.textcol - delcount + 1));
+ int columns = (int)oldlen - bd.textcol - delcount + 1;
+ assert(columns >= 0);
+ memmove(ptr, oldp + bd.textcol + delcount, (size_t)columns);
ml_replace(curwin->w_cursor.lnum, newp, false);
++curwin->w_cursor.lnum;
@@ -3198,11 +3224,11 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
xfree(newp);
oldp = ml_get(lnum);
- newp = (char_u *) xmalloc((size_t)(col + yanklen + 1));
- /* copy first part of line */
+ newp = (char_u *)xmalloc((size_t)col + (size_t)yanklen + 1);
+ // copy first part of line
memmove(newp, oldp, (size_t)col);
- /* append to first line */
- memmove(newp + col, y_array[0], (size_t)(yanklen + 1));
+ // append to first line
+ memmove(newp + col, y_array[0], (size_t)yanklen + 1);
ml_replace(lnum, newp, false);
curwin->w_cursor.lnum = lnum;
@@ -3416,13 +3442,13 @@ void ex_display(exarg_T *eap)
msg_putchar(name);
MSG_PUTS(" ");
- int n = (int)Columns - 6;
+ int n = Columns - 6;
for (size_t j = 0; j < yb->y_size && n > 1; j++) {
if (j) {
MSG_PUTS_ATTR("^J", attr);
n -= 2;
}
- for (p = yb->y_array[j]; *p && (n -= ptr2cells(p)) >= 0; ++p) {
+ for (p = yb->y_array[j]; *p && (n -= ptr2cells(p)) >= 0; p++) { // -V1019 NOLINT(whitespace/line_length)
clen = (*mb_ptr2len)(p);
msg_outtrans_len(p, clen);
p += clen - 1;
@@ -3499,8 +3525,8 @@ void ex_display(exarg_T *eap)
* display a string for do_dis()
* truncate at end of screen line
*/
-static void
-dis_msg (
+static void
+dis_msg(
char_u *p,
int skip_esc /* if TRUE, ignore trailing ESC */
)
@@ -3508,7 +3534,7 @@ dis_msg (
int n;
int l;
- n = (int)Columns - 6;
+ n = Columns - 6;
while (*p != NUL
&& !(*p == ESC && skip_esc && *(p + 1) == NUL)
&& (n -= ptr2cells(p)) >= 0) {
@@ -3694,11 +3720,11 @@ int do_join(size_t count,
}
}
- /* store the column position before last line */
+ // store the column position before last line
col = sumsize - currsize - spaces[count - 1];
- /* allocate the space for the new line */
- newp = (char_u *) xmalloc((size_t)(sumsize + 1));
+ // allocate the space for the new line
+ newp = (char_u *)xmalloc((size_t)sumsize + 1);
cend = newp + sumsize;
*cend = 0;
@@ -3738,7 +3764,7 @@ int do_join(size_t count,
if (setmark) {
// Set the '] mark.
curwin->w_buffer->b_op_end.lnum = curwin->w_cursor.lnum;
- curwin->w_buffer->b_op_end.col = (colnr_T)STRLEN(newp);
+ curwin->w_buffer->b_op_end.col = sumsize;
}
/* Only report the change in the first line here, del_lines() will report
@@ -3840,8 +3866,8 @@ static int same_leader(linenr_T lnum, int leader1_len, char_u *leader1_flags, in
/*
* Implementation of the format operator 'gq'.
*/
-void
-op_format (
+void
+op_format(
oparg_T *oap,
int keep_cursor /* keep cursor on same text char */
)
@@ -3920,8 +3946,8 @@ void op_formatexpr(oparg_T *oap)
op_format(oap, FALSE);
}
-int
-fex_format (
+int
+fex_format(
linenr_T lnum,
long count,
int c /* character to be inserted */
@@ -3963,8 +3989,8 @@ fex_format (
* Lines after the cursor line are saved for undo, caller must have saved the
* first line.
*/
-void
-format_lines (
+void
+format_lines(
linenr_T line_count,
int avoid_fex /* don't use 'formatexpr' */
)
@@ -5461,7 +5487,7 @@ void cursor_pos_info(dict_T *dict)
byte_count_cursor = byte_count
+ line_count_info(ml_get(lnum), &word_count_cursor,
&char_count_cursor,
- (varnumber_T)(curwin->w_cursor.col + 1),
+ (varnumber_T)curwin->w_cursor.col + 1,
eol_size);
}
}
@@ -5479,8 +5505,10 @@ void cursor_pos_info(dict_T *dict)
if (l_VIsual_active) {
if (l_VIsual_mode == Ctrl_V && curwin->w_curswant < MAXCOL) {
getvcols(curwin, &min_pos, &max_pos, &min_pos.col, &max_pos.col);
+ int64_t cols;
+ STRICT_SUB(oparg.end_vcol + 1, oparg.start_vcol, &cols, int64_t);
vim_snprintf((char *)buf1, sizeof(buf1), _("%" PRId64 " Cols; "),
- (int64_t)(oparg.end_vcol - oparg.start_vcol + 1));
+ cols);
} else {
buf1[0] = NUL;
}
@@ -5873,33 +5901,45 @@ static inline bool reg_empty(const yankreg_T *const reg)
&& *(reg->y_array[0]) == NUL));
}
-/// Iterate over registerrs
+/// Iterate over global registers.
+///
+/// @see op_register_iter
+const void *op_global_reg_iter(const void *const iter, char *const name,
+ yankreg_T *const reg, bool *is_unnamed)
+ FUNC_ATTR_NONNULL_ARG(2, 3, 4) FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ return op_reg_iter(iter, y_regs, name, reg, is_unnamed);
+}
+
+/// Iterate over registers `regs`.
///
/// @param[in] iter Iterator. Pass NULL to start iteration.
+/// @param[in] regs Registers list to be iterated.
/// @param[out] name Register name.
/// @param[out] reg Register contents.
///
-/// @return Pointer that needs to be passed to next `op_register_iter` call or
+/// @return Pointer that must be passed to next `op_register_iter` call or
/// NULL if iteration is over.
-const void *op_register_iter(const void *const iter, char *const name,
- yankreg_T *const reg, bool *is_unnamed)
- FUNC_ATTR_NONNULL_ARG(2, 3) FUNC_ATTR_WARN_UNUSED_RESULT
+const void *op_reg_iter(const void *const iter, const yankreg_T *const regs,
+ char *const name, yankreg_T *const reg,
+ bool *is_unnamed)
+ FUNC_ATTR_NONNULL_ARG(3, 4, 5) FUNC_ATTR_WARN_UNUSED_RESULT
{
*name = NUL;
const yankreg_T *iter_reg = (iter == NULL
- ? &(y_regs[0])
- : (const yankreg_T *const) iter);
- while (iter_reg - &(y_regs[0]) < NUM_SAVED_REGISTERS && reg_empty(iter_reg)) {
+ ? &(regs[0])
+ : (const yankreg_T *const)iter);
+ while (iter_reg - &(regs[0]) < NUM_SAVED_REGISTERS && reg_empty(iter_reg)) {
iter_reg++;
}
- if (iter_reg - &(y_regs[0]) == NUM_SAVED_REGISTERS || reg_empty(iter_reg)) {
+ if (iter_reg - &(regs[0]) == NUM_SAVED_REGISTERS || reg_empty(iter_reg)) {
return NULL;
}
- int iter_off = (int)(iter_reg - &(y_regs[0]));
+ int iter_off = (int)(iter_reg - &(regs[0]));
*name = (char)get_register_name(iter_off);
*reg = *iter_reg;
*is_unnamed = (iter_reg == y_previous);
- while (++iter_reg - &(y_regs[0]) < NUM_SAVED_REGISTERS) {
+ while (++iter_reg - &(regs[0]) < NUM_SAVED_REGISTERS) {
if (!reg_empty(iter_reg)) {
return (void *) iter_reg;
}
@@ -5908,7 +5948,7 @@ const void *op_register_iter(const void *const iter, char *const name,
}
/// Get a number of non-empty registers
-size_t op_register_amount(void)
+size_t op_reg_amount(void)
FUNC_ATTR_WARN_UNUSED_RESULT
{
size_t ret = 0;
@@ -5927,7 +5967,7 @@ size_t op_register_amount(void)
/// @param[in] is_unnamed Whether to set the unnamed regiseter to reg
///
/// @return true on success, false on failure.
-bool op_register_set(const char name, const yankreg_T reg, bool is_unnamed)
+bool op_reg_set(const char name, const yankreg_T reg, bool is_unnamed)
{
int i = op_reg_index(name);
if (i == -1) {
@@ -5947,7 +5987,7 @@ bool op_register_set(const char name, const yankreg_T reg, bool is_unnamed)
/// @param[in] name Register name.
///
/// @return Pointer to the register contents or NULL.
-const yankreg_T *op_register_get(const char name)
+const yankreg_T *op_reg_get(const char name)
{
int i = op_reg_index(name);
if (i == -1) {
@@ -5961,7 +6001,7 @@ const yankreg_T *op_register_get(const char name)
/// @param[in] name Register name.
///
/// @return true on success, false on failure.
-bool op_register_set_previous(const char name)
+bool op_reg_set_previous(const char name)
FUNC_ATTR_WARN_UNUSED_RESULT
{
int i = op_reg_index(name);
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 8ff62d5b12..55b69edba0 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -74,6 +74,7 @@
#include "nvim/strings.h"
#include "nvim/syntax.h"
#include "nvim/ui.h"
+#include "nvim/ui_compositor.h"
#include "nvim/undo.h"
#include "nvim/window.h"
#include "nvim/os/os.h"
@@ -253,6 +254,7 @@ typedef struct vimoption {
#define P_RWINONLY 0x10000000U ///< only redraw current window
#define P_NDNAME 0x20000000U ///< only normal dir name chars allowed
#define P_UI_OPTION 0x40000000U ///< send option to remote ui
+#define P_MLE 0x80000000U ///< under control of 'modelineexpr'
#define HIGHLIGHT_INIT \
"8:SpecialKey,~:EndOfBuffer,z:TermCursor,Z:TermCursorNC,@:NonText," \
@@ -626,7 +628,11 @@ void set_init_1(void)
char *p;
# ifdef UNIX
if (*names[n] == NUL) {
+# ifdef __APPLE__
+ p = "/private/tmp";
+# else
p = "/tmp";
+# endif
mustfree = false;
} else
# endif
@@ -1323,6 +1329,11 @@ int do_set(
errmsg = (char_u *)_("E520: Not allowed in a modeline");
goto skip;
}
+ if ((flags & P_MLE) && !p_mle) {
+ errmsg = (char_u *)_(
+ "E992: Not allowed in a modeline when 'modelineexpr' is off");
+ goto skip;
+ }
// In diff mode some options are overruled. This avoids that
// 'foldmethod' becomes "marker" instead of "diff" and that
// "wrap" gets set.
@@ -2166,10 +2177,8 @@ static char_u *option_expand(int opt_idx, char_u *val)
return NameBuff;
}
-/*
- * After setting various option values: recompute variables that depend on
- * option values.
- */
+// After setting various option values: recompute variables that depend on
+// option values.
static void didset_options(void)
{
// initialize the table for 'iskeyword' et.al.
@@ -2182,8 +2191,10 @@ static void didset_options(void)
(void)opt_strings_flags(p_vop, p_ssop_values, &vop_flags, true);
(void)opt_strings_flags(p_fdo, p_fdo_values, &fdo_flags, true);
(void)opt_strings_flags(p_dy, p_dy_values, &dy_flags, true);
+ (void)opt_strings_flags(p_rdb, p_rdb_values, &rdb_flags, true);
(void)opt_strings_flags(p_tc, p_tc_values, &tc_flags, false);
(void)opt_strings_flags(p_ve, p_ve_values, &ve_flags, true);
+ (void)opt_strings_flags(p_wop, p_wop_values, &wop_flags, true);
(void)spell_check_msm();
(void)spell_check_sps();
(void)compile_cap_prog(curwin->w_s);
@@ -2640,6 +2651,10 @@ did_set_string_option(
if (opt_strings_flags(p_vop, p_ssop_values, &vop_flags, true) != OK) {
errmsg = e_invarg;
}
+ } else if (varp == &p_rdb) { // 'redrawdebug'
+ if (opt_strings_flags(p_rdb, p_rdb_values, &rdb_flags, true) != OK) {
+ errmsg = e_invarg;
+ }
} else if (varp == &p_sbo) { // 'scrollopt'
if (check_opt_strings(p_sbo, p_scbopt_values, true) != OK) {
errmsg = e_invarg;
@@ -3493,7 +3508,9 @@ static char_u *set_chars_option(win_T *wp, char_u **varp)
{
int round, i, len, entries;
char_u *p, *s;
- int c1 = 0, c2 = 0, c3 = 0;
+ int c1;
+ int c2 = 0;
+ int c3 = 0;
struct chars_tab {
int *cp; ///< char value
@@ -3560,7 +3577,7 @@ static char_u *set_chars_option(win_T *wp, char_u **varp)
if (STRNCMP(p, tab[i].name, len) == 0
&& p[len] == ':'
&& p[len + 1] != NUL) {
- c1 = c2 = c3 = 0;
+ c2 = c3 = 0;
s = p + len + 1;
// TODO(bfredl): use schar_T representation and utfc_ptr2len
@@ -3752,7 +3769,8 @@ static bool parse_winhl_opt(win_T *wp)
size_t nlen = (size_t)(colon-p);
char *hi = colon+1;
char *commap = xstrchrnul(hi, ',');
- int hl_id = syn_check_group((char_u *)hi, (int)(commap-hi));
+ int len = (int)(commap-hi);
+ int hl_id = len ? syn_check_group((char_u *)hi, len) : -1;
if (strncmp("Normal", p, nlen) == 0) {
w_hl_id_normal = hl_id;
@@ -4149,7 +4167,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value,
char_u *errmsg = NULL;
long old_value = *(long *)varp;
long old_Rows = Rows; // remember old Rows
- long old_Columns = Columns; // remember old Columns
long *pp = (long *)varp;
// Disallow changing some options from secure mode.
@@ -4264,7 +4281,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value,
} else if (pp == &curwin->w_p_nuw || pp == &curwin->w_allbuf_opt.wo_nuw) {
if (value < 1) {
errmsg = e_positive;
- } else if (value > 10) {
+ } else if (value > 20) {
errmsg = e_invarg;
}
} else if (pp == &curbuf->b_p_iminsert || pp == &p_iminsert) {
@@ -4381,11 +4398,10 @@ static char *set_num_option(int opt_idx, char_u *varp, long value,
}
} else if (pp == &p_pb) {
p_pb = MAX(MIN(p_pb, 100), 0);
- if (old_value != 0) {
- hl_invalidate_blends();
- }
+ hl_invalidate_blends();
+ pum_grid.blending = (p_pb > 0);
if (pum_drawn()) {
- pum_recompose();
+ pum_redraw();
}
} else if (pp == &p_pyx) {
if (p_pyx != 0 && p_pyx != 2 && p_pyx != 3) {
@@ -4408,40 +4424,50 @@ static char *set_num_option(int opt_idx, char_u *varp, long value,
}
} else if (pp == &curwin->w_p_nuw) {
curwin->w_nrwidth_line_count = 0;
+ } else if (pp == &curwin->w_p_winbl && value != old_value) {
+ // 'floatblend'
+ curwin->w_p_winbl = MAX(MIN(curwin->w_p_winbl, 100), 0);
+ curwin->w_hl_needs_update = true;
+ curwin->w_grid.blending = curwin->w_p_winbl > 0;
}
// Check the (new) bounds for Rows and Columns here.
- if (Rows < min_rows() && full_screen) {
+ if (p_lines < min_rows() && full_screen) {
if (errbuf != NULL) {
vim_snprintf((char *)errbuf, errbuflen,
_("E593: Need at least %d lines"), min_rows());
errmsg = errbuf;
}
- Rows = min_rows();
+ p_lines = min_rows();
}
- if (Columns < MIN_COLUMNS && full_screen) {
+ if (p_columns < MIN_COLUMNS && full_screen) {
if (errbuf != NULL) {
vim_snprintf((char *)errbuf, errbuflen,
_("E594: Need at least %d columns"), MIN_COLUMNS);
errmsg = errbuf;
}
- Columns = MIN_COLUMNS;
+ p_columns = MIN_COLUMNS;
}
- limit_screen_size();
+ // True max size is defined by check_shellsize()
+ p_lines = MIN(p_lines, INT_MAX);
+ p_columns = MIN(p_columns, INT_MAX);
// If the screen (shell) height has been changed, assume it is the
// physical screenheight.
- if (old_Rows != Rows || old_Columns != Columns) {
+ if (p_lines != Rows || p_columns != Columns) {
// Changing the screen size is not allowed while updating the screen.
if (updating_screen) {
*pp = old_value;
} else if (full_screen) {
- screen_resize((int)Columns, (int)Rows);
+ screen_resize((int)p_columns, (int)p_lines);
} else {
+ // TODO(bfredl): is this branch ever needed?
// Postpone the resizing; check the size and cmdline position for
// messages.
+ Rows = (int)p_lines;
+ Columns = (int)p_columns;
check_shellsize();
if (cmdline_row > Rows - p_ch && Rows > p_ch) {
assert(p_ch >= 0 && Rows - p_ch <= INT_MAX);
@@ -5027,6 +5053,11 @@ showoptions(
// collect the items in items[]
item_count = 0;
for (p = &options[0]; p->fullname != NULL; p++) {
+ // apply :filter /pat/
+ if (message_filtered((char_u *)p->fullname)) {
+ continue;
+ }
+
varp = NULL;
if (opt_flags != 0) {
if (p->indir != PV_NONE) {
@@ -5054,8 +5085,8 @@ showoptions(
* display the items
*/
if (run == 1) {
- assert(Columns <= LONG_MAX - GAP
- && Columns + GAP >= LONG_MIN + 3
+ assert(Columns <= INT_MAX - GAP
+ && Columns + GAP >= INT_MIN + 3
&& (Columns + GAP - 3) / INC >= INT_MIN
&& (Columns + GAP - 3) / INC <= INT_MAX);
cols = (int)((Columns + GAP - 3) / INC);
@@ -5703,6 +5734,7 @@ static char_u *get_varp(vimoption_T *p)
case PV_WINHL: return (char_u *)&(curwin->w_p_winhl);
case PV_FCS: return (char_u *)&(curwin->w_p_fcs);
case PV_LCS: return (char_u *)&(curwin->w_p_lcs);
+ case PV_WINBL: return (char_u *)&(curwin->w_p_winbl);
default: IEMSG(_("E356: get_varp ERROR"));
}
// always return a valid pointer to avoid a crash!
@@ -5782,6 +5814,7 @@ void copy_winopt(winopt_T *from, winopt_T *to)
to->wo_winhl = vim_strsave(from->wo_winhl);
to->wo_fcs = vim_strsave(from->wo_fcs);
to->wo_lcs = vim_strsave(from->wo_lcs);
+ to->wo_winbl = from->wo_winbl;
check_winopt(to); // don't want NULL pointers
}
@@ -5844,7 +5877,8 @@ void didset_window_options(win_T *wp)
briopt_check(wp);
set_chars_option(wp, &wp->w_p_fcs);
set_chars_option(wp, &wp->w_p_lcs);
- parse_winhl_opt(wp);
+ parse_winhl_opt(wp); // sets w_hl_needs_update also for w_p_winbl
+ wp->w_grid.blending = wp->w_p_winbl > 0;
}
diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h
index a9f44976c9..fa6ebc70e5 100644
--- a/src/nvim/option_defs.h
+++ b/src/nvim/option_defs.h
@@ -181,28 +181,29 @@ enum {
0, \
})
-/* characters for p_go: */
-#define GO_ASEL 'a' /* autoselect */
-#define GO_ASELML 'A' /* autoselect modeless selection */
-#define GO_BOT 'b' /* use bottom scrollbar */
-#define GO_CONDIALOG 'c' /* use console dialog */
-#define GO_TABLINE 'e' /* may show tabline */
-#define GO_FORG 'f' /* start GUI in foreground */
-#define GO_GREY 'g' /* use grey menu items */
-#define GO_HORSCROLL 'h' /* flexible horizontal scrolling */
-#define GO_ICON 'i' /* use Vim icon */
-#define GO_LEFT 'l' /* use left scrollbar */
-#define GO_VLEFT 'L' /* left scrollbar with vert split */
-#define GO_MENUS 'm' /* use menu bar */
-#define GO_NOSYSMENU 'M' /* don't source system menu */
-#define GO_POINTER 'p' /* pointer enter/leave callbacks */
-#define GO_ASELPLUS 'P' /* autoselectPlus */
-#define GO_RIGHT 'r' /* use right scrollbar */
-#define GO_VRIGHT 'R' /* right scrollbar with vert split */
-#define GO_TOOLBAR 'T' /* add toolbar */
-#define GO_FOOTER 'F' /* add footer */
-#define GO_VERTICAL 'v' /* arrange dialog buttons vertically */
-#define GO_ALL "aAbcefFghilmMprTv" /* all possible flags for 'go' */
+// characters for p_go:
+#define GO_ASEL 'a' // autoselect
+#define GO_ASELML 'A' // autoselect modeless selection
+#define GO_BOT 'b' // use bottom scrollbar
+#define GO_CONDIALOG 'c' // use console dialog
+#define GO_TABLINE 'e' // may show tabline
+#define GO_FORG 'f' // start GUI in foreground
+#define GO_GREY 'g' // use grey menu items
+#define GO_HORSCROLL 'h' // flexible horizontal scrolling
+#define GO_ICON 'i' // use Vim icon
+#define GO_LEFT 'l' // use left scrollbar
+#define GO_VLEFT 'L' // left scrollbar with vert split
+#define GO_MENUS 'm' // use menu bar
+#define GO_NOSYSMENU 'M' // don't source system menu
+#define GO_POINTER 'p' // pointer enter/leave callbacks
+#define GO_ASELPLUS 'P' // autoselectPlus
+#define GO_RIGHT 'r' // use right scrollbar
+#define GO_VRIGHT 'R' // right scrollbar with vert split
+#define GO_TOOLBAR 'T' // add toolbar
+#define GO_FOOTER 'F' // add footer
+#define GO_VERTICAL 'v' // arrange dialog buttons vertically
+#define GO_KEEPWINSIZE 'k' // keep GUI window size
+#define GO_ALL "aAbcefFghilmMprTvk" // all possible flags for 'go'
/* flags for 'comments' option */
#define COM_NEST 'n' /* comments strings nest */
@@ -365,6 +366,7 @@ static char *(p_cb_values[]) = {"unnamed", "unnamedplus", NULL};
# define CB_UNNAMEDMASK (CB_UNNAMED | CB_UNNAMEDPLUS)
EXTERN long p_cwh; // 'cmdwinheight'
EXTERN long p_ch; // 'cmdheight'
+EXTERN long p_columns; // 'columns'
EXTERN int p_confirm; // 'confirm'
EXTERN int p_cp; // 'compatible'
EXTERN char_u *p_cot; // 'completeopt'
@@ -475,7 +477,8 @@ EXTERN char_u *p_langmap; // 'langmap'
EXTERN int p_lnr; // 'langnoremap'
EXTERN int p_lrm; // 'langremap'
EXTERN char_u *p_lm; // 'langmenu'
-EXTERN long *p_linespace; // 'linespace'
+EXTERN long p_lines; // 'lines'
+EXTERN long p_linespace; // 'linespace'
EXTERN char_u *p_lispwords; // 'lispwords'
EXTERN long p_ls; // 'laststatus'
EXTERN long p_stal; // 'showtabline'
@@ -495,6 +498,7 @@ EXTERN long p_mmd; // 'maxmapdepth'
EXTERN long p_mmp; // 'maxmempattern'
EXTERN long p_mis; // 'menuitems'
EXTERN char_u *p_msm; // 'mkspellmem'
+EXTERN long p_mle; // 'modelineexpr'
EXTERN long p_mls; // 'modelines'
EXTERN char_u *p_mouse; // 'mouse'
EXTERN char_u *p_mousem; // 'mousemodel'
@@ -509,6 +513,13 @@ EXTERN char_u *p_pm; // 'patchmode'
EXTERN char_u *p_path; // 'path'
EXTERN char_u *p_cdpath; // 'cdpath'
EXTERN long p_pyx; // 'pyxversion'
+EXTERN char_u *p_rdb; // 'redrawdebug'
+EXTERN unsigned rdb_flags;
+# ifdef IN_OPTION_C
+static char *(p_rdb_values[]) = { "compositor", NULL };
+# endif
+# define RDB_COMPOSITOR 0x001
+
EXTERN long p_rdt; // 'redrawtime'
EXTERN int p_remap; // 'remap'
EXTERN long p_re; // 'regexpengine'
@@ -814,6 +825,7 @@ enum {
, WV_WINHL
, WV_FCS
, WV_LCS
+ , WV_WINBL
, WV_COUNT // must be the last one
};
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index 96e098778c..3280c92b2d 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -8,6 +8,7 @@
-- defaults={condition=nil, if_true={vi=224, vim=0}, if_false=nil},
-- secure=nil, gettext=nil, noglob=nil, normal_fname_chars=nil,
-- pri_mkrc=nil, deny_in_modelines=nil, normal_dname_chars=nil,
+-- modelineexpr=nil,
-- expand=nil, nodefault=nil, no_mkrc=nil, vi_def=true, vim=true,
-- alloced=nil,
-- save_pv_indir=nil,
@@ -39,7 +40,7 @@ local imacros=function(s)
return '(intptr_t)' .. s
end
end
-local N_=function(s)
+local N_=function(s) -- luacheck: ignore 211 (currently unused)
return function()
return 'N_(' .. cstr(s) .. ')'
end
@@ -188,7 +189,6 @@ return {
},
{
full_name='belloff', abbreviation='bo',
- deny_duplicates=true,
type='string', list='comma', scope={'global'},
deny_duplicates=true,
vi_def=true,
@@ -283,6 +283,7 @@ return {
deny_duplicates=true,
vi_def=true,
expand=true,
+ secure=true,
varname='p_cdpath',
defaults={if_true={vi=",,"}}
},
@@ -378,10 +379,9 @@ return {
full_name='columns', abbreviation='co',
type='number', scope={'global'},
no_mkrc=true,
- nodefault=true,
vi_def=true,
redraw={'everything'},
- varname='Columns',
+ varname='p_columns',
defaults={if_true={vi=macros('DFLT_COLS')}}
},
{
@@ -543,7 +543,7 @@ return {
full_name='cursorcolumn', abbreviation='cuc',
type='bool', scope={'window'},
vi_def=true,
- redraw={'current_window'},
+ redraw={'current_window_only'},
defaults={if_true={vi=false}}
},
{
@@ -847,6 +847,7 @@ return {
type='string', scope={'window'},
vi_def=true,
vim=true,
+ modelineexpr=true,
alloced=true,
redraw={'current_window'},
defaults={if_true={vi="0"}}
@@ -922,6 +923,7 @@ return {
type='string', scope={'window'},
vi_def=true,
vim=true,
+ modelineexpr=true,
alloced=true,
redraw={'current_window'},
defaults={if_true={vi="foldtext()"}}
@@ -931,6 +933,7 @@ return {
type='string', scope={'buffer'},
vi_def=true,
vim=true,
+ modelineexpr=true,
alloced=true,
varname='p_fex',
defaults={if_true={vi=""}}
@@ -1045,6 +1048,7 @@ return {
full_name='guitablabel', abbreviation='gtl',
type='string', scope={'global'},
vi_def=true,
+ modelineexpr=true,
redraw={'current_window'},
enable_if=false,
},
@@ -1136,6 +1140,7 @@ return {
full_name='iconstring',
type='string', scope={'global'},
vi_def=true,
+ modelineexpr=true,
varname='p_iconstring',
defaults={if_true={vi=""}}
},
@@ -1198,6 +1203,7 @@ return {
full_name='includeexpr', abbreviation='inex',
type='string', scope={'buffer'},
vi_def=true,
+ modelineexpr=true,
alloced=true,
varname='p_inex',
defaults={if_true={vi=""}}
@@ -1214,6 +1220,7 @@ return {
type='string', scope={'buffer'},
vi_def=true,
vim=true,
+ modelineexpr=true,
alloced=true,
varname='p_inde',
defaults={if_true={vi=""}}
@@ -1376,10 +1383,9 @@ return {
full_name='lines',
type='number', scope={'global'},
no_mkrc=true,
- nodefault=true,
vi_def=true,
redraw={'everything'},
- varname='Rows',
+ varname='p_lines',
defaults={if_true={vi=macros('DFLT_ROWS')}}
},
{
@@ -1528,6 +1534,14 @@ return {
defaults={if_true={vi=false, vim=true}}
},
{
+ full_name='modelineexpr', abbreviation='mle',
+ type='bool', scope={'global'},
+ vi_def=true,
+ secure=true,
+ varname='p_mle',
+ defaults={if_true={vi=false}}
+ },
+ {
full_name='modelines', abbreviation='mls',
type='number', scope={'global'},
vi_def=true,
@@ -1832,6 +1846,13 @@ return {
defaults={if_true={vi=false}}
},
{
+ full_name='redrawdebug', abbreviation='rdb',
+ type='string', list='onecomma', scope={'global'},
+ vi_def=true,
+ varname='p_rdb',
+ defaults={if_true={vi=''}}
+ },
+ {
full_name='redrawtime', abbreviation='rdt',
type='number', scope={'global'},
vi_def=true,
@@ -1903,6 +1924,7 @@ return {
type='string', scope={'global'},
vi_def=true,
alloced=true,
+ modelineexpr=true,
redraw={'statuslines'},
varname='p_ruf',
defaults={if_true={vi=""}}
@@ -2310,6 +2332,7 @@ return {
type='string', scope={'global', 'window'},
vi_def=true,
alloced=true,
+ modelineexpr=true,
redraw={'statuslines'},
varname='p_stl',
defaults={if_true={vi=""}}
@@ -2369,6 +2392,7 @@ return {
full_name='tabline', abbreviation='tal',
type='string', scope={'global'},
vi_def=true,
+ modelineexpr=true,
redraw={'all_windows'},
varname='p_tal',
defaults={if_true={vi=""}}
@@ -2528,6 +2552,7 @@ return {
full_name='titlestring',
type='string', scope={'global'},
vi_def=true,
+ modelineexpr=true,
varname='p_titlestring',
defaults={if_true={vi=""}}
},
@@ -2729,9 +2754,9 @@ return {
full_name='wildoptions', abbreviation='wop',
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
- vi_def=true,
+ vim=true,
varname='p_wop',
- defaults={if_true={vi=""}}
+ defaults={if_true={vi='', vim='pum,tagfile'}}
},
{
full_name='winaltkeys', abbreviation='wak',
@@ -2741,6 +2766,13 @@ return {
defaults={if_true={vi="menu"}}
},
{
+ full_name='winblend', abbreviation='winbl',
+ type='number', scope={'window'},
+ vi_def=true,
+ redraw={'current_window'},
+ defaults={if_true={vi=0}}
+ },
+ {
full_name='winhighlight', abbreviation='winhl',
type='string', scope={'window'},
vi_def=true,
diff --git a/src/nvim/os/dl.c b/src/nvim/os/dl.c
index bbd0424a82..f0fadb16f2 100644
--- a/src/nvim/os/dl.c
+++ b/src/nvim/os/dl.c
@@ -54,6 +54,7 @@ bool os_libcall(const char *libname,
// open the dynamic loadable library
if (uv_dlopen(libname, &lib)) {
EMSG2(_("dlerror = \"%s\""), uv_dlerror(&lib));
+ uv_dlclose(&lib);
return false;
}
diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c
index 871298f415..62457e155c 100644
--- a/src/nvim/os/env.c
+++ b/src/nvim/os/env.c
@@ -45,6 +45,7 @@ void env_init(void)
}
/// Like getenv(), but returns NULL if the variable is empty.
+/// @see os_env_exists
const char *os_getenv(const char *name)
FUNC_ATTR_NONNULL_ALL
{
@@ -101,10 +102,19 @@ bool os_env_exists(const char *name)
assert(r != UV_EINVAL);
if (r != 0 && r != UV_ENOENT && r != UV_ENOBUFS) {
ELOG("uv_os_getenv(%s) failed: %d %s", name, r, uv_err_name(r));
+#ifdef WIN32
+ return (r == UV_UNKNOWN);
+#endif
}
return (r == 0 || r == UV_ENOBUFS);
}
+/// Sets an environment variable.
+///
+/// Windows (Vim-compat): Empty string (:let $FOO="") undefines the env var.
+///
+/// @warning Existing pointers to the result of os_getenv("foo") are
+/// INVALID after os_setenv("foo", …).
int os_setenv(const char *name, const char *value, int overwrite)
FUNC_ATTR_NONNULL_ALL
{
@@ -115,15 +125,21 @@ int os_setenv(const char *name, const char *value, int overwrite)
if (!overwrite && os_getenv(name) != NULL) {
return 0;
}
+ if (value[0] == '\0') {
+ // Windows (Vim-compat): Empty string undefines the env var.
+ return os_unsetenv(name);
+ }
#else
if (!overwrite && os_env_exists(name)) {
return 0;
}
#endif
uv_mutex_lock(&mutex);
- pmap_del2(envmap, name);
int r = uv_os_setenv(name, value);
assert(r != UV_EINVAL);
+ // Destroy the old map item. Do this AFTER uv_os_setenv(), because `value`
+ // could be a previous os_getenv() result.
+ pmap_del2(envmap, name);
if (r != 0) {
ELOG("uv_os_setenv(%s) failed: %d %s", name, r, uv_err_name(r));
}
@@ -294,6 +310,30 @@ void init_homedir(void)
if (var == NULL) {
var = os_getenv("USERPROFILE");
}
+
+ // Weird but true: $HOME may contain an indirect reference to another
+ // variable, esp. "%USERPROFILE%". Happens when $USERPROFILE isn't set
+ // when $HOME is being set.
+ if (var != NULL && *var == '%') {
+ const char *p = strchr(var + 1, '%');
+ if (p != NULL) {
+ vim_snprintf(os_buf, (size_t)(p - var), "%s", var + 1);
+ const char *exp = os_getenv(os_buf);
+ if (exp != NULL && *exp != NUL
+ && STRLEN(exp) + STRLEN(p) < MAXPATHL) {
+ vim_snprintf(os_buf, MAXPATHL, "%s%s", exp, p + 1);
+ var = os_buf;
+ }
+ }
+ }
+
+ // Default home dir is C:/
+ // Best assumption we can make in such a situation.
+ if (var == NULL
+ // Empty means "undefined"
+ || *var == NUL) {
+ var = "C:/";
+ }
#endif
if (var != NULL) {
@@ -695,17 +735,17 @@ char *vim_getenv(const char *name)
// init_path() should have been called before now.
assert(get_vim_var_str(VV_PROGPATH)[0] != NUL);
- const char *kos_env_path = os_getenv(name);
- if (kos_env_path != NULL) {
- return xstrdup(kos_env_path);
- }
-
#ifdef WIN32
if (strcmp(name, "HOME") == 0) {
return xstrdup(homedir);
}
#endif
+ const char *kos_env_path = os_getenv(name);
+ if (kos_env_path != NULL) {
+ return xstrdup(kos_env_path);
+ }
+
bool vimruntime = (strcmp(name, "VIMRUNTIME") == 0);
if (!vimruntime && strcmp(name, "VIM") != 0) {
return NULL;
diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c
index d5500b230c..dcb3ef7c4a 100644
--- a/src/nvim/os/fs.c
+++ b/src/nvim/os/fs.c
@@ -24,6 +24,7 @@
#include "nvim/message.h"
#include "nvim/assert.h"
#include "nvim/misc1.h"
+#include "nvim/option.h"
#include "nvim/path.h"
#include "nvim/strings.h"
@@ -229,7 +230,7 @@ int os_exepath(char *buffer, size_t *size)
/// Checks if the file `name` is executable.
///
/// @param[in] name Filename to check.
-/// @param[out] abspath Returns resolved executable path, if not NULL.
+/// @param[out,allocated] abspath Returns resolved exe path, if not NULL.
/// @param[in] use_path Also search $PATH.
///
/// @return true if `name` is executable and
@@ -238,27 +239,16 @@ int os_exepath(char *buffer, size_t *size)
/// - is absolute.
///
/// @return `false` otherwise.
-bool os_can_exe(const char_u *name, char_u **abspath, bool use_path)
+bool os_can_exe(const char *name, char **abspath, bool use_path)
FUNC_ATTR_NONNULL_ARG(1)
{
- bool no_path = !use_path || path_is_absolute(name);
- // If the filename is "qualified" (relative or absolute) do not check $PATH.
-#ifdef WIN32
- no_path |= (name[0] == '.'
- && ((name[1] == '/' || name[1] == '\\')
- || (name[1] == '.' && (name[2] == '/' || name[2] == '\\'))));
-#else
- no_path |= (name[0] == '.'
- && (name[1] == '/' || (name[1] == '.' && name[2] == '/')));
-#endif
-
- if (no_path) {
+ if (!use_path || gettail_dir(name) != name) {
#ifdef WIN32
- if (is_executable_ext((char *)name, abspath)) {
+ if (is_executable_ext(name, abspath)) {
#else
// Must have path separator, cannot execute files in the current directory.
- if ((const char_u *)gettail_dir((const char *)name) != name
- && is_executable((char *)name, abspath)) {
+ if ((use_path || gettail_dir(name) != name)
+ && is_executable(name, abspath)) {
#endif
return true;
} else {
@@ -270,10 +260,13 @@ bool os_can_exe(const char_u *name, char_u **abspath, bool use_path)
}
/// Returns true if `name` is an executable file.
-static bool is_executable(const char *name, char_u **abspath)
+///
+/// @param[in] name Filename to check.
+/// @param[out,allocated] abspath Returns full exe path, if not NULL.
+static bool is_executable(const char *name, char **abspath)
FUNC_ATTR_NONNULL_ARG(1)
{
- int32_t mode = os_getperm((const char *)name);
+ int32_t mode = os_getperm(name);
if (mode < 0) {
return false;
@@ -291,7 +284,7 @@ static bool is_executable(const char *name, char_u **abspath)
const bool ok = (r == 0);
#endif
if (ok && abspath != NULL) {
- *abspath = save_abs_path((char_u *)name);
+ *abspath = save_abs_path(name);
}
return ok;
}
@@ -300,7 +293,7 @@ static bool is_executable(const char *name, char_u **abspath)
/// Checks if file `name` is executable under any of these conditions:
/// - extension is in $PATHEXT and `name` is executable
/// - result of any $PATHEXT extension appended to `name` is executable
-static bool is_executable_ext(char *name, char_u **abspath)
+static bool is_executable_ext(char *name, char **abspath)
FUNC_ATTR_NONNULL_ARG(1)
{
const bool is_unix_shell = strstr((char *)path_tail(p_sh), "sh") != NULL;
@@ -312,7 +305,8 @@ static bool is_executable_ext(char *name, char_u **abspath)
if (!pathext) {
pathext = ".com;.exe;.bat;.cmd";
}
- for (const char *ext = pathext; *ext; ext++) {
+ const char *ext = pathext;
+ while (*ext) {
// If $PATHEXT itself contains dot:
if (ext[0] == '.' && (ext[1] == '\0' || ext[1] == ENV_SEPCHAR)) {
if (is_executable(name, abspath)) {
@@ -320,13 +314,17 @@ static bool is_executable_ext(char *name, char_u **abspath)
}
// Skip it.
ext++;
+ if (*ext) {
+ ext++;
+ }
continue;
}
- const char *ext_end = xstrchrnul(ext, ENV_SEPCHAR);
- size_t ext_len = (size_t)(ext_end - ext);
+ const char *ext_end = ext;
+ size_t ext_len =
+ copy_option_part((char_u **)&ext_end, (char_u *)buf_end,
+ sizeof(os_buf) - (size_t)(buf_end - os_buf), ENV_SEPSTR);
if (ext_len != 0) {
- STRLCPY(buf_end, ext, ext_len + 1);
bool in_pathext = nameext_len == ext_len
&& 0 == mb_strnicmp((char_u *)nameext, (char_u *)ext, ext_len);
@@ -347,7 +345,7 @@ static bool is_executable_ext(char *name, char_u **abspath)
/// @param[out] abspath Returns resolved executable path, if not NULL.
///
/// @return `true` if `name` is an executable inside `$PATH`.
-static bool is_executable_in_path(const char_u *name, char_u **abspath)
+static bool is_executable_in_path(const char *name, char **abspath)
FUNC_ATTR_NONNULL_ARG(1)
{
const char *path_env = os_getenv("PATH");
@@ -358,13 +356,13 @@ static bool is_executable_in_path(const char_u *name, char_u **abspath)
#ifdef WIN32
// Prepend ".;" to $PATH.
size_t pathlen = strlen(path_env);
- char *path = memcpy(xmallocz(pathlen + 3), "." ENV_SEPSTR, 2);
+ char *path = memcpy(xmallocz(pathlen + 2), "." ENV_SEPSTR, 2);
memcpy(path + 2, path_env, pathlen);
#else
char *path = xstrdup(path_env);
#endif
- size_t buf_len = STRLEN(name) + strlen(path) + 2;
+ size_t buf_len = strlen(name) + strlen(path) + 2;
char *buf = xmalloc(buf_len);
// Walk through all entries in $PATH to check if "name" exists there and
@@ -376,7 +374,7 @@ static bool is_executable_in_path(const char_u *name, char_u **abspath)
// Combine the $PATH segment with `name`.
STRLCPY(buf, p, e - p + 1);
- append_path(buf, (char *)name, buf_len);
+ append_path(buf, name, buf_len);
#ifdef WIN32
if (is_executable_ext(buf, abspath)) {
@@ -406,10 +404,11 @@ end:
/// calls (read, write, lseek, fcntl, etc.). If the operation fails, a libuv
/// error code is returned, and no file is created or modified.
///
+/// @param path Filename
/// @param flags Bitwise OR of flags defined in <fcntl.h>
/// @param mode Permissions for the newly-created file (IGNORED if 'flags' is
/// not `O_CREAT` or `O_TMPFILE`), subject to the current umask
-/// @return file descriptor, or libuv error code on failure
+/// @return file descriptor, or negative error code on failure
int os_open(const char *path, int flags, int mode)
{
if (path == NULL) { // uv_fs_open asserts on NULL. #7561
@@ -420,6 +419,68 @@ int os_open(const char *path, int flags, int mode)
return r;
}
+/// Compatibility wrapper conforming to fopen(3).
+///
+/// Windows: works with UTF-16 filepaths by delegating to libuv (os_open).
+///
+/// Future: remove this, migrate callers to os/fileio.c ?
+/// But file_open_fd does not support O_RDWR yet.
+///
+/// @param path Filename
+/// @param flags String flags, one of { r w a r+ w+ a+ rb wb ab }
+/// @return FILE pointer, or NULL on error.
+FILE *os_fopen(const char *path, const char *flags)
+{
+ assert(flags != NULL && strlen(flags) > 0 && strlen(flags) <= 2);
+ int iflags = 0;
+ // Per table in fopen(3) manpage.
+ if (flags[1] == '\0' || flags[1] == 'b') {
+ switch (flags[0]) {
+ case 'r':
+ iflags = O_RDONLY;
+ break;
+ case 'w':
+ iflags = O_WRONLY | O_CREAT | O_TRUNC;
+ break;
+ case 'a':
+ iflags = O_WRONLY | O_CREAT | O_APPEND;
+ break;
+ default:
+ abort();
+ }
+#ifdef WIN32
+ if (flags[1] == 'b') {
+ iflags |= O_BINARY;
+ }
+#endif
+ } else {
+ // char 0 must be one of ('r','w','a').
+ // char 1 is always '+' ('b' is handled above).
+ assert(flags[1] == '+');
+ switch (flags[0]) {
+ case 'r':
+ iflags = O_RDWR;
+ break;
+ case 'w':
+ iflags = O_RDWR | O_CREAT | O_TRUNC;
+ break;
+ case 'a':
+ iflags = O_RDWR | O_CREAT | O_APPEND;
+ break;
+ default:
+ abort();
+ }
+ }
+ // Per open(2) manpage.
+ assert((iflags|O_RDONLY) || (iflags|O_WRONLY) || (iflags|O_RDWR));
+ // Per fopen(3) manpage: default to 0666, it will be umask-adjusted.
+ int fd = os_open(path, iflags, 0666);
+ if (fd < 0) {
+ return NULL;
+ }
+ return fdopen(fd, flags);
+}
+
/// Sets file descriptor `fd` to close-on-exec.
//
// @return -1 if failed to set, 0 otherwise.
@@ -746,6 +807,22 @@ bool os_path_exists(const char_u *path)
return os_stat((char *)path, &statbuf) == kLibuvSuccess;
}
+/// Sets file access and modification times.
+///
+/// @see POSIX utime(2)
+///
+/// @param path File path.
+/// @param atime Last access time.
+/// @param mtime Last modification time.
+///
+/// @return 0 on success, or negative error code.
+int os_file_settime(const char *path, double atime, double mtime)
+{
+ int r;
+ RUN_UV_FS_FUNC(r, uv_fs_utime, path, atime, mtime, NULL);
+ return r;
+}
+
/// Check if a file is readable.
///
/// @return true if `name` is readable, otherwise false.
@@ -815,12 +892,12 @@ int os_mkdir_recurse(const char *const dir, int32_t mode,
// We're done when it's "/" or "c:/".
const size_t dirlen = strlen(dir);
char *const curdir = xmemdupz(dir, dirlen);
- char *const past_head = (char *) get_past_head((char_u *) curdir);
+ char *const past_head = (char *)get_past_head((char_u *)curdir);
char *e = curdir + dirlen;
const char *const real_end = e;
const char past_head_save = *past_head;
- while (!os_isdir((char_u *) curdir)) {
- e = (char *) path_tail_with_sep((char_u *) curdir);
+ while (!os_isdir((char_u *)curdir)) {
+ e = (char *)path_tail_with_sep((char_u *)curdir);
if (e <= past_head) {
*past_head = NUL;
break;
@@ -972,7 +1049,7 @@ bool os_fileinfo_fd(int file_descriptor, FileInfo *file_info)
///
/// @return `true` if the two FileInfos represent the same file.
bool os_fileinfo_id_equal(const FileInfo *file_info_1,
- const FileInfo *file_info_2)
+ const FileInfo *file_info_2)
FUNC_ATTR_NONNULL_ALL
{
return file_info_1->stat.st_ino == file_info_2->stat.st_ino
diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c
index 8070f4c420..95e9e8e414 100644
--- a/src/nvim/os/input.c
+++ b/src/nvim/os/input.c
@@ -50,6 +50,11 @@ void input_init(void)
input_buffer = rbuffer_new(INPUT_BUFFER_SIZE + MAX_KEY_CODE_LEN);
}
+void input_global_fd_init(int fd)
+{
+ global_fd = fd;
+}
+
/// Global TTY (or pipe for "-es") input stream, before UI starts.
int input_global_fd(void)
{
@@ -62,7 +67,7 @@ void input_start(int fd)
return;
}
- global_fd = fd;
+ input_global_fd_init(fd);
rstream_init_fd(&main_loop, &read_stream, fd, READ_BUFFER_SIZE);
rstream_start(&read_stream, input_read_cb, NULL);
}
@@ -316,10 +321,10 @@ static unsigned int handle_mouse_event(char **ptr, uint8_t *buf,
// Make sure the mouse position is valid. Some terminals may
// return weird values.
if (col >= Columns) {
- col = (int)Columns - 1;
+ col = Columns - 1;
}
if (row >= Rows) {
- row = (int)Rows - 1;
+ row = Rows - 1;
}
mouse_grid = 0;
mouse_row = row;
diff --git a/src/nvim/os/process.c b/src/nvim/os/process.c
index a1020be215..c7b473a012 100644
--- a/src/nvim/os/process.c
+++ b/src/nvim/os/process.c
@@ -89,21 +89,12 @@ bool os_proc_tree_kill(int pid, int sig)
bool os_proc_tree_kill(int pid, int sig)
{
assert(sig == SIGTERM || sig == SIGKILL);
- int pgid = getpgid(pid);
- if (pgid > 0) { // Ignore error. Never kill self (pid=0).
- if (pgid == pid) {
- ILOG("sending %s to process group: -%d",
- sig == SIGTERM ? "SIGTERM" : "SIGKILL", pgid);
- int rv = uv_kill(-pgid, sig);
- return rv == 0;
- } else {
- // Should never happen, because process_spawn() did setsid() in the child.
- ELOG("pgid %d != pid %d", pgid, pid);
- }
- } else {
- ELOG("getpgid(%d) returned %d", pid, pgid);
+ if (pid == 0) {
+ // Never kill self (pid=0).
+ return false;
}
- return false;
+ ILOG("sending %s to PID %d", sig == SIGTERM ? "SIGTERM" : "SIGKILL", -pid);
+ return uv_kill(-pid, sig) == 0;
}
#endif
diff --git a/src/nvim/os/pty_process_unix.c b/src/nvim/os/pty_process_unix.c
index 5fdf0e6181..f0bc13783c 100644
--- a/src/nvim/os/pty_process_unix.c
+++ b/src/nvim/os/pty_process_unix.c
@@ -288,7 +288,7 @@ static void chld_handler(uv_signal_t *handle, int signum)
if (WIFEXITED(stat)) {
proc->status = WEXITSTATUS(stat);
} else if (WIFSIGNALED(stat)) {
- proc->status = WTERMSIG(stat);
+ proc->status = 128 + WTERMSIG(stat);
}
proc->internal_exit_cb(proc);
}
diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c
index c5f8efadff..290668bca3 100644
--- a/src/nvim/os/pty_process_win.c
+++ b/src/nvim/os/pty_process_win.c
@@ -252,7 +252,7 @@ static void pty_process_finish2(PtyProcess *ptyproc)
DWORD exit_code = 0;
GetExitCodeProcess(ptyproc->process_handle, &exit_code);
- proc->status = (int)exit_code;
+ proc->status = proc->exit_signal ? 128 + proc->exit_signal : (int)exit_code;
CloseHandle(ptyproc->process_handle);
ptyproc->process_handle = NULL;
diff --git a/src/nvim/os/time.c b/src/nvim/os/time.c
index 31ef1a0cd6..4dd0614fe2 100644
--- a/src/nvim/os/time.c
+++ b/src/nvim/os/time.c
@@ -9,6 +9,7 @@
#include <uv.h>
+#include "nvim/assert.h"
#include "nvim/os/time.h"
#include "nvim/os/input.h"
#include "nvim/event/loop.h"
@@ -22,6 +23,7 @@ static uv_cond_t delay_cond;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/time.c.generated.h"
#endif
+
/// Initializes the time module
void time_init(void)
{
@@ -29,15 +31,32 @@ void time_init(void)
uv_cond_init(&delay_cond);
}
-/// Obtain a high-resolution timer value
+/// Gets a high-resolution (nanosecond), monotonically-increasing time relative
+/// to an arbitrary time in the past.
+///
+/// Not related to the time of day and therefore not subject to clock drift.
///
-/// @return a timer value, not related to the time of day and not subject
-/// to clock drift. The value is expressed in nanoseconds.
+/// @return Relative time value with nanosecond precision.
uint64_t os_hrtime(void)
+ FUNC_ATTR_WARN_UNUSED_RESULT
{
return uv_hrtime();
}
+/// Gets a millisecond-resolution, monotonically-increasing time relative to an
+/// arbitrary time in the past.
+///
+/// Not related to the time of day and therefore not subject to clock drift.
+/// The value is cached by the loop, it will not change until the next
+/// loop-tick (unless uv_update_time is called).
+///
+/// @return Relative time value with millisecond precision.
+uint64_t os_now(void)
+ FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ return uv_now(&main_loop.uv);
+}
+
/// Sleeps for `ms` milliseconds.
///
/// @param ms Number of milliseconds to sleep
diff --git a/src/nvim/os_unix.c b/src/nvim/os_unix.c
index 35a7942059..ded575529f 100644
--- a/src/nvim/os_unix.c
+++ b/src/nvim/os_unix.c
@@ -369,10 +369,7 @@ int mch_expand_wildcards(int num_pat, char_u **pat, int *num_file,
// With interactive completion, the error message is not printed.
if (!(flags & EW_SILENT)) {
msg_putchar('\n'); // clear bottom line quickly
-#if SIZEOF_LONG > SIZEOF_INT
- assert(Rows <= (long)INT_MAX + 1);
-#endif
- cmdline_row = (int)(Rows - 1); // continue on last line
+ cmdline_row = Rows - 1; // continue on last line
MSG(_(e_wildexpand));
msg_start(); // don't overwrite this message
}
@@ -538,7 +535,7 @@ int mch_expand_wildcards(int num_pat, char_u **pat, int *num_file,
// Skip files that are not executable if we check for that.
if (!dir && (flags & EW_EXEC)
- && !os_can_exe((*file)[i], NULL, !(flags & EW_SHELLCMD))) {
+ && !os_can_exe((char *)(*file)[i], NULL, !(flags & EW_SHELLCMD))) {
continue;
}
diff --git a/src/nvim/path.c b/src/nvim/path.c
index b43a172991..75a26d88c1 100644
--- a/src/nvim/path.c
+++ b/src/nvim/path.c
@@ -453,13 +453,13 @@ char *FullName_save(const char *fname, bool force)
/// Saves the absolute path.
/// @param name An absolute or relative path.
/// @return The absolute path of `name`.
-char_u *save_abs_path(const char_u *name)
+char *save_abs_path(const char *name)
FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL
{
- if (!path_is_absolute(name)) {
- return (char_u *)FullName_save((char *)name, true);
+ if (!path_is_absolute((char_u *)name)) {
+ return FullName_save(name, true);
}
- return vim_strsave((char_u *) name);
+ return (char *)vim_strsave((char_u *)name);
}
/// Checks if a path has a wildcard character including '~', unless at the end.
@@ -1401,7 +1401,7 @@ void addfile(
// If the file isn't executable, may not add it. Do accept directories.
// When invoked from expand_shellcmd() do not use $PATH.
if (!isdir && (flags & EW_EXEC)
- && !os_can_exe(f, NULL, !(flags & EW_SHELLCMD))) {
+ && !os_can_exe((char *)f, NULL, !(flags & EW_SHELLCMD))) {
return;
}
@@ -2306,7 +2306,7 @@ void path_guess_exepath(const char *argv0, char *buf, size_t bufsize)
xstrlcpy((char *)NameBuff, dir, dir_len + 1);
xstrlcat((char *)NameBuff, PATHSEPSTR, sizeof(NameBuff));
xstrlcat((char *)NameBuff, argv0, sizeof(NameBuff));
- if (os_can_exe(NameBuff, NULL, false)) {
+ if (os_can_exe((char *)NameBuff, NULL, false)) {
xstrlcpy(buf, (char *)NameBuff, bufsize);
return;
}
diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c
index 58a0008e04..ce40bc15e0 100644
--- a/src/nvim/popupmnu.c
+++ b/src/nvim/popupmnu.c
@@ -376,6 +376,7 @@ void pum_redraw(void)
pum_height, grid_width, false, true);
bool invalid_grid = moved || pum_invalid;
pum_invalid = false;
+ must_redraw_pum = false;
if (!pum_grid.chars
|| pum_grid.Rows != pum_height || pum_grid.Columns != grid_width) {
@@ -412,7 +413,7 @@ void pum_redraw(void)
idx = i + pum_first;
attr = (idx == pum_selected) ? attr_select : attr_norm;
- screen_puts_line_start(row);
+ grid_puts_line_start(&pum_grid, row);
// prepend a space if there is room
if (extra_space) {
@@ -564,7 +565,7 @@ void pum_redraw(void)
? attr_thumb : attr_scroll);
}
}
- grid_puts_line_flush(&pum_grid, false);
+ grid_puts_line_flush(false);
row++;
}
}
@@ -790,6 +791,7 @@ void pum_undisplay(bool immediate)
{
pum_is_visible = false;
pum_array = NULL;
+ must_redraw_pum = false;
if (immediate) {
pum_check_clear();
@@ -853,7 +855,8 @@ int pum_get_height(void)
return pum_height;
}
-void pum_set_boundings(dict_T *dict)
+/// Add size information about the pum to "dict".
+void pum_set_event_info(dict_T *dict)
{
if (!pum_visible()) {
return;
diff --git a/src/nvim/profile.c b/src/nvim/profile.c
index 8fb8e92add..0a5030edae 100644
--- a/src/nvim/profile.c
+++ b/src/nvim/profile.c
@@ -5,6 +5,7 @@
#include <math.h>
#include <assert.h>
+#include "nvim/assert.h"
#include "nvim/profile.h"
#include "nvim/os/time.h"
#include "nvim/func_attr.h"
@@ -16,11 +17,9 @@
# include "profile.c.generated.h"
#endif
-/// functions for profiling
-
static proftime_T prof_wait_time;
-/// profile_start - return the current time
+/// Gets the current time.
///
/// @return the current time
proftime_T profile_start(void) FUNC_ATTR_WARN_UNUSED_RESULT
@@ -28,31 +27,29 @@ proftime_T profile_start(void) FUNC_ATTR_WARN_UNUSED_RESULT
return os_hrtime();
}
-/// profile_end - compute the time elapsed
+/// Computes the time elapsed.
///
-/// @return the elapsed time from `tm` until now.
+/// @return Elapsed time from `tm` until now.
proftime_T profile_end(proftime_T tm) FUNC_ATTR_WARN_UNUSED_RESULT
{
- return os_hrtime() - tm;
+ return profile_sub(os_hrtime(), tm);
}
-/// profile_msg - return a string that represents the time in `tm`
+/// Gets a string representing time `tm`.
///
/// @warning Do not modify or free this string, not multithread-safe.
///
-/// @param tm The time to be represented
-/// @return a static string representing `tm` in the
-/// form "seconds.microseconds".
+/// @param tm Time
+/// @return Static string representing `tm` in the form "seconds.microseconds".
const char *profile_msg(proftime_T tm) FUNC_ATTR_WARN_UNUSED_RESULT
{
static char buf[50];
-
- snprintf(buf, sizeof(buf), "%10.6lf", (double)tm / 1000000000.0);
-
+ snprintf(buf, sizeof(buf), "%10.6lf",
+ (double)profile_signed(tm) / 1000000000.0);
return buf;
}
-/// profile_setlimit - return the time `msec` into the future
+/// Gets the time `msec` into the future.
///
/// @param msec milliseconds, the maximum number of milliseconds is
/// (2^63 / 10^6) - 1 = 9.223372e+12.
@@ -64,14 +61,12 @@ proftime_T profile_setlimit(int64_t msec) FUNC_ATTR_WARN_UNUSED_RESULT
// no limit
return profile_zero();
}
-
assert(msec <= (INT64_MAX / 1000000LL) - 1);
-
- proftime_T nsec = (proftime_T) msec * 1000000ULL;
+ proftime_T nsec = (proftime_T)msec * 1000000ULL;
return os_hrtime() + nsec;
}
-/// profile_passed_limit - check if current time has passed `tm`
+/// Checks if current time has passed `tm`.
///
/// @return true if the current time is past `tm`, false if not or if the
/// timer was not set.
@@ -85,7 +80,7 @@ bool profile_passed_limit(proftime_T tm) FUNC_ATTR_WARN_UNUSED_RESULT
return profile_cmp(os_hrtime(), tm) < 0;
}
-/// profile_zero - obtain the zero time
+/// Gets the zero time.
///
/// @return the zero time
proftime_T profile_zero(void) FUNC_ATTR_CONST
@@ -93,7 +88,7 @@ proftime_T profile_zero(void) FUNC_ATTR_CONST
return 0;
}
-/// profile_divide - divide the time `tm` by `count`.
+/// Divides time `tm` by `count`.
///
/// @return 0 if count <= 0, otherwise tm / count
proftime_T profile_divide(proftime_T tm, int count) FUNC_ATTR_CONST
@@ -105,7 +100,7 @@ proftime_T profile_divide(proftime_T tm, int count) FUNC_ATTR_CONST
return (proftime_T) round((double) tm / (double) count);
}
-/// profile_add - add the time `tm2` to `tm1`
+/// Adds time `tm2` to `tm1`.
///
/// @return `tm1` + `tm2`
proftime_T profile_add(proftime_T tm1, proftime_T tm2) FUNC_ATTR_CONST
@@ -113,7 +108,12 @@ proftime_T profile_add(proftime_T tm1, proftime_T tm2) FUNC_ATTR_CONST
return tm1 + tm2;
}
-/// profile_sub - subtract `tm2` from `tm1`
+/// Subtracts time `tm2` from `tm1`.
+///
+/// Unsigned overflow (wraparound) occurs if `tm2` is greater than `tm1`.
+/// Use `profile_signed()` to get the signed integer value.
+///
+/// @see profile_signed
///
/// @return `tm1` - `tm2`
proftime_T profile_sub(proftime_T tm1, proftime_T tm2) FUNC_ATTR_CONST
@@ -121,8 +121,7 @@ proftime_T profile_sub(proftime_T tm1, proftime_T tm2) FUNC_ATTR_CONST
return tm1 - tm2;
}
-/// profile_self - add the `self` time from the total time and the
-/// children's time
+/// Adds the `self` time from the total time and the `children` time.
///
/// @return if `total` <= `children`, then self, otherwise `self` + `total` -
/// `children`
@@ -139,7 +138,7 @@ proftime_T profile_self(proftime_T self, proftime_T total, proftime_T children)
return profile_sub(profile_add(self, total), children);
}
-/// profile_get_wait - get the current waittime
+/// Gets the current waittime.
///
/// @return the current waittime
proftime_T profile_get_wait(void) FUNC_ATTR_PURE
@@ -147,13 +146,13 @@ proftime_T profile_get_wait(void) FUNC_ATTR_PURE
return prof_wait_time;
}
-/// profile_set_wait - set the current waittime
+/// Sets the current waittime.
void profile_set_wait(proftime_T wait)
{
prof_wait_time = wait;
}
-/// profile_sub_wait - subtract the passed waittime since `tm`
+/// Subtracts the passed waittime since `tm`.
///
/// @return `tma` - (waittime - `tm`)
proftime_T profile_sub_wait(proftime_T tm, proftime_T tma) FUNC_ATTR_PURE
@@ -162,7 +161,7 @@ proftime_T profile_sub_wait(proftime_T tm, proftime_T tma) FUNC_ATTR_PURE
return profile_sub(tma, tm3);
}
-/// profile_equal - check if `tm1` is equal to `tm2`
+/// Checks if time `tm1` is equal to `tm2`.
///
/// @return true if `tm1` == `tm2`
bool profile_equal(proftime_T tm1, proftime_T tm2) FUNC_ATTR_CONST
@@ -170,30 +169,38 @@ bool profile_equal(proftime_T tm1, proftime_T tm2) FUNC_ATTR_CONST
return tm1 == tm2;
}
-/// sgn64 - calculates the sign of a 64-bit integer
+/// Converts time duration `tm` (`profile_sub` result) to a signed integer.
///
-/// @return -1, 0, or +1
-static inline int sgn64(int64_t x) FUNC_ATTR_CONST
+/// @return signed representation of the given time value
+int64_t profile_signed(proftime_T tm)
+ FUNC_ATTR_CONST
{
- return (int) ((x > 0) - (x < 0));
+ // (tm > INT64_MAX) is >=150 years, so we can assume it was produced by
+ // arithmetic of two proftime_T values. For human-readable representation
+ // (and Vim-compat) we want the difference after unsigned wraparound. #10452
+ return (tm <= INT64_MAX) ? (int64_t)tm : -(int64_t)(UINT64_MAX - tm);
}
-/// profile_cmp - compare profiling times
+/// Compares profiling times.
///
-/// Only guarantees correct results if both input times are not more than
-/// 150 years apart.
+/// Times `tm1` and `tm2` must be less than 150 years apart.
///
-/// @return <0, 0 or >0 if `tm2` < `tm1`, `tm2` == `tm1` or `tm2` > `tm1`
+/// @return <0: `tm2` < `tm1`
+/// 0: `tm2` == `tm1`
+/// >0: `tm2` > `tm1`
int profile_cmp(proftime_T tm1, proftime_T tm2) FUNC_ATTR_CONST
{
- return sgn64((int64_t)(tm2 - tm1));
+ if (tm1 == tm2) {
+ return 0;
+ }
+ return profile_signed(tm2 - tm1) < 0 ? -1 : 1;
}
/// globals for use in the startuptime related functionality (time_*).
static proftime_T g_start_time;
static proftime_T g_prev_time;
-/// time_push - save the previous time before doing something that could nest
+/// Saves the previous time before doing something that could nest.
///
/// After calling this function, the static global `g_prev_time` will
/// contain the current time.
@@ -212,7 +219,7 @@ void time_push(proftime_T *rel, proftime_T *start)
g_prev_time = now;
}
-/// time_pop - compute the prev time after doing something that could nest
+/// Computes the prev time after doing something that could nest.
///
/// Subtracts `tp` from the static global `g_prev_time`.
///
@@ -222,18 +229,18 @@ void time_pop(proftime_T tp)
g_prev_time -= tp;
}
-/// time_diff - print the difference between `then` and `now`
+/// Prints the difference between `then` and `now`.
///
/// the format is "msec.usec".
static void time_diff(proftime_T then, proftime_T now)
{
proftime_T diff = profile_sub(now, then);
- fprintf(time_fd, "%07.3lf", (double) diff / 1.0E6);
+ fprintf(time_fd, "%07.3lf", (double)diff / 1.0E6);
}
-/// time_start - initialize the startuptime code
+/// Initializes the startuptime code.
///
-/// Needs to be called once before calling other startuptime code (such as
+/// Must be called once before calling other startuptime code (such as
/// time_{push,pop,msg,...}).
///
/// @param message the message that will be displayed
@@ -253,7 +260,7 @@ void time_start(const char *message)
time_msg(message, NULL);
}
-/// time_msg - print out timing info
+/// Prints out timing info.
///
/// @warning don't forget to call `time_start()` once before calling this.
///
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index ced0cf0f80..1eb616bca7 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -863,7 +863,7 @@ qf_init_ext(
fields.errmsg = xmalloc(fields.errmsglen);
fields.pattern = xmalloc(CMDBUFFSIZE + 1);
- if (efile != NULL && (state.fd = mch_fopen((char *)efile, "r")) == NULL) {
+ if (efile != NULL && (state.fd = os_fopen((char *)efile, "r")) == NULL) {
EMSG2(_(e_openerrf), efile);
goto qf_init_end;
}
@@ -1326,7 +1326,7 @@ static int qf_parse_multiline_pfx(qf_info_T *qi, int qf_idx, int idx,
if (qfprev == NULL) {
return QF_FAIL;
}
- if (*fields->errmsg && !qfl->qf_multiignore) {
+ if (*fields->errmsg) {
size_t textlen = strlen((char *)qfprev->qf_text);
size_t errlen = strlen((char *)fields->errmsg);
qfprev->qf_text = xrealloc(qfprev->qf_text, textlen + errlen + 2);
@@ -2056,7 +2056,7 @@ static int qf_jump_to_usable_window(int qf_fnum, int *opened_window)
win_T *usable_win_ptr = NULL;
int usable_win;
int flags;
- win_T *win = NULL;
+ win_T *win;
win_T *altwin;
usable_win = 0;
@@ -2079,7 +2079,6 @@ static int qf_jump_to_usable_window(int qf_fnum, int *opened_window)
// Locate a window showing a normal buffer
FOR_ALL_WINDOWS_IN_TAB(win2, curtab) {
if (win2->w_buffer->b_p_bt[0] == NUL) {
- win = win2;
usable_win = 1;
break;
}
@@ -2204,7 +2203,7 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit,
// Open help file (do_ecmd() will set b_help flag, readfile() will
// set b_p_ro flag).
if (!can_abandon(curbuf, forceit)) {
- EMSG(_(e_nowrtmsg));
+ no_write_message();
retval = false;
} else {
retval = do_ecmd(qf_ptr->qf_fnum, NULL, NULL, NULL, (linenr_T)1,
@@ -2212,7 +2211,6 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit,
oldwin == curwin ? curwin : NULL);
}
} else {
- int old_qf_curlist = qi->qf_curlist;
unsigned save_qfid = qi->qf_lists[qi->qf_curlist].qf_id;
retval = buflist_getfile(qf_ptr->qf_fnum, (linenr_T)1,
@@ -2229,8 +2227,7 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit,
EMSG(_(e_loc_list_changed));
*abort = true;
}
- } else if (old_qf_curlist != qi->qf_curlist
- || !is_qf_entry_present(qi, qf_ptr)) {
+ } else if (!is_qf_entry_present(qi, qf_ptr)) {
if (IS_QF_STACK(qi)) {
EMSG(_("E925: Current quickfix was changed"));
} else {
@@ -2528,9 +2525,9 @@ void qf_list(exarg_T *eap)
qfp = qi->qf_lists[qi->qf_curlist].qf_start;
for (i = 1; !got_int && i <= qi->qf_lists[qi->qf_curlist].qf_count; ) {
if ((qfp->qf_valid || all) && idx1 <= i && i <= idx2) {
- msg_putchar('\n');
- if (got_int)
+ if (got_int) {
break;
+ }
fname = NULL;
if (qfp->qf_module != NULL && *qfp->qf_module != NUL) {
@@ -2549,6 +2546,27 @@ void qf_list(exarg_T *eap)
vim_snprintf((char *)IObuff, IOSIZE, "%2d %s", i, (char *)fname);
}
}
+
+ // Support for filtering entries using :filter /pat/ clist
+ // Match against the module name, file name, search pattern and
+ // text of the entry.
+ bool filter_entry = true;
+ if (qfp->qf_module != NULL && *qfp->qf_module != NUL) {
+ filter_entry &= message_filtered(qfp->qf_module);
+ }
+ if (filter_entry && fname != NULL) {
+ filter_entry &= message_filtered(fname);
+ }
+ if (filter_entry && qfp->qf_pattern != NULL) {
+ filter_entry &= message_filtered(qfp->qf_pattern);
+ }
+ if (filter_entry) {
+ filter_entry &= message_filtered(qfp->qf_text);
+ }
+ if (filter_entry) {
+ goto next_entry;
+ }
+ msg_putchar('\n');
msg_outtrans_attr(IObuff, i == qi->qf_lists[qi->qf_curlist].qf_index
? HL_ATTR(HLF_QFL) : HL_ATTR(HLF_D));
if (qfp->qf_lnum == 0) {
@@ -2579,6 +2597,7 @@ void qf_list(exarg_T *eap)
ui_flush(); /* show one line at a time */
}
+next_entry:
qfp = qfp->qf_next;
if (qfp == NULL) {
break;
@@ -2632,7 +2651,7 @@ static void qf_msg(qf_info_T *qi, int which, char *lead)
}
xstrlcat((char *)buf, title, IOSIZE);
}
- trunc_string(buf, buf, (int)Columns - 1, IOSIZE);
+ trunc_string(buf, buf, Columns - 1, IOSIZE);
msg(buf);
}
@@ -2845,6 +2864,39 @@ static char_u *qf_types(int c, int nr)
return buf;
}
+// When "split" is false: Open the entry/result under the cursor.
+// When "split" is true: Open the entry/result under the cursor in a new window.
+void qf_view_result(bool split)
+{
+ qf_info_T *qi = &ql_info;
+
+ if (!bt_quickfix(curbuf)) {
+ return;
+ }
+ if (IS_LL_WINDOW(curwin)) {
+ qi = GET_LOC_LIST(curwin);
+ }
+ if (qi == NULL
+ || qi->qf_lists[qi->qf_curlist].qf_count == 0) {
+ EMSG(_(e_quickfix));
+ return;
+ }
+
+ if (split) {
+ char cmd[32];
+
+ snprintf(cmd, sizeof(cmd), "split +%" PRId64 "%s",
+ (int64_t)curwin->w_cursor.lnum,
+ IS_LL_WINDOW(curwin) ? "ll" : "cc");
+ if (do_cmdline_cmd(cmd) == OK) {
+ do_cmdline_cmd("clearjumps");
+ }
+ return;
+ }
+
+ do_cmdline_cmd((IS_LL_WINDOW(curwin) ? ".ll" : ".cc"));
+}
+
/*
* ":cwindow": open the quickfix window if we have errors to display,
* close it if not.
@@ -4688,11 +4740,8 @@ static int qf_getprop_defaults(qf_info_T *qi, int flags, dict_T *retdict)
/// Return the quickfix list title as 'title' in retdict
static int qf_getprop_title(qf_info_T *qi, int qf_idx, dict_T *retdict)
{
- char_u *t = qi->qf_lists[qf_idx].qf_title;
- if (t == NULL) {
- t = (char_u *)"";
- }
- return tv_dict_add_str(retdict, S_LEN("title"), (const char *)t);
+ return tv_dict_add_str(retdict, S_LEN("title"),
+ (const char *)qi->qf_lists[qf_idx].qf_title);
}
/// Return the quickfix list items/entries as 'items' in retdict
@@ -5335,8 +5384,11 @@ void ex_cexpr(exarg_T *eap)
apply_autocmds(EVENT_QUICKFIXCMDPOST, (char_u *)au_name,
curbuf->b_fname, true, curbuf);
}
- if (res > 0 && (eap->cmdidx == CMD_cexpr || eap->cmdidx == CMD_lexpr)) {
- qf_jump(qi, 0, 0, eap->forceit); // display first error
+ if (res > 0
+ && (eap->cmdidx == CMD_cexpr || eap->cmdidx == CMD_lexpr)
+ && qi == GET_LOC_LIST(curwin)) {
+ // Jump to the first error if autocmds didn't free the list.
+ qf_jump(qi, 0, 0, eap->forceit);
}
} else {
EMSG(_("E777: String or List expected"));
@@ -5440,7 +5492,7 @@ void ex_helpgrep(exarg_T *eap)
+ STRLEN(fnames[fi]) - 3, 3) == 0)) {
continue;
}
- fd = mch_fopen((char *)fnames[fi], "r");
+ fd = os_fopen((char *)fnames[fi], "r");
if (fd != NULL) {
lnum = 1;
while (!vim_fgets(IObuff, IOSIZE, fd) && !got_int) {
diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c
index 8598da6376..06b99d0b75 100644
--- a/src/nvim/regexp.c
+++ b/src/nvim/regexp.c
@@ -247,9 +247,9 @@
#define BRACE_COMPLEX 140 /* -149 node Match nodes between m & n times */
-#define NOPEN 150 /* Mark this point in input as start of
- \%( subexpr. */
-#define NCLOSE 151 /* Analogous to NOPEN. */
+#define NOPEN 150 // Mark this point in input as start of
+ // \%( subexpr.
+#define NCLOSE 151 // Analogous to NOPEN.
#define MULTIBYTECODE 200 /* mbc Match one multi-byte character */
#define RE_BOF 201 /* Match "" at beginning of file. */
@@ -348,13 +348,13 @@ typedef enum regstate_E {
* more things.
*/
typedef struct regitem_S {
- regstate_T rs_state; /* what we are doing, one of RS_ above */
- char_u *rs_scan; /* current node in program */
+ regstate_T rs_state; // what we are doing, one of RS_ above
+ uint16_t rs_no; // submatch nr or BEHIND/NOBEHIND
+ char_u *rs_scan; // current node in program
union {
save_se_T sesave;
regsave_T regsave;
- } rs_un; /* room for saving reginput */
- short rs_no; /* submatch nr or BEHIND/NOBEHIND */
+ } rs_un; // room for saving reginput
} regitem_T;
@@ -2058,10 +2058,14 @@ static char_u *regatom(int *flagp)
EMSG2_RET_NULL(_(e_missing_sb),
reg_magic == MAGIC_ALL);
br = regnode(BRANCH);
- if (ret == NULL)
+ if (ret == NULL) {
ret = br;
- else
+ } else {
regtail(lastnode, br);
+ if (reg_toolong) {
+ return NULL;
+ }
+ }
ungetchr();
one_exactly = TRUE;
@@ -2083,6 +2087,9 @@ static char_u *regatom(int *flagp)
for (br = ret; br != lastnode; ) {
if (OP(br) == BRANCH) {
regtail(br, lastbranch);
+ if (reg_toolong) {
+ return NULL;
+ }
br = OPERAND(br);
} else
br = regnext(br);
@@ -4316,8 +4323,10 @@ static int regmatch(
/* Still at same position as last time, fail. */
status = RA_NOMATCH;
- if (status != RA_FAIL && status != RA_NOMATCH)
+ assert(status != RA_FAIL);
+ if (status != RA_NOMATCH) {
reg_save(&bp[i].bp_pos, &backpos);
+ }
}
break;
diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c
index dc1ab971ab..c0129a00fb 100644
--- a/src/nvim/regexp_nfa.c
+++ b/src/nvim/regexp_nfa.c
@@ -1263,8 +1263,8 @@ static int nfa_regatom(void)
IEMSGN("INTERNAL: Unknown character class char: %" PRId64, c);
return FAIL;
}
- /* When '.' is followed by a composing char ignore the dot, so that
- * the composing char is matched here. */
+ // When '.' is followed by a composing char ignore the dot, so that
+ // the composing char is matched here.
if (enc_utf8 && c == Magic('.') && utf_iscomposing(peekchr())) {
old_regparse = regparse;
c = getchr();
@@ -1279,25 +1279,26 @@ static int nfa_regatom(void)
break;
case Magic('n'):
- if (reg_string)
- /* In a string "\n" matches a newline character. */
+ if (reg_string) {
+ // In a string "\n" matches a newline character.
EMIT(NL);
- else {
- /* In buffer text "\n" matches the end of a line. */
+ } else {
+ // In buffer text "\n" matches the end of a line.
EMIT(NFA_NEWL);
regflags |= RF_HASNL;
}
break;
case Magic('('):
- if (nfa_reg(REG_PAREN) == FAIL)
- return FAIL; /* cascaded error */
+ if (nfa_reg(REG_PAREN) == FAIL) {
+ return FAIL; // cascaded error
+ }
break;
case Magic('|'):
case Magic('&'):
case Magic(')'):
- EMSGN(_(e_misplaced), no_Magic(c));
+ EMSGN(_(e_misplaced), no_Magic(c)); // -V1037
return FAIL;
case Magic('='):
@@ -1306,7 +1307,7 @@ static int nfa_regatom(void)
case Magic('@'):
case Magic('*'):
case Magic('{'):
- /* these should follow an atom, not form an atom */
+ // these should follow an atom, not form an atom
EMSGN(_(e_misplaced), no_Magic(c));
return FAIL;
@@ -1314,8 +1315,8 @@ static int nfa_regatom(void)
{
char_u *lp;
- /* Previous substitute pattern.
- * Generated as "\%(pattern\)". */
+ // Previous substitute pattern.
+ // Generated as "\%(pattern\)".
if (reg_prev_sub == NULL) {
EMSG(_(e_nopresub));
return FAIL;
@@ -1492,16 +1493,22 @@ static int nfa_regatom(void)
default:
{
- long n = 0;
- int cmp = c;
+ int64_t n = 0;
+ const int cmp = c;
if (c == '<' || c == '>')
c = getchr();
while (ascii_isdigit(c)) {
+ if (n > (INT32_MAX - (c - '0')) / 10) {
+ EMSG(_("E951: \\% value too large"));
+ return FAIL;
+ }
n = n * 10 + (c - '0');
c = getchr();
}
if (c == 'l' || c == 'c' || c == 'v') {
+ int32_t limit = INT32_MAX;
+
if (c == 'l') {
// \%{n}l \%{n}<l \%{n}>l
EMIT(cmp == '<' ? NFA_LNUM_LT :
@@ -1517,13 +1524,12 @@ static int nfa_regatom(void)
// \%{n}v \%{n}<v \%{n}>v
EMIT(cmp == '<' ? NFA_VCOL_LT :
cmp == '>' ? NFA_VCOL_GT : NFA_VCOL);
+ limit = INT32_MAX / MB_MAXBYTES;
}
-#if SIZEOF_INT < SIZEOF_LONG
- if (n > INT_MAX) {
+ if (n >= limit) {
EMSG(_("E951: \\% value too large"));
return FAIL;
}
-#endif
EMIT((int)n);
break;
} else if (c == '\'' && n == 0) {
@@ -3002,8 +3008,8 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size)
return NULL; \
}
- if (nfa_calc_size == FALSE) {
- /* Allocate space for the stack. Max states on the stack : nstate */
+ if (nfa_calc_size == false) {
+ // Allocate space for the stack. Max states on the stack: "nstate".
stack = xmalloc((nstate + 1) * sizeof(Frag_T));
stackp = stack;
stack_end = stack + (nstate + 1);
@@ -3925,13 +3931,14 @@ state_in_list (
// Add "state" and possibly what follows to state list ".".
// Returns "subs_arg", possibly copied into temp_subs.
-static regsubs_T *
-addstate (
- nfa_list_T *l, /* runtime state list */
- nfa_state_T *state, /* state to update */
- regsubs_T *subs_arg, /* pointers to subexpressions */
- nfa_pim_T *pim, /* postponed look-behind match */
- int off_arg) /* byte offset, when -1 go to next line */
+// Returns NULL when recursiveness is too deep.
+static regsubs_T *addstate(
+ nfa_list_T *l, // runtime state list
+ nfa_state_T *state, // state to update
+ regsubs_T *subs_arg, // pointers to subexpressions
+ nfa_pim_T *pim, // postponed look-behind match
+ int off_arg) // byte offset, when -1 go to next line
+ FUNC_ATTR_NONNULL_ARG(1, 2) FUNC_ATTR_WARN_UNUSED_RESULT
{
int subidx;
int off = off_arg;
@@ -3950,6 +3957,14 @@ addstate (
#ifdef REGEXP_DEBUG
int did_print = FALSE;
#endif
+ static int depth = 0;
+
+ // This function is called recursively. When the depth is too much we run
+ // out of stack and crash, limit recursiveness here.
+ if (++depth >= 5000 || subs == NULL) {
+ depth--;
+ return NULL;
+ }
if (off_arg <= -ADDSTATE_HERE_OFFSET) {
add_here = true;
@@ -4053,6 +4068,7 @@ skip_add:
abs(state->id), l->id, state->c, code,
pim == NULL ? "NULL" : "yes", l->has_pim, found);
#endif
+ depth--;
return subs;
}
}
@@ -4063,11 +4079,17 @@ skip_add:
goto skip_add;
}
- /* When there are backreferences or PIMs the number of states may
- * be (a lot) bigger than anticipated. */
+ // When there are backreferences or PIMs the number of states may
+ // be (a lot) bigger than anticipated.
if (l->n == l->len) {
- int newlen = l->len * 3 / 2 + 50;
+ const int newlen = l->len * 3 / 2 + 50;
+ const size_t newsize = newlen * sizeof(nfa_thread_T);
+ if ((long)(newsize >> 10) >= p_mmp) {
+ EMSG(_(e_maxmempat));
+ depth--;
+ return NULL;
+ }
if (subs != &temp_subs) {
/* "subs" may point into the current array, need to make a
* copy before it becomes invalid. */
@@ -4077,7 +4099,8 @@ skip_add:
subs = &temp_subs;
}
- l->t = xrealloc(l->t, newlen * sizeof(nfa_thread_T));
+ nfa_thread_T *const newt = xrealloc(l->t, newsize);
+ l->t = newt;
l->len = newlen;
}
@@ -4196,6 +4219,9 @@ skip_add:
}
subs = addstate(l, state->out, subs, pim, off_arg);
+ if (subs == NULL) {
+ break;
+ }
// "subs" may have changed, need to set "sub" again.
if (state->c >= NFA_ZOPEN && state->c <= NFA_ZOPEN9) { // -V560
sub = &subs->synt;
@@ -4217,7 +4243,7 @@ skip_add:
if (nfa_has_zend && (REG_MULTI
? subs->norm.list.multi[0].end_lnum >= 0
: subs->norm.list.line[0].end != NULL)) {
- /* Do not overwrite the position set by \ze. */
+ // Do not overwrite the position set by \ze.
subs = addstate(l, state->out, subs, pim, off_arg);
break;
}
@@ -4278,6 +4304,9 @@ skip_add:
}
subs = addstate(l, state->out, subs, pim, off_arg);
+ if (subs == NULL) {
+ break;
+ }
// "subs" may have changed, need to set "sub" again.
if (state->c >= NFA_ZCLOSE && state->c <= NFA_ZCLOSE9) { // -V560
sub = &subs->synt;
@@ -4293,6 +4322,7 @@ skip_add:
sub->in_use = save_in_use;
break;
}
+ depth--;
return subs;
}
@@ -4302,14 +4332,14 @@ skip_add:
* This makes sure the order of states to be tried does not change, which
* matters for alternatives.
*/
-static void
-addstate_here (
- nfa_list_T *l, /* runtime state list */
- nfa_state_T *state, /* state to update */
- regsubs_T *subs, /* pointers to subexpressions */
- nfa_pim_T *pim, /* postponed look-behind match */
+static regsubs_T *addstate_here(
+ nfa_list_T *l, // runtime state list
+ nfa_state_T *state, // state to update
+ regsubs_T *subs, // pointers to subexpressions
+ nfa_pim_T *pim, // postponed look-behind match
int *ip
)
+ FUNC_ATTR_NONNULL_ARG(1, 2, 5) FUNC_ATTR_WARN_UNUSED_RESULT
{
int tlen = l->n;
int count;
@@ -4318,26 +4348,37 @@ addstate_here (
/* First add the state(s) at the end, so that we know how many there are.
* Pass the listidx as offset (avoids adding another argument to
* addstate(). */
- addstate(l, state, subs, pim, -listidx - ADDSTATE_HERE_OFFSET);
+ regsubs_T *r = addstate(l, state, subs, pim, -listidx - ADDSTATE_HERE_OFFSET);
+ if (r == NULL) {
+ return NULL;
+ }
- /* when "*ip" was at the end of the list, nothing to do */
- if (listidx + 1 == tlen)
- return;
+ // when "*ip" was at the end of the list, nothing to do
+ if (listidx + 1 == tlen) {
+ return r;
+ }
- /* re-order to put the new state at the current position */
+ // re-order to put the new state at the current position
count = l->n - tlen;
- if (count == 0)
- return; /* no state got added */
+ if (count == 0) {
+ return r; // no state got added
+ }
if (count == 1) {
- /* overwrite the current state */
+ // overwrite the current state
l->t[listidx] = l->t[l->n - 1];
} else if (count > 1) {
if (l->n + count - 1 >= l->len) {
/* not enough space to move the new states, reallocate the list
* and move the states to the right position */
+ const int newlen = l->len * 3 / 2 + 50;
+ const size_t newsize = newlen * sizeof(nfa_thread_T);
- l->len = l->len * 3 / 2 + 50;
- nfa_thread_T *newl = xmalloc(l->len * sizeof(nfa_thread_T));
+ if ((long)(newsize >> 10) >= p_mmp) {
+ EMSG(_(e_maxmempat));
+ return NULL;
+ }
+ nfa_thread_T *const newl = xmalloc(newsize);
+ l->len = newlen;
memmove(&(newl[0]),
&(l->t[0]),
sizeof(nfa_thread_T) * listidx);
@@ -4362,6 +4403,8 @@ addstate_here (
}
--l->n;
*ip = listidx - 1;
+
+ return r;
}
/*
@@ -4991,6 +5034,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
int add_count;
int add_off = 0;
int toplevel = start->c == NFA_MOPEN;
+ regsubs_T *r;
#ifdef NFA_REGEXP_DEBUG_LOG
FILE *debug = fopen(NFA_REGEXP_DEBUG_LOG, "a");
@@ -5058,9 +5102,14 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
} else
m->norm.list.line[0].start = reginput;
m->norm.in_use = 1;
- addstate(thislist, start->out, m, NULL, 0);
- } else
- addstate(thislist, start, m, NULL, 0);
+ r = addstate(thislist, start->out, m, NULL, 0);
+ } else {
+ r = addstate(thislist, start, m, NULL, 0);
+ }
+ if (r == NULL) {
+ nfa_match = NFA_TOO_EXPENSIVE;
+ goto theend;
+ }
#define ADD_STATE_IF_MATCH(state) \
if (result) { \
@@ -5327,8 +5376,11 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
// t->state->out1 is the corresponding END_INVISIBLE
// node; Add its out to the current list (zero-width
// match).
- addstate_here(thislist, t->state->out1->out, &t->subs,
- &pim, &listidx);
+ if (addstate_here(thislist, t->state->out1->out, &t->subs,
+ &pim, &listidx) == NULL) {
+ nfa_match = NFA_TOO_EXPENSIVE;
+ goto theend;
+ }
}
}
break;
@@ -6144,12 +6196,17 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
pim = &pim_copy;
}
- if (add_here)
- addstate_here(thislist, add_state, &t->subs, pim, &listidx);
- else {
- addstate(nextlist, add_state, &t->subs, pim, add_off);
- if (add_count > 0)
+ if (add_here) {
+ r = addstate_here(thislist, add_state, &t->subs, pim, &listidx);
+ } else {
+ r = addstate(nextlist, add_state, &t->subs, pim, add_off);
+ if (add_count > 0) {
nextlist->t[nextlist->n - 1].count = add_count;
+ }
+ }
+ if (r == NULL) {
+ nfa_match = NFA_TOO_EXPENSIVE;
+ goto theend;
}
}
} // for (thislist = thislist; thislist->state; thislist++)
@@ -6219,10 +6276,17 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
(colnr_T)(reginput - regline) + clen;
else
m->norm.list.line[0].start = reginput + clen;
- addstate(nextlist, start->out, m, NULL, clen);
+ if (addstate(nextlist, start->out, m, NULL, clen) == NULL) {
+ nfa_match = NFA_TOO_EXPENSIVE;
+ goto theend;
+ }
}
- } else
- addstate(nextlist, start, m, NULL, clen);
+ } else {
+ if (addstate(nextlist, start, m, NULL, clen) == NULL) {
+ nfa_match = NFA_TOO_EXPENSIVE;
+ goto theend;
+ }
+ }
}
#ifdef REGEXP_DEBUG
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index 84c3f169ef..99ccce1793 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -153,6 +153,8 @@ static bool conceal_cursor_used = false;
static bool redraw_popupmenu = false;
+static bool resizing = false;
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "screen.c.generated.h"
#endif
@@ -169,6 +171,7 @@ void redraw_later(int type)
}
void redraw_win_later(win_T *wp, int type)
+ FUNC_ATTR_NONNULL_ALL
{
if (!exiting && wp->w_redr_type < type) {
wp->w_redr_type = type;
@@ -241,6 +244,7 @@ redrawWinline(
win_T *wp,
linenr_T lnum
)
+ FUNC_ATTR_NONNULL_ALL
{
if (lnum >= wp->w_topline
&& lnum < wp->w_botline) {
@@ -269,14 +273,16 @@ void update_curbuf(int type)
/// and redraw_all_later() to mark parts of the screen as needing a redraw.
///
/// @param type set to a NOT_VALID to force redraw of entire screen
-void update_screen(int type)
+int update_screen(int type)
{
static int did_intro = FALSE;
int did_one;
// Don't do anything if the screen structures are (not yet) valid.
- if (!default_grid.chars) {
- return;
+ // A VimResized autocmd can invoke redrawing in the middle of a resize,
+ // which would bypass the checks in screen_resize for popupmenu etc.
+ if (!default_grid.chars || resizing) {
+ return FAIL;
}
if (must_redraw) {
@@ -299,9 +305,10 @@ void update_screen(int type)
if (!redrawing() || updating_screen) {
redraw_later(type); /* remember type for next time */
must_redraw = type;
- if (type > INVERTED_ALL)
- curwin->w_lines_valid = 0; /* don't use w_lines[].wl_size now */
- return;
+ if (type > INVERTED_ALL) {
+ curwin->w_lines_valid = 0; // don't use w_lines[].wl_size now
+ }
+ return FAIL;
}
updating_screen = TRUE;
@@ -336,8 +343,7 @@ void update_screen(int type)
type = CLEAR;
} else if (type != CLEAR) {
check_for_delay(false);
- grid_ins_lines(&default_grid, 0, msg_scrolled, (int)Rows,
- 0, (int)Columns);
+ grid_ins_lines(&default_grid, 0, msg_scrolled, Rows, 0, Columns);
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (wp->w_floating) {
continue;
@@ -482,13 +488,13 @@ void update_screen(int type)
}
end_search_hl();
+
// May need to redraw the popup menu.
- if (pum_drawn() && redraw_popupmenu) {
+ if (pum_drawn() && must_redraw_pum) {
pum_redraw();
}
send_grid_resize = false;
- redraw_popupmenu = false;
/* Reset b_mod_set flags. Going through all windows is probably faster
* than going through all buffers (there could be many buffers). */
@@ -511,28 +517,30 @@ void update_screen(int type)
// either cmdline is cleared, not drawn or mode is last drawn
cmdline_was_last_drawn = false;
+ return OK;
}
-/*
- * Return TRUE if the cursor line in window "wp" may be concealed, according
- * to the 'concealcursor' option.
- */
-int conceal_cursor_line(win_T *wp)
+// Return true if the cursor line in window "wp" may be concealed, according
+// to the 'concealcursor' option.
+bool conceal_cursor_line(const win_T *wp)
+ FUNC_ATTR_NONNULL_ALL
{
int c;
- if (*wp->w_p_cocu == NUL)
- return FALSE;
- if (get_real_state() & VISUAL)
+ if (*wp->w_p_cocu == NUL) {
+ return false;
+ }
+ if (get_real_state() & VISUAL) {
c = 'v';
- else if (State & INSERT)
+ } else if (State & INSERT) {
c = 'i';
- else if (State & NORMAL)
+ } else if (State & NORMAL) {
c = 'n';
- else if (State & CMDLINE)
+ } else if (State & CMDLINE) {
c = 'c';
- else
- return FALSE;
+ } else {
+ return false;
+ }
return vim_strchr(wp->w_p_cocu, c) != NULL;
}
@@ -554,7 +562,8 @@ void conceal_check_cursor_line(void)
///
/// If true, both old and new cursorline will need
/// need to be redrawn when moving cursor within windows.
-bool win_cursorline_standout(win_T *wp)
+bool win_cursorline_standout(const win_T *wp)
+ FUNC_ATTR_NONNULL_ALL
{
return wp->w_p_cul || (wp->w_p_cole > 0 && !conceal_cursor_line(wp));
}
@@ -600,8 +609,7 @@ static void win_update(win_T *wp)
updating. 999 when no bot area updating */
int scrolled_down = FALSE; /* TRUE when scrolled down when
w_topline got smaller a bit */
- matchitem_T *cur; /* points to the match list */
- int top_to_mod = FALSE; /* redraw above mod_top */
+ bool top_to_mod = false; // redraw above mod_top
int row; /* current window row to display */
linenr_T lnum; /* current buffer lnum to display */
@@ -696,21 +704,20 @@ static void win_update(win_T *wp)
if (mod_bot == 0 || mod_bot < buf->b_mod_bot)
mod_bot = buf->b_mod_bot;
- /* When 'hlsearch' is on and using a multi-line search pattern, a
- * change in one line may make the Search highlighting in a
- * previous line invalid. Simple solution: redraw all visible
- * lines above the change.
- * Same for a match pattern.
- */
+ // When 'hlsearch' is on and using a multi-line search pattern, a
+ // change in one line may make the Search highlighting in a
+ // previous line invalid. Simple solution: redraw all visible
+ // lines above the change.
+ // Same for a match pattern.
if (search_hl.rm.regprog != NULL
- && re_multiline(search_hl.rm.regprog))
- top_to_mod = TRUE;
- else {
- cur = wp->w_match_head;
+ && re_multiline(search_hl.rm.regprog)) {
+ top_to_mod = true;
+ } else {
+ const matchitem_T *cur = wp->w_match_head;
while (cur != NULL) {
if (cur->match.regprog != NULL
&& re_multiline(cur->match.regprog)) {
- top_to_mod = TRUE;
+ top_to_mod = true;
break;
}
cur = cur->next;
@@ -1457,7 +1464,7 @@ static void win_update(win_T *wp)
// Last line isn't finished: Display "@@@" in the last screen line.
grid_puts_len(&wp->w_grid, (char_u *)"@@", 2, scr_row, 0, at_attr);
- grid_fill(&wp->w_grid, scr_row, scr_row + 1, 2, (int)wp->w_grid.Columns,
+ grid_fill(&wp->w_grid, scr_row, scr_row + 1, 2, wp->w_grid.Columns,
'@', ' ', at_attr);
set_empty_rows(wp, srow);
wp->w_botline = lnum;
@@ -1468,7 +1475,7 @@ static void win_update(win_T *wp)
set_empty_rows(wp, srow);
wp->w_botline = lnum;
} else {
- win_draw_end(wp, '@', ' ', true, srow, wp->w_grid.Rows, at_attr);
+ win_draw_end(wp, '@', ' ', true, srow, wp->w_grid.Rows, HLF_AT);
wp->w_botline = lnum;
}
} else {
@@ -1584,6 +1591,7 @@ static int win_fill_end(win_T *wp, int c1, int c2, int off, int width, int row,
static void win_draw_end(win_T *wp, int c1, int c2, bool draw_margin, int row,
int endrow, hlf_T hl)
{
+ assert(hl >= 0 && hl < HLF_COUNT);
int n = 0;
if (draw_margin) {
@@ -1709,7 +1717,6 @@ static void fold_line(win_T *wp, long fold_count, foldinfo_T *foldinfo, linenr_T
int col;
int txtcol;
int off;
- int ri;
/* Build the fold line:
* 1. Add the cmdwin_type for the command-line window
@@ -1753,15 +1760,18 @@ static void fold_line(win_T *wp, long fold_count, foldinfo_T *foldinfo, linenr_T
col += fdc;
}
-# define RL_MEMSET(p, v, l) if (wp->w_p_rl) { \
- for (ri = 0; ri < l; ri++) { \
- linebuf_attr[off + (wp->w_grid.Columns - (p) - (l)) + ri] = v; \
- } \
- } else { \
- for (ri = 0; ri < l; ri++) { \
- linebuf_attr[off + (p) + ri] = v; \
+# define RL_MEMSET(p, v, l) \
+ do { \
+ if (wp->w_p_rl) { \
+ for (int ri = 0; ri < l; ri++) { \
+ linebuf_attr[off + (wp->w_grid.Columns - (p) - (l)) + ri] = v; \
+ } \
+ } else { \
+ for (int ri = 0; ri < l; ri++) { \
+ linebuf_attr[off + (p) + ri] = v; \
+ } \
} \
- }
+ } while (0)
/* Set all attributes of the 'number' or 'relativenumber' column and the
* text */
@@ -1775,7 +1785,9 @@ static void fold_line(win_T *wp, long fold_count, foldinfo_T *foldinfo, linenr_T
if (len > len_max) {
len = len_max;
}
- copy_text_attr(off + col, (char_u *)" ", len,
+ char_u space_buf[18] = " ";
+ assert((size_t)len_max <= sizeof(space_buf));
+ copy_text_attr(off + col, space_buf, len,
win_hl_attr(wp, HLF_FL));
col += len;
}
@@ -2061,7 +2073,8 @@ win_line (
int row; // row in the window, excl w_winrow
ScreenGrid *grid = &wp->w_grid; // grid specfic to the window
- char_u extra[18]; // line number and 'fdc' must fit in here
+ char_u extra[57]; // sign, line number and 'fdc' must
+ // fit in here
int n_extra = 0; // number of extra chars
char_u *p_extra = NULL; // string of extra chars, plus NUL
char_u *p_extra_free = NULL; // p_extra needs to be freed
@@ -2074,7 +2087,7 @@ win_line (
int lcs_eol_one = wp->w_p_lcs_chars.eol; // 'eol' until it's been used
int lcs_prec_todo = wp->w_p_lcs_chars.prec; // 'prec' until it's been used
- /* saved "extra" items for when draw_state becomes WL_LINE (again) */
+ // saved "extra" items for when draw_state becomes WL_LINE (again)
int saved_n_extra = 0;
char_u *saved_p_extra = NULL;
int saved_c_extra = 0;
@@ -2712,15 +2725,24 @@ win_line (
sign_idx, count);
if (text_sign != 0) {
p_extra = sign_get_text(text_sign);
- int symbol_blen = (int)STRLEN(p_extra);
if (p_extra != NULL) {
+ int symbol_blen = (int)STRLEN(p_extra);
+
c_extra = NUL;
c_final = NUL;
+
+ // TODO(oni-link): Is sign text already extended to
+ // full cell width?
+ assert((size_t)win_signcol_width(wp)
+ >= mb_string2cells(p_extra));
// symbol(s) bytes + (filling spaces) (one byte each)
n_extra = symbol_blen +
(win_signcol_width(wp) - mb_string2cells(p_extra));
+
+ assert(sizeof(extra) > (size_t)symbol_blen);
memset(extra, ' ', sizeof(extra));
- STRNCPY(extra, p_extra, STRLEN(p_extra));
+ memcpy(extra, p_extra, symbol_blen);
+
p_extra = extra;
p_extra[n_extra] = NUL;
}
@@ -3121,7 +3143,7 @@ win_line (
c = '>';
mb_c = c;
mb_l = 1;
- mb_utf8 = false;
+ (void)mb_l;
multi_attr = win_hl_attr(wp, HLF_AT);
// put the pointer back to output the double-width
@@ -3132,9 +3154,9 @@ win_line (
n_extra -= mb_l - 1;
p_extra += mb_l - 1;
}
- ++p_extra;
+ p_extra++;
}
- --n_extra;
+ n_extra--;
} else {
int c0;
@@ -3753,15 +3775,14 @@ win_line (
n_attr3 = 1;
}
- /*
- * At end of the text line or just after the last character.
- */
+ // At end of the text line or just after the last character.
if (c == NUL) {
- long prevcol = (long)(ptr - line) - (c == NUL);
+ long prevcol = (long)(ptr - line) - 1;
- /* we're not really at that column when skipping some text */
- if ((long)(wp->w_p_wrap ? wp->w_skipcol : wp->w_leftcol) > prevcol)
- ++prevcol;
+ // we're not really at that column when skipping some text
+ if ((long)(wp->w_p_wrap ? wp->w_skipcol : wp->w_leftcol) > prevcol) {
+ prevcol++;
+ }
// Invert at least one char, used for Visual and empty line or
// highlight match at end of line. If it's beyond the last
@@ -3784,8 +3805,7 @@ win_line (
&& ((area_attr != 0 && vcol == fromcol
&& (VIsual_mode != Ctrl_V
|| lnum == VIsual.lnum
- || lnum == curwin->w_cursor.lnum)
- && c == NUL)
+ || lnum == curwin->w_cursor.lnum))
// highlight 'hlsearch' match at end of line
|| prevcol_hl_flag)) {
int n = 0;
@@ -4012,7 +4032,7 @@ win_line (
&& filler_todo <= 0
&& (wp->w_p_rl ? col == 0 : col == grid->Columns - 1)
&& (*ptr != NUL
- || (wp->w_p_list && lcs_eol_one > 0)
+ || lcs_eol_one > 0
|| (n_extra && (c_extra != NUL || *p_extra != NUL)))) {
c = wp->w_p_lcs_chars.ext;
char_attr = win_hl_attr(wp, HLF_AT);
@@ -4026,14 +4046,15 @@ win_line (
}
}
- /* advance to the next 'colorcolumn' */
- if (draw_color_col)
+ // advance to the next 'colorcolumn'
+ if (draw_color_col) {
draw_color_col = advance_color_col(VCOL_HLC, &color_cols);
+ }
- /* Highlight the cursor column if 'cursorcolumn' is set. But don't
- * highlight the cursor position itself.
- * Also highlight the 'colorcolumn' if it is different than
- * 'cursorcolumn' */
+ // Highlight the cursor column if 'cursorcolumn' is set. But don't
+ // highlight the cursor position itself.
+ // Also highlight the 'colorcolumn' if it is different than
+ // 'cursorcolumn'
vcol_save_attr = -1;
if (draw_state == WL_LINE && !lnum_in_visual_area
&& search_attr == 0 && area_attr == 0) {
@@ -4052,10 +4073,8 @@ win_line (
char_attr = hl_combine_attr(line_attr_lowprio, char_attr);
}
- /*
- * Store character to be displayed.
- * Skip characters that are left of the screen for 'nowrap'.
- */
+ // Store character to be displayed.
+ // Skip characters that are left of the screen for 'nowrap'.
vcol_prev = vcol;
if (draw_state < WL_LINE || n_skip <= 0) {
//
@@ -4355,6 +4374,12 @@ static void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol,
screen_adjust_grid(&grid, &row, &coloff);
+ // Safety check. Avoids clang warnings down the call stack.
+ if (grid->chars == NULL || row >= grid->Rows || col >= grid->Columns) {
+ DLOG("invalid state, skipped");
+ return;
+ }
+
off_from = 0;
off_to = grid->line_offset[row] + coloff;
max_off_from = linebuf_size;
@@ -4550,17 +4575,21 @@ void redraw_statuslines(void)
/*
* Redraw all status lines at the bottom of frame "frp".
*/
-void win_redraw_last_status(frame_T *frp)
+void win_redraw_last_status(const frame_T *frp)
+ FUNC_ATTR_NONNULL_ARG(1)
{
- if (frp->fr_layout == FR_LEAF)
- frp->fr_win->w_redr_status = TRUE;
- else if (frp->fr_layout == FR_ROW) {
- for (frp = frp->fr_child; frp != NULL; frp = frp->fr_next)
+ if (frp->fr_layout == FR_LEAF) {
+ frp->fr_win->w_redr_status = true;
+ } else if (frp->fr_layout == FR_ROW) {
+ FOR_ALL_FRAMES(frp, frp->fr_child) {
win_redraw_last_status(frp);
- } else { /* frp->fr_layout == FR_COL */
+ }
+ } else {
+ assert(frp->fr_layout == FR_COL);
frp = frp->fr_child;
- while (frp->fr_next != NULL)
+ while (frp->fr_next != NULL) {
frp = frp->fr_next;
+ }
win_redraw_last_status(frp);
}
}
@@ -4641,7 +4670,7 @@ win_redr_status_matches (
int showtail
)
{
-#define L_MATCH(m) (showtail ? sm_gettail(matches[m]) : matches[m])
+#define L_MATCH(m) (showtail ? sm_gettail(matches[m], false) : matches[m])
int row;
char_u *buf;
int len;
@@ -4798,7 +4827,7 @@ win_redr_status_matches (
grid_puts(&default_grid, selstart, row, selstart_col, HL_ATTR(HLF_WM));
}
- grid_fill(&default_grid, row, row + 1, clen, (int)Columns,
+ grid_fill(&default_grid, row, row + 1, clen, Columns,
fillchar, fillchar, attr);
}
@@ -5141,7 +5170,7 @@ win_redr_custom (
/*
* Draw each snippet with the specified highlighting.
*/
- screen_puts_line_start(row);
+ grid_puts_line_start(&default_grid, row);
curattr = attr;
p = buf;
@@ -5164,7 +5193,7 @@ win_redr_custom (
grid_puts(&default_grid, p >= buf + len ? (char_u *)"" : p, row, col,
curattr);
- grid_puts_line_flush(&default_grid, false);
+ grid_puts_line_flush(false);
if (wp == NULL) {
// Fill the tab_page_click_defs array for clicking in the tab pages line.
@@ -5310,18 +5339,20 @@ void grid_puts(ScreenGrid *grid, char_u *text, int row, int col, int attr)
grid_puts_len(grid, text, -1, row, col, attr);
}
+static ScreenGrid *put_dirty_grid = NULL;
static int put_dirty_row = -1;
static int put_dirty_first = INT_MAX;
static int put_dirty_last = 0;
-/// Start a group of screen_puts_len calls that builds a single screen line.
+/// Start a group of grid_puts_len calls that builds a single grid line.
///
-/// Must be matched with a screen_puts_line_flush call before moving to
+/// Must be matched with a grid_puts_line_flush call before moving to
/// another line.
-void screen_puts_line_start(int row)
+void grid_puts_line_start(ScreenGrid *grid, int row)
{
assert(put_dirty_row == -1);
put_dirty_row = row;
+ put_dirty_grid = grid;
}
/// like grid_puts(), but output "text[len]". When "len" is -1 output up to
@@ -5347,16 +5378,19 @@ void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row,
screen_adjust_grid(&grid, &row, &col);
- // safety check
- if (grid->chars == NULL || row >= grid->Rows || col >= grid->Columns) {
+ // Safety check. The check for negative row and column is to fix issue
+ // vim/vim#4102. TODO: find out why row/col could be negative.
+ if (grid->chars == NULL
+ || row >= grid->Rows || row < 0
+ || col >= grid->Columns || col < 0) {
return;
}
if (put_dirty_row == -1) {
- screen_puts_line_start(row);
+ grid_puts_line_start(grid, row);
do_flush = true;
} else {
- if (row != put_dirty_row) {
+ if (grid != put_dirty_grid || row != put_dirty_row) {
abort();
}
}
@@ -5459,31 +5493,31 @@ void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row,
}
if (do_flush) {
- grid_puts_line_flush(grid, true);
+ grid_puts_line_flush(true);
}
}
-/// End a group of screen_puts_len calls and send the screen buffer to the UI
+/// End a group of grid_puts_len calls and send the screen buffer to the UI
/// layer.
///
-/// @param grid The grid which contains the buffer.
/// @param set_cursor Move the visible cursor to the end of the changed region.
/// This is a workaround for not yet refactored code paths
/// and shouldn't be used in new code.
-void grid_puts_line_flush(ScreenGrid *grid, bool set_cursor)
+void grid_puts_line_flush(bool set_cursor)
{
assert(put_dirty_row != -1);
if (put_dirty_first < put_dirty_last) {
if (set_cursor) {
- ui_grid_cursor_goto(grid->handle, put_dirty_row,
- MIN(put_dirty_last, grid->Columns-1));
+ ui_grid_cursor_goto(put_dirty_grid->handle, put_dirty_row,
+ MIN(put_dirty_last, put_dirty_grid->Columns-1));
}
- ui_line(grid, put_dirty_row, put_dirty_first, put_dirty_last,
+ ui_line(put_dirty_grid, put_dirty_row, put_dirty_first, put_dirty_last,
put_dirty_last, 0, false);
put_dirty_first = INT_MAX;
put_dirty_last = 0;
}
put_dirty_row = -1;
+ put_dirty_grid = NULL;
}
/*
@@ -5971,7 +6005,14 @@ void grid_assign_handle(ScreenGrid *grid)
/// needed.
void screenalloc(void)
{
- static bool entered = false; // avoid recursiveness
+ // It's possible that we produce an out-of-memory message below, which
+ // will cause this function to be called again. To break the loop, just
+ // return here.
+ if (resizing) {
+ return;
+ }
+ resizing = true;
+
int retry_count = 0;
retry:
@@ -5985,19 +6026,11 @@ retry:
|| Rows == 0
|| Columns == 0
|| (!full_screen && default_grid.chars == NULL)) {
+ resizing = false;
return;
}
/*
- * It's possible that we produce an out-of-memory message below, which
- * will cause this function to be called again. To break the loop, just
- * return here.
- */
- if (entered)
- return;
- entered = TRUE;
-
- /*
* Note that the window sizes are updated before reallocating the arrays,
* thus we must not redraw here!
*/
@@ -6037,8 +6070,7 @@ retry:
must_redraw = CLEAR; // need to clear the screen later
- entered = FALSE;
- --RedrawingDisabled;
+ RedrawingDisabled--;
/*
* Do not apply autocommands more than 3 times to avoid an endless loop
@@ -6050,14 +6082,16 @@ retry:
* jump back to check if we need to allocate the screen again. */
goto retry;
}
+
+ resizing = false;
}
void grid_alloc(ScreenGrid *grid, int rows, int columns, bool copy, bool valid)
{
int new_row;
ScreenGrid new = *grid;
-
- size_t ncells = (size_t)((rows+1) * columns);
+ assert(rows >= 0 && columns >= 0);
+ size_t ncells = (size_t)rows * columns;
new.chars = xmalloc(ncells * sizeof(schar_T));
new.attrs = xmalloc(ncells * sizeof(sattr_T));
new.line_offset = xmalloc((size_t)(rows * sizeof(unsigned)));
@@ -6623,7 +6657,7 @@ static void recording_mode(int attr)
/*
* Draw the tab pages line at the top of the Vim window.
*/
-static void draw_tabline(void)
+void draw_tabline(void)
{
int tabcount = 0;
int tabwidth = 0;
@@ -6781,13 +6815,11 @@ static void draw_tabline(void)
c = '_';
else
c = ' ';
- grid_fill(&default_grid, 0, 1, col, (int)Columns, c, c,
- attr_fill);
+ grid_fill(&default_grid, 0, 1, col, Columns, c, c, attr_fill);
/* Put an "X" for closing the current tab if there are several. */
if (first_tabpage->tp_next != NULL) {
- grid_putchar(&default_grid, 'X', 0, (int)Columns - 1,
- attr_nosel);
+ grid_putchar(&default_grid, 'X', 0, Columns - 1, attr_nosel);
tab_page_click_defs[Columns - 1] = (StlClickDefinition) {
.type = kStlClickTabClose,
.tabnr = 999,
@@ -7150,6 +7182,8 @@ void screen_resize(int width, int height)
check_shellsize();
height = Rows;
width = Columns;
+ p_lines = Rows;
+ p_columns = Columns;
ui_call_grid_resize(1, width, height);
send_grid_resize = true;
diff --git a/src/nvim/search.c b/src/nvim/search.c
index 4d02a07cbd..fe4fdf57ba 100644
--- a/src/nvim/search.c
+++ b/src/nvim/search.c
@@ -352,7 +352,7 @@ int ignorecase_opt(char_u *pat, int ic_in, int scs)
{
int ic = ic_in;
if (ic && !no_smartcase && scs
- && !(ctrl_x_mode && curbuf->b_p_inf)
+ && !(ctrl_x_mode_not_default() && curbuf->b_p_inf)
) {
ic = !pat_has_uppercase(pat);
}
@@ -864,13 +864,11 @@ int searchit(
}
at_first_line = FALSE;
- /*
- * Stop the search if wrapscan isn't set, "stop_lnum" is
- * specified, after an interrupt, after a match and after looping
- * twice.
- */
+ // Stop the search if wrapscan isn't set, "stop_lnum" is
+ // specified, after an interrupt, after a match and after looping
+ // twice.
if (!p_ws || stop_lnum != 0 || got_int || called_emsg
- || (timed_out != NULL && timed_out)
+ || (timed_out != NULL && *timed_out)
|| break_loop
|| found || loop) {
break;
@@ -1132,14 +1130,14 @@ int do_search(
&& !cmd_silent && msg_silent == 0) {
char_u *trunc;
char_u off_buf[40];
- int off_len = 0;
+ size_t off_len = 0;
// Compute msg_row early.
msg_start();
// Get the offset, so we know how long it is.
if (spats[0].off.line || spats[0].off.end || spats[0].off.off) {
- p = off_buf;
+ p = off_buf; // -V507
*p++ = dirc;
if (spats[0].off.end) {
*p++ = 'e';
@@ -1168,12 +1166,14 @@ int do_search(
// search stat. Use all the space available, so that the
// search state is right aligned. If there is not enough space
// msg_strtrunc() will shorten in the middle.
- if (msg_scrolled != 0) {
+ if (ui_has(kUIMessages)) {
+ len = 0; // adjusted below
+ } else if (msg_scrolled != 0) {
// Use all the columns.
- len = (int)(Rows - msg_row) * Columns - 1;
+ len = (Rows - msg_row) * Columns - 1;
} else {
// Use up to 'showcmd' column.
- len = (int)(Rows - msg_row - 1) * Columns + sc_col - 1;
+ len = (Rows - msg_row - 1) * Columns + sc_col - 1;
}
if (len < STRLEN(p) + off_len + SEARCH_STAT_BUF_LEN + 3) {
len = STRLEN(p) + off_len + SEARCH_STAT_BUF_LEN + 3;
@@ -1183,7 +1183,7 @@ int do_search(
len = STRLEN(p) + off_len + 3;
}
- msgbuf = xmalloc((int)len);
+ msgbuf = xmalloc(len);
{
memset(msgbuf, ' ', len);
msgbuf[0] = dirc;
@@ -1215,7 +1215,7 @@ int do_search(
xfree(msgbuf);
msgbuf = r;
// move reversed text to beginning of buffer
- while (*r != NUL && *r == ' ') {
+ while (*r == ' ') {
r++;
}
size_t pat_len = msgbuf + STRLEN(msgbuf) - r;
@@ -3368,7 +3368,6 @@ current_tagblock(
)
{
long count = count_arg;
- long n;
pos_T old_pos;
pos_T start_pos;
pos_T end_pos;
@@ -3377,7 +3376,6 @@ current_tagblock(
char_u *p;
char_u *cp;
int len;
- int r;
bool do_include = include;
bool save_p_ws = p_ws;
int retval = FAIL;
@@ -3426,12 +3424,12 @@ again:
* Search backwards for unclosed "<aaa>".
* Put this position in start_pos.
*/
- for (n = 0; n < count; ++n) {
- if (do_searchpair((char_u *)
- "<[^ \t>/!]\\+\\%(\\_s\\_[^>]\\{-}[^/]>\\|$\\|\\_s\\=>\\)",
- (char_u *)"",
- (char_u *)"</[^>]*>", BACKWARD, (char_u *)"", 0,
- NULL, (linenr_T)0, 0L) <= 0) {
+ for (long n = 0; n < count; n++) {
+ if (do_searchpair(
+ (char_u *)"<[^ \t>/!]\\+\\%(\\_s\\_[^>]\\{-}[^/]>\\|$\\|\\_s\\=>\\)",
+ (char_u *)"",
+ (char_u *)"</[^>]*>", BACKWARD, NULL, 0,
+ NULL, (linenr_T)0, 0L) <= 0) {
curwin->w_cursor = old_pos;
goto theend;
}
@@ -3457,8 +3455,8 @@ again:
sprintf((char *)spat, "<%.*s\\>\\%%(\\s\\_[^>]\\{-}[^/]>\\|>\\)\\c", len, p);
sprintf((char *)epat, "</%.*s>\\c", len, p);
- r = do_searchpair(spat, (char_u *)"", epat, FORWARD, (char_u *)"",
- 0, NULL, (linenr_T)0, 0L);
+ const int r = do_searchpair(spat, (char_u *)"", epat, FORWARD, NULL,
+ 0, NULL, (linenr_T)0, 0L);
xfree(spat);
xfree(epat);
@@ -4244,6 +4242,7 @@ static void search_stat(int dirc, pos_T *pos,
// STRNICMP ignores case, but we should not ignore case.
// Unfortunately, there is no STRNICMP function.
if (!(chgtick == buf_get_changedtick(curbuf)
+ && lastpat != NULL // supress clang/NULL passed as nonnull parameter
&& STRNICMP(lastpat, spats[last_idx].pat, STRLEN(lastpat)) == 0
&& STRLEN(lastpat) == STRLEN(spats[last_idx].pat)
&& equalpos(lastpos, curwin->w_cursor)
@@ -4289,7 +4288,7 @@ static void search_stat(int dirc, pos_T *pos,
if (curwin->w_p_rl && *curwin->w_p_rlc == 's') {
if (cur == OUT_OF_TIME) {
- vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]");
+ vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?\?/?]");
} else if (cnt > 99 && cur > 99) {
vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>99/>99]");
} else if (cnt > 99) {
@@ -4328,6 +4327,7 @@ static void search_stat(int dirc, pos_T *pos,
// keep the message even after redraw, but don't put in history
msg_hist_off = true;
+ msg_ext_set_kind("search_count");
give_warning(msgbuf, false);
msg_hist_off = false;
}
@@ -4343,8 +4343,8 @@ find_pattern_in_path(
char_u *ptr, // pointer to search pattern
int dir, // direction of expansion
size_t len, // length of search pattern
- int whole, // match whole words only
- int skip_comments, // don't match inside comments
+ bool whole, // match whole words only
+ bool skip_comments, // don't match inside comments
int type, // Type of search; are we looking for a type?
// a macro?
long count,
@@ -4574,10 +4574,9 @@ find_pattern_in_path(
xfree(files);
files = bigger;
}
- if ((files[depth + 1].fp = mch_fopen((char *)new_fname, "r"))
- == NULL)
+ if ((files[depth + 1].fp = os_fopen((char *)new_fname, "r")) == NULL) {
xfree(new_fname);
- else {
+ } else {
if (++depth == old_files) {
// Something wrong. We will forget one of our already visited files
// now.
diff --git a/src/nvim/shada.c b/src/nvim/shada.c
index 4aafc669dc..3ebe75b980 100644
--- a/src/nvim/shada.c
+++ b/src/nvim/shada.c
@@ -151,15 +151,6 @@ KHASH_SET_INIT_STR(strset)
/// Callback function for add_search_pattern
typedef void (*SearchPatternGetter)(SearchPattern *);
-/// Flags for shada_read_file and children
-typedef enum {
- kShaDaWantInfo = 1, ///< Load non-mark information
- kShaDaWantMarks = 2, ///< Load local file marks and change list
- kShaDaForceit = 4, ///< Overwrite info already read
- kShaDaGetOldfiles = 8, ///< Load v:oldfiles.
- kShaDaMissingError = 16, ///< Error out when os_open returns -ENOENT.
-} ShaDaReadFileFlags;
-
/// Possible ShaDa entry types
///
/// @warning Enum values are part of the API and must not be altered.
@@ -283,7 +274,7 @@ typedef struct {
char sep;
list_T *additional_elements;
} history_item;
- struct reg {
+ struct reg { // yankreg_T
char name;
MotionType type;
char **contents;
@@ -1328,13 +1319,13 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags)
break;
}
if (!force) {
- const yankreg_T *const reg = op_register_get(cur_entry.data.reg.name);
+ const yankreg_T *const reg = op_reg_get(cur_entry.data.reg.name);
if (reg == NULL || reg->timestamp >= cur_entry.timestamp) {
shada_free_shada_entry(&cur_entry);
break;
}
}
- if (!op_register_set(cur_entry.data.reg.name, (yankreg_T) {
+ if (!op_reg_set(cur_entry.data.reg.name, (yankreg_T) {
.y_array = (char_u **)cur_entry.data.reg.contents,
.y_size = cur_entry.data.reg.contents_size,
.y_type = cur_entry.data.reg.type,
@@ -2496,7 +2487,7 @@ static inline void shada_initialize_registers(WriteMergerState *const wms,
yankreg_T reg;
char name = NUL;
bool is_unnamed = false;
- reg_iter = op_register_iter(reg_iter, &name, &reg, &is_unnamed);
+ reg_iter = op_global_reg_iter(reg_iter, &name, &reg, &is_unnamed);
if (name == NUL) {
break;
}
@@ -2552,6 +2543,19 @@ static inline void replace_numbered_mark(WriteMergerState *const wms,
wms->numbered_marks[idx].data.data.filemark.name = (char)('0' + (int)idx);
}
+/// Find buffers ignored due to their location.
+///
+/// @param[out] removable_bufs Cache of buffers ignored due to their location.
+static inline void find_removable_bufs(khash_t(bufset) *removable_bufs)
+{
+ FOR_ALL_BUFFERS(buf) {
+ if (buf->b_ffname != NULL && shada_removable((char *)buf->b_ffname)) {
+ int kh_ret;
+ (void)kh_put(bufset, removable_bufs, (uintptr_t)buf, &kh_ret);
+ }
+ }
+}
+
/// Write ShaDa file
///
/// @param[in] sd_writer Structure containing file writer definition.
@@ -2621,12 +2625,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer,
set_last_cursor(wp);
}
- FOR_ALL_BUFFERS(buf) {
- if (buf->b_ffname != NULL && shada_removable((char *) buf->b_ffname)) {
- int kh_ret;
- (void) kh_put(bufset, &removable_bufs, (uintptr_t) buf, &kh_ret);
- }
- }
+ find_removable_bufs(&removable_bufs);
// Write header
if (shada_pack_entry(packer, (ShadaEntry) {
@@ -2673,7 +2672,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer,
do {
typval_T vartv;
const char *name = NULL;
- var_iter = var_shada_iter(var_iter, &name, &vartv);
+ var_iter = var_shada_iter(var_iter, &name, &vartv, VAR_FLAVOUR_SHADA);
if (name == NULL) {
break;
}
@@ -2737,49 +2736,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer,
}
// Initialize jump list
- const void *jump_iter = NULL;
- cleanup_jumplist(curwin, false);
- setpcmark();
- do {
- xfmark_T fm;
- jump_iter = mark_jumplist_iter(jump_iter, curwin, &fm);
-
- if (fm.fmark.mark.lnum == 0) {
- iemsgf("ShaDa: mark lnum zero (ji:%p, js:%p, len:%i)",
- (void *)jump_iter, (void *)&curwin->w_jumplist[0],
- curwin->w_jumplistlen);
- continue;
- }
- const buf_T *const buf = (fm.fmark.fnum == 0
- ? NULL
- : buflist_findnr(fm.fmark.fnum));
- if (buf != NULL
- ? in_bufset(&removable_bufs, buf)
- : fm.fmark.fnum != 0) {
- continue;
- }
- const char *const fname = (char *) (fm.fmark.fnum == 0
- ? (fm.fname == NULL ? NULL : fm.fname)
- : buf->b_ffname);
- if (fname == NULL) {
- continue;
- }
- wms->jumps[wms->jumps_size++] = (PossiblyFreedShadaEntry) {
- .can_free_entry = false,
- .data = {
- .type = kSDItemJump,
- .timestamp = fm.fmark.timestamp,
- .data = {
- .filemark = {
- .name = NUL,
- .mark = fm.fmark.mark,
- .fname = (char *) fname,
- .additional_data = fm.fmark.additional_data,
- }
- }
- }
- };
- } while (jump_iter != NULL);
+ wms->jumps_size = shada_init_jumps(wms->jumps, &removable_bufs);
// Initialize global marks
if (dump_global_marks) {
@@ -4117,3 +4074,236 @@ static bool shada_removable(const char *name)
xfree(new_name);
return retval;
}
+
+/// Initialize ShaDa jumplist entries.
+///
+/// @param[in,out] jumps Array of ShaDa entries to set.
+/// @param[in] removable_bufs Cache of buffers ignored due to their
+/// location.
+///
+/// @return number of jumplist entries
+static inline size_t shada_init_jumps(
+ PossiblyFreedShadaEntry *jumps, khash_t(bufset) *const removable_bufs)
+{
+ // Initialize jump list
+ size_t jumps_size = 0;
+ const void *jump_iter = NULL;
+ setpcmark();
+ cleanup_jumplist(curwin, false);
+ do {
+ xfmark_T fm;
+ jump_iter = mark_jumplist_iter(jump_iter, curwin, &fm);
+
+ if (fm.fmark.mark.lnum == 0) {
+ iemsgf("ShaDa: mark lnum zero (ji:%p, js:%p, len:%i)",
+ (void *)jump_iter, (void *)&curwin->w_jumplist[0],
+ curwin->w_jumplistlen);
+ continue;
+ }
+ const buf_T *const buf = (fm.fmark.fnum == 0
+ ? NULL
+ : buflist_findnr(fm.fmark.fnum));
+ if (buf != NULL
+ ? in_bufset(removable_bufs, buf)
+ : fm.fmark.fnum != 0) {
+ continue;
+ }
+ const char *const fname = (char *) (fm.fmark.fnum == 0
+ ? (fm.fname == NULL ? NULL : fm.fname)
+ : buf->b_ffname);
+ if (fname == NULL) {
+ continue;
+ }
+ jumps[jumps_size++] = (PossiblyFreedShadaEntry) {
+ .can_free_entry = false,
+ .data = {
+ .type = kSDItemJump,
+ .timestamp = fm.fmark.timestamp,
+ .data = {
+ .filemark = {
+ .name = NUL,
+ .mark = fm.fmark.mark,
+ .fname = (char *) fname,
+ .additional_data = fm.fmark.additional_data,
+ }
+ }
+ }
+ };
+ } while (jump_iter != NULL);
+ return jumps_size;
+}
+
+/// Write registers ShaDa entries in given msgpack_sbuffer.
+///
+/// @param[in] sbuf target msgpack_sbuffer to write to.
+void shada_encode_regs(msgpack_sbuffer *const sbuf)
+ FUNC_ATTR_NONNULL_ALL
+{
+ WriteMergerState *const wms = xcalloc(1, sizeof(*wms));
+ shada_initialize_registers(wms, -1);
+ msgpack_packer packer;
+ msgpack_packer_init(&packer, sbuf, msgpack_sbuffer_write);
+ for (size_t i = 0; i < ARRAY_SIZE(wms->registers); i++) {
+ if (wms->registers[i].data.type == kSDItemRegister) {
+ if (kSDWriteFailed
+ == shada_pack_pfreed_entry(&packer, wms->registers[i], 0)) {
+ abort();
+ }
+ }
+ }
+ xfree(wms);
+}
+
+/// Write jumplist ShaDa entries in given msgpack_sbuffer.
+///
+/// @param[in] sbuf target msgpack_sbuffer to write to.
+void shada_encode_jumps(msgpack_sbuffer *const sbuf)
+ FUNC_ATTR_NONNULL_ALL
+{
+ khash_t(bufset) removable_bufs = KHASH_EMPTY_TABLE(bufset);
+ find_removable_bufs(&removable_bufs);
+ PossiblyFreedShadaEntry jumps[JUMPLISTSIZE];
+ size_t jumps_size = shada_init_jumps(jumps, &removable_bufs);
+ msgpack_packer packer;
+ msgpack_packer_init(&packer, sbuf, msgpack_sbuffer_write);
+ for (size_t i = 0; i < jumps_size; i++) {
+ if (kSDWriteFailed == shada_pack_pfreed_entry(&packer, jumps[i], 0)) {
+ abort();
+ }
+ }
+}
+
+/// Write buffer list ShaDa entry in given msgpack_sbuffer.
+///
+/// @param[in] sbuf target msgpack_sbuffer to write to.
+void shada_encode_buflist(msgpack_sbuffer *const sbuf)
+ FUNC_ATTR_NONNULL_ALL
+{
+ khash_t(bufset) removable_bufs = KHASH_EMPTY_TABLE(bufset);
+ find_removable_bufs(&removable_bufs);
+ ShadaEntry buflist_entry = shada_get_buflist(&removable_bufs);
+ msgpack_packer packer;
+ msgpack_packer_init(&packer, sbuf, msgpack_sbuffer_write);
+ if (kSDWriteFailed == shada_pack_entry(&packer, buflist_entry, 0)) {
+ abort();
+ }
+ xfree(buflist_entry.data.buffer_list.buffers);
+}
+
+/// Write global variables ShaDa entries in given msgpack_sbuffer.
+///
+/// @param[in] sbuf target msgpack_sbuffer to write to.
+void shada_encode_gvars(msgpack_sbuffer *const sbuf)
+ FUNC_ATTR_NONNULL_ALL
+{
+ msgpack_packer packer;
+ msgpack_packer_init(&packer, sbuf, msgpack_sbuffer_write);
+ const void *var_iter = NULL;
+ const Timestamp cur_timestamp = os_time();
+ do {
+ typval_T vartv;
+ const char *name = NULL;
+ var_iter = var_shada_iter(
+ var_iter, &name, &vartv,
+ VAR_FLAVOUR_DEFAULT | VAR_FLAVOUR_SESSION | VAR_FLAVOUR_SHADA);
+ if (name == NULL) {
+ break;
+ }
+ if (vartv.v_type != VAR_FUNC && vartv.v_type != VAR_PARTIAL) {
+ typval_T tgttv;
+ tv_copy(&vartv, &tgttv);
+ ShaDaWriteResult r = shada_pack_entry(&packer, (ShadaEntry) {
+ .type = kSDItemVariable,
+ .timestamp = cur_timestamp,
+ .data = {
+ .global_var = {
+ .name = (char *)name,
+ .value = tgttv,
+ .additional_elements = NULL,
+ }
+ }
+ }, 0);
+ if (kSDWriteFailed == r) {
+ abort();
+ }
+ tv_clear(&tgttv);
+ }
+ tv_clear(&vartv);
+ } while (var_iter != NULL);
+}
+
+/// Wrapper for reading from msgpack_sbuffer.
+///
+/// @return number of bytes read.
+static ptrdiff_t read_sbuf(ShaDaReadDef *const sd_reader, void *const dest,
+ const size_t size)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ msgpack_sbuffer *sbuf = (msgpack_sbuffer *)sd_reader->cookie;
+ const uintmax_t bytes_read = MIN(size, sbuf->size - sd_reader->fpos);
+ if (bytes_read < size) {
+ sd_reader->eof = true;
+ }
+ memcpy(dest, sbuf->data + sd_reader->fpos, (size_t)bytes_read);
+ sd_reader->fpos += bytes_read;
+ return (ptrdiff_t)bytes_read;
+}
+
+/// Wrapper for read that ignores bytes read from msgpack_sbuffer.
+///
+/// Used for skipping.
+///
+/// @param[in,out] sd_reader ShaDaReadDef with msgpack_sbuffer.
+/// @param[in] offset Amount of bytes to skip.
+///
+/// @return FAIL in case of failure, OK in case of success. May set
+/// sd_reader->eof.
+static int sd_sbuf_reader_skip_read(ShaDaReadDef *const sd_reader,
+ const size_t offset)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ msgpack_sbuffer *sbuf = (msgpack_sbuffer *)sd_reader->cookie;
+ assert(sbuf->size >= sd_reader->fpos);
+ const uintmax_t skip_bytes = MIN(offset, sbuf->size - sd_reader->fpos);
+ if (skip_bytes < offset) {
+ sd_reader->eof = true;
+ return FAIL;
+ }
+ sd_reader->fpos += offset;
+ return OK;
+}
+
+/// Prepare ShaDaReadDef with msgpack_sbuffer for reading.
+///
+/// @param[in] sbuf msgpack_sbuffer to read from.
+/// @param[out] sd_reader Location where reader structure will be saved.
+static void open_shada_sbuf_for_reading(const msgpack_sbuffer *const sbuf,
+ ShaDaReadDef *sd_reader)
+ FUNC_ATTR_NONNULL_ALL
+{
+ *sd_reader = (ShaDaReadDef) {
+ .read = &read_sbuf,
+ .close = NULL,
+ .skip = &sd_sbuf_reader_skip_read,
+ .error = NULL,
+ .eof = false,
+ .fpos = 0,
+ .cookie = (void *)sbuf,
+ };
+}
+
+/// Read ShaDa from msgpack_sbuffer.
+///
+/// @param[in] file msgpack_sbuffer to read from.
+/// @param[in] flags Flags, see ShaDaReadFileFlags enum.
+void shada_read_sbuf(msgpack_sbuffer *const sbuf, const int flags)
+ FUNC_ATTR_NONNULL_ALL
+{
+ assert(sbuf != NULL);
+ if (sbuf->data == NULL) {
+ return;
+ }
+ ShaDaReadDef sd_reader;
+ open_shada_sbuf_for_reading(sbuf, &sd_reader);
+ shada_read(&sd_reader, flags);
+}
diff --git a/src/nvim/shada.h b/src/nvim/shada.h
index 49986ac1c1..2a945a06bc 100644
--- a/src/nvim/shada.h
+++ b/src/nvim/shada.h
@@ -1,6 +1,17 @@
#ifndef NVIM_SHADA_H
#define NVIM_SHADA_H
+#include <msgpack.h>
+
+/// Flags for shada_read_file and children
+typedef enum {
+ kShaDaWantInfo = 1, ///< Load non-mark information
+ kShaDaWantMarks = 2, ///< Load local file marks and change list
+ kShaDaForceit = 4, ///< Overwrite info already read
+ kShaDaGetOldfiles = 8, ///< Load v:oldfiles.
+ kShaDaMissingError = 16, ///< Error out when os_open returns -ENOENT.
+} ShaDaReadFileFlags;
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "shada.h.generated.h"
#endif
diff --git a/src/nvim/sign.c b/src/nvim/sign.c
index 8c85fbdaa7..16ab00a52b 100644
--- a/src/nvim/sign.c
+++ b/src/nvim/sign.c
@@ -28,9 +28,6 @@ struct sign
int sn_typenr; // type number of sign
char_u *sn_name; // name of sign
char_u *sn_icon; // name of pixmap
-# ifdef FEAT_SIGN_ICONS
- void *sn_image; // icon image
-# endif
char_u *sn_text; // text used instead of pixmap
int sn_line_hl; // highlight ID for line
int sn_text_hl; // highlight ID for text
@@ -83,11 +80,8 @@ static signgroup_T * sign_group_ref(const char_u *groupname)
hi = hash_lookup(&sg_table, (char *)groupname, STRLEN(groupname), hash);
if (HASHITEM_EMPTY(hi)) {
// new group
- group = (signgroup_T *)xmalloc(
- (unsigned)(sizeof(signgroup_T) + STRLEN(groupname)));
- if (group == NULL) {
- return NULL;
- }
+ group = xmalloc((unsigned)(sizeof(signgroup_T) + STRLEN(groupname)));
+
STRCPY(group->sg_name, groupname);
group->refcount = 1;
group->next_sign_id = 1;
@@ -188,10 +182,6 @@ static void insert_sign(
newsign->typenr = typenr;
if (group != NULL) {
newsign->group = sign_group_ref(group);
- if (newsign->group == NULL) {
- xfree(newsign);
- return;
- }
} else {
newsign->group = NULL;
}
@@ -263,11 +253,7 @@ char_u * sign_typenr2name(int typenr)
/// Return information about a sign in a Dict
dict_T * sign_get_info(signlist_T *sign)
{
- dict_T *d;
-
- if ((d = tv_dict_alloc()) == NULL) {
- return NULL;
- }
+ dict_T *d = tv_dict_alloc();
tv_dict_add_nr(d, S_LEN("id"), sign->id);
tv_dict_add_str(d, S_LEN("group"), ((sign->group == NULL)
? (char *)""
@@ -696,15 +682,6 @@ static void sign_define_init_icon(sign_T *sp, char_u *icon)
xfree(sp->sn_icon);
sp->sn_icon = vim_strsave(icon);
backslash_halve(sp->sn_icon);
-# ifdef FEAT_SIGN_ICONS
- if (gui.in_use) {
- out_flush();
- if (sp->sn_image != NULL) {
- gui_mch_destroy_sign(sp->sn_image);
- }
- sp->sn_image = gui_mch_register_sign(sp->sn_icon);
- }
-# endif
}
/// Initialize the text for a new sign
@@ -1347,8 +1324,8 @@ static void sign_getinfo(sign_T *sp, dict_T *retdict)
/// Otherwise, return information about the specified sign.
void sign_getlist(const char_u *name, list_T *retlist)
{
- sign_T *sp = first_sign;
- dict_T *dict;
+ sign_T *sp = first_sign;
+ dict_T *dict;
if (name != NULL) {
sp = sign_find(name, NULL);
@@ -1358,9 +1335,7 @@ void sign_getlist(const char_u *name, list_T *retlist)
}
for (; sp != NULL && !got_int; sp = sp->sn_next) {
- if ((dict = tv_dict_alloc()) == NULL) {
- return;
- }
+ dict = tv_dict_alloc();
tv_list_append_dict(retlist, dict);
sign_getinfo(sp, dict);
@@ -1374,14 +1349,13 @@ void sign_getlist(const char_u *name, list_T *retlist)
list_T *get_buffer_signs(buf_T *buf)
FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
- signlist_T *sign;
- dict_T *d;
+ signlist_T *sign;
+ dict_T *d;
list_T *const l = tv_list_alloc(kListLenMayKnow);
FOR_ALL_SIGNS_IN_BUF(buf, sign) {
- if ((d = sign_get_info(sign)) != NULL) {
- tv_list_append_dict(l, d);
- }
+ d = sign_get_info(sign);
+ tv_list_append_dict(l, d);
}
return l;
}
@@ -1394,21 +1368,16 @@ static void sign_get_placed_in_buf(
const char_u *sign_group,
list_T *retlist)
{
- dict_T *d;
- list_T *l;
- signlist_T *sign;
- dict_T *sdict;
+ dict_T *d;
+ list_T *l;
+ signlist_T *sign;
- if ((d = tv_dict_alloc()) == NULL) {
- return;
- }
+ d = tv_dict_alloc();
tv_list_append_dict(retlist, d);
tv_dict_add_nr(d, S_LEN("bufnr"), (long)buf->b_fnum);
- if ((l = tv_list_alloc(kListLenMayKnow)) == NULL) {
- return;
- }
+ l = tv_list_alloc(kListLenMayKnow);
tv_dict_add_list(d, S_LEN("signs"), l);
FOR_ALL_SIGNS_IN_BUF(buf, sign) {
@@ -1419,9 +1388,7 @@ static void sign_get_placed_in_buf(
|| (sign_id == 0 && lnum == sign->lnum)
|| (lnum == 0 && sign_id == sign->id)
|| (lnum == sign->lnum && sign_id == sign->id)) {
- if ((sdict = sign_get_info(sign)) != NULL) {
- tv_list_append_dict(l, sdict);
- }
+ tv_list_append_dict(l, sign_get_info(sign));
}
}
}
@@ -1448,21 +1415,6 @@ void sign_get_placed(
}
}
-# if defined(FEAT_SIGN_ICONS) || defined(PROTO)
-/// Allocate the icons. Called when the GUI has started. Allows defining
-/// signs before it starts.
-void sign_gui_started(void)
-{
- sign_T *sp;
-
- for (sp = first_sign; sp != NULL; sp = sp->sn_next) {
- if (sp->sn_icon != NULL) {
- sp->sn_image = gui_mch_register_sign(sp->sn_icon);
- }
- }
-}
-# endif
-
/// List one sign.
static void sign_list_defined(sign_T *sp)
{
@@ -1566,22 +1518,6 @@ char_u * sign_get_text(int typenr)
return NULL;
}
-# if defined(FEAT_SIGN_ICONS) || defined(PROTO)
-void * sign_get_image(
- int typenr // the attribute which may have a sign
-)
-{
- sign_T *sp;
-
- for (sp = first_sign; sp != NULL; sp = sp->sn_next) {
- if (sp->sn_typenr == typenr) {
- return sp->sn_image;
- }
- }
- return NULL;
-}
-# endif
-
/// Undefine/free all signs.
void free_signs(void)
{
diff --git a/src/nvim/spell.c b/src/nvim/spell.c
index 6fd22a6537..8d800843f8 100644
--- a/src/nvim/spell.c
+++ b/src/nvim/spell.c
@@ -84,6 +84,7 @@
#include "nvim/ascii.h"
#include "nvim/spell.h"
#include "nvim/buffer.h"
+#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/edit.h"
@@ -1626,7 +1627,7 @@ static void spell_load_lang(char_u *lang)
if (starting) {
// Prompt the user at VimEnter if spell files are missing. #3027
// Plugins aren't loaded yet, so spellfile.vim cannot handle this case.
- char autocmd_buf[128] = { 0 };
+ char autocmd_buf[512] = { 0 };
snprintf(autocmd_buf, sizeof(autocmd_buf),
"autocmd VimEnter * call spellfile#LoadFile('%s')|set spell",
lang);
@@ -1807,9 +1808,11 @@ void count_common_word(slang_T *lp, char_u *word, int len, int count)
char_u buf[MAXWLEN];
char_u *p;
- if (len == -1)
+ if (len == -1) {
p = word;
- else {
+ } else if (len >= MAXWLEN) {
+ return;
+ } else {
STRLCPY(buf, word, len + 1);
p = buf;
}
@@ -2616,7 +2619,7 @@ static bool spell_mb_isword_class(int cl, win_T *wp)
if (wp->w_s->b_cjk)
// East Asian characters are not considered word characters.
return cl == 2 || cl == 0x2800;
- return cl >= 2 && cl != 0x2070 && cl != 0x2080;
+ return cl >= 2 && cl != 0x2070 && cl != 0x2080 && cl != 3;
}
// Returns true if "p" points to a word character.
@@ -3262,7 +3265,7 @@ static void spell_suggest_file(suginfo_T *su, char_u *fname)
char_u cword[MAXWLEN];
// Open the file.
- fd = mch_fopen((char *)fname, "r");
+ fd = os_fopen((char *)fname, "r");
if (fd == NULL) {
EMSG2(_(e_notopen), fname);
return;
@@ -4500,7 +4503,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so
sp->ts_state = STATE_SWAP3;
break;
}
- if (c2 != NUL && TRY_DEEPER(su, stack, depth, SCORE_SWAP)) {
+ if (TRY_DEEPER(su, stack, depth, SCORE_SWAP)) {
go_deeper(stack, depth, SCORE_SWAP);
#ifdef DEBUG_TRIEWALK
snprintf(changename[depth], sizeof(changename[0]),
@@ -5283,7 +5286,7 @@ add_sound_suggest (
}
// Go over the list of good words that produce this soundfold word
- nrline = ml_get_buf(slang->sl_sugbuf, (linenr_T)(sfwordnr + 1), FALSE);
+ nrline = ml_get_buf(slang->sl_sugbuf, (linenr_T)sfwordnr + 1, false);
orgnr = 0;
while (*nrline != NUL) {
// The wordnr was stored in a minimal nr of bytes as an offset to the
diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c
index 5f5f74cf2e..405e390589 100644
--- a/src/nvim/spellfile.c
+++ b/src/nvim/spellfile.c
@@ -586,7 +586,7 @@ spell_load_file (
int c = 0;
int res;
- fd = mch_fopen((char *)fname, "r");
+ fd = os_fopen((char *)fname, "r");
if (fd == NULL) {
if (!silent)
EMSG2(_(e_notopen), fname);
@@ -885,9 +885,10 @@ void suggest_load_files(void)
continue;
}
STRCPY(dotp, ".sug");
- fd = mch_fopen((char *)slang->sl_fname, "r");
- if (fd == NULL)
+ fd = os_fopen((char *)slang->sl_fname, "r");
+ if (fd == NULL) {
goto nextone;
+ }
// <SUGHEADER>: <fileID> <versionnr> <timestamp>
for (i = 0; i < VIMSUGMAGICL; ++i)
@@ -1984,7 +1985,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname)
char_u *sofoto = NULL; // SOFOTO value
// Open the file.
- fd = mch_fopen((char *)fname, "r");
+ fd = os_fopen((char *)fname, "r");
if (fd == NULL) {
EMSG2(_(e_notopen), fname);
return NULL;
@@ -3019,7 +3020,7 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile)
int duplicate = 0;
// Open the file.
- fd = mch_fopen((char *)fname, "r");
+ fd = os_fopen((char *)fname, "r");
if (fd == NULL) {
EMSG2(_(e_notopen), fname);
return FAIL;
@@ -3539,7 +3540,7 @@ static int spell_read_wordfile(spellinfo_T *spin, char_u *fname)
int regionmask;
// Open the file.
- fd = mch_fopen((char *)fname, "r");
+ fd = os_fopen((char *)fname, "r");
if (fd == NULL) {
EMSG2(_(e_notopen), fname);
return FAIL;
@@ -4185,7 +4186,7 @@ static int write_vim_spell(spellinfo_T *spin, char_u *fname)
int retval = OK;
int regionmask;
- FILE *fd = mch_fopen((char *)fname, "w");
+ FILE *fd = os_fopen((char *)fname, "w");
if (fd == NULL) {
EMSG2(_(e_notopen), fname);
return FAIL;
@@ -4986,7 +4987,7 @@ static int offset2bytes(int nr, char_u *buf)
static void sug_write(spellinfo_T *spin, char_u *fname)
{
// Create the file. Note that an existing file is silently overwritten!
- FILE *fd = mch_fopen((char *)fname, "w");
+ FILE *fd = os_fopen((char *)fname, "w");
if (fd == NULL) {
EMSG2(_(e_notopen), fname);
return;
@@ -5365,7 +5366,7 @@ spell_add_word (
if (bad || undo) {
// When the word appears as good word we need to remove that one,
// since its flags sort before the one with WF_BANNED.
- fd = mch_fopen((char *)fname, "r");
+ fd = os_fopen((char *)fname, "r");
if (fd != NULL) {
while (!vim_fgets(line, MAXWLEN * 2, fd)) {
fpos = fpos_next;
@@ -5376,7 +5377,7 @@ spell_add_word (
// the start of the line. Mixing reading and writing
// doesn't work for all systems, close the file first.
fclose(fd);
- fd = mch_fopen((char *)fname, "r+");
+ fd = os_fopen((char *)fname, "r+");
if (fd == NULL) {
break;
}
@@ -5399,7 +5400,7 @@ spell_add_word (
}
if (!undo) {
- fd = mch_fopen((char *)fname, "a");
+ fd = os_fopen((char *)fname, "a");
if (fd == NULL && new_spf) {
char_u *p;
@@ -5415,7 +5416,7 @@ spell_add_word (
*p = NUL;
os_mkdir((char *)fname, 0755);
*p = c;
- fd = mch_fopen((char *)fname, "a");
+ fd = os_fopen((char *)fname, "a");
}
}
diff --git a/src/nvim/state.c b/src/nvim/state.c
index e6eeaac8d0..7c7d035366 100644
--- a/src/nvim/state.c
+++ b/src/nvim/state.c
@@ -143,7 +143,7 @@ char *get_mode(void)
}
if (ins_compl_active()) {
buf[1] = 'c';
- } else if (ctrl_x_mode == 1) {
+ } else if (ctrl_x_mode_not_defined_yet()) {
buf[1] = 'x';
}
}
diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c
index 418756abc4..4fa70c0684 100644
--- a/src/nvim/syntax.c
+++ b/src/nvim/syntax.c
@@ -14,6 +14,7 @@
#include "nvim/vim.h"
#include "nvim/ascii.h"
+#include "nvim/api/private/helpers.h"
#include "nvim/syntax.h"
#include "nvim/charset.h"
#include "nvim/cursor_shape.h"
@@ -75,6 +76,8 @@ struct hl_group {
uint8_t *sg_rgb_fg_name; ///< RGB foreground color name
uint8_t *sg_rgb_bg_name; ///< RGB background color name
uint8_t *sg_rgb_sp_name; ///< RGB special color name
+
+ int sg_blend; ///< blend level (0-100 inclusive), -1 if unset
};
/// \addtogroup SG_SET
@@ -348,7 +351,7 @@ static reg_extmatch_T *next_match_extmatch = NULL;
/*
* A state stack is an array of integers or stateitem_T, stored in a
- * garray_T. A state stack is invalid if it's itemsize entry is zero.
+ * garray_T. A state stack is invalid if its itemsize entry is zero.
*/
#define INVALID_STATE(ssp) ((ssp)->ga_itemsize == 0)
#define VALID_STATE(ssp) ((ssp)->ga_itemsize != 0)
@@ -997,6 +1000,7 @@ static void syn_stack_free_block(synblock_T *block)
clear_syn_state(p);
}
XFREE_CLEAR(block->b_sst_array);
+ block->b_sst_first = NULL;
block->b_sst_len = 0;
}
}
@@ -1108,9 +1112,6 @@ static void syn_stack_apply_changes_block(synblock_T *block, buf_T *buf)
synstate_T *p, *prev, *np;
linenr_T n;
- if (block->b_sst_array == NULL) /* nothing to do */
- return;
-
prev = NULL;
for (p = block->b_sst_first; p != NULL; ) {
if (p->sst_lnum + block->b_syn_sync_linebreaks > buf->b_mod_top) {
@@ -1158,8 +1159,9 @@ static int syn_stack_cleanup(void)
int dist;
int retval = FALSE;
- if (syn_block->b_sst_array == NULL || syn_block->b_sst_first == NULL)
+ if (syn_block->b_sst_first == NULL) {
return retval;
+ }
/* Compute normal distance between non-displayed entries. */
if (syn_block->b_sst_len <= Rows)
@@ -2923,8 +2925,9 @@ static int syn_regexec(regmmatch_T *rmp, linenr_T lnum, colnr_T col, syn_time_T
if (r > 0)
++st->match;
}
- if (timed_out) {
+ if (timed_out && !syn_win->w_s->b_syn_slow) {
syn_win->w_s->b_syn_slow = true;
+ MSG(_("'redrawtime' exceeded, syntax highlighting disabled"));
}
if (r > 0) {
@@ -3124,11 +3127,11 @@ static void syn_cmd_iskeyword(exarg_T *eap, int syncing)
arg = skipwhite(arg);
if (*arg == NUL) {
MSG_PUTS("\n");
- MSG_PUTS(_("syntax iskeyword "));
if (curwin->w_s->b_syn_isk != empty_option) {
+ MSG_PUTS(_("syntax iskeyword "));
msg_outtrans(curwin->w_s->b_syn_isk);
} else {
- msg_outtrans((char_u *)"not set");
+ msg_outtrans((char_u *)_("syntax iskeyword not set"));
}
} else {
if (STRNICMP(arg, "clear", 5) == 0) {
@@ -3606,7 +3609,7 @@ syn_list_one(
continue;
}
- (void)syn_list_header(did_header, 999, id);
+ (void)syn_list_header(did_header, 0, id, true);
did_header = true;
last_matchgroup = 0;
if (spp->sp_type == SPTYPE_MATCH) {
@@ -3655,7 +3658,7 @@ syn_list_one(
/* list the link, if there is one */
if (HL_TABLE()[id - 1].sg_link && (did_header || link_only) && !got_int) {
- (void)syn_list_header(did_header, 999, id);
+ (void)syn_list_header(did_header, 0, id, true);
msg_puts_attr("links to", attr);
msg_putchar(' ');
msg_outtrans(HL_TABLE()[HL_TABLE()[id - 1].sg_link - 1].sg_name);
@@ -3797,7 +3800,6 @@ static bool syn_list_keywords(
const int attr
)
{
- int outlen;
int prev_contained = 0;
const int16_t *prev_next_list = NULL;
const int16_t *prev_cont_in_list = NULL;
@@ -3815,17 +3817,20 @@ static bool syn_list_keywords(
todo--;
for (keyentry_T *kp = HI2KE(hi); kp != NULL && !got_int; kp = kp->ke_next) {
if (kp->k_syn.id == id) {
+ int outlen = 0;
+ bool force_newline = false;
if (prev_contained != (kp->flags & HL_CONTAINED)
|| prev_skipnl != (kp->flags & HL_SKIPNL)
|| prev_skipwhite != (kp->flags & HL_SKIPWHITE)
|| prev_skipempty != (kp->flags & HL_SKIPEMPTY)
|| prev_cont_in_list != kp->k_syn.cont_in_list
- || prev_next_list != kp->next_list)
- outlen = 9999;
- else
+ || prev_next_list != kp->next_list) {
+ force_newline = true;
+ } else {
outlen = (int)STRLEN(kp->keyword);
- /* output "contained" and "nextgroup" on each line */
- if (syn_list_header(did_header, outlen, id)) {
+ }
+ // output "contained" and "nextgroup" on each line
+ if (syn_list_header(did_header, outlen, id, force_newline)) {
prev_contained = 0;
prev_next_list = NULL;
prev_cont_in_list = NULL;
@@ -5968,6 +5973,10 @@ static const char *highlight_init_both[] = {
"default link Whitespace NonText",
"default link MsgSeparator StatusLine",
"default link NormalFloat Pmenu",
+ "RedrawDebugNormal cterm=reverse gui=reverse",
+ "RedrawDebugClear ctermbg=Yellow guibg=Yellow",
+ "RedrawDebugComposed ctermbg=Green guibg=Green",
+ "RedrawDebugRecompose ctermbg=Red guibg=Red",
NULL
};
@@ -6465,6 +6474,7 @@ void do_highlight(const char *line, const bool forceit, const bool init)
int id;
int idx;
struct hl_group item_before;
+ bool did_change = false;
bool dodefault = false;
bool doclear = false;
bool dolink = false;
@@ -6821,18 +6831,23 @@ void do_highlight(const char *line, const bool forceit, const bool init)
}
}
} else if (strcmp(key, "GUIFG") == 0) {
+ char_u **const namep = &HL_TABLE()[idx].sg_rgb_fg_name;
+
if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) {
if (!init) {
HL_TABLE()[idx].sg_set |= SG_GUI;
}
- xfree(HL_TABLE()[idx].sg_rgb_fg_name);
- if (strcmp(arg, "NONE")) {
- HL_TABLE()[idx].sg_rgb_fg_name = (char_u *)xstrdup((char *)arg);
- HL_TABLE()[idx].sg_rgb_fg = name_to_color((const char_u *)arg);
- } else {
- HL_TABLE()[idx].sg_rgb_fg_name = NULL;
- HL_TABLE()[idx].sg_rgb_fg = -1;
+ if (*namep == NULL || STRCMP(*namep, arg) != 0) {
+ xfree(*namep);
+ if (strcmp(arg, "NONE") != 0) {
+ *namep = (char_u *)xstrdup(arg);
+ HL_TABLE()[idx].sg_rgb_fg = name_to_color((char_u *)arg);
+ } else {
+ *namep = NULL;
+ HL_TABLE()[idx].sg_rgb_fg = -1;
+ }
+ did_change = true;
}
}
@@ -6840,18 +6855,23 @@ void do_highlight(const char *line, const bool forceit, const bool init)
normal_fg = HL_TABLE()[idx].sg_rgb_fg;
}
} else if (STRCMP(key, "GUIBG") == 0) {
+ char_u **const namep = &HL_TABLE()[idx].sg_rgb_bg_name;
+
if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) {
if (!init) {
HL_TABLE()[idx].sg_set |= SG_GUI;
}
- xfree(HL_TABLE()[idx].sg_rgb_bg_name);
- if (STRCMP(arg, "NONE") != 0) {
- HL_TABLE()[idx].sg_rgb_bg_name = (char_u *)xstrdup((char *)arg);
- HL_TABLE()[idx].sg_rgb_bg = name_to_color((const char_u *)arg);
- } else {
- HL_TABLE()[idx].sg_rgb_bg_name = NULL;
- HL_TABLE()[idx].sg_rgb_bg = -1;
+ if (*namep == NULL || STRCMP(*namep, arg) != 0) {
+ xfree(*namep);
+ if (STRCMP(arg, "NONE") != 0) {
+ *namep = (char_u *)xstrdup(arg);
+ HL_TABLE()[idx].sg_rgb_bg = name_to_color((char_u *)arg);
+ } else {
+ *namep = NULL;
+ HL_TABLE()[idx].sg_rgb_bg = -1;
+ }
+ did_change = true;
}
}
@@ -6859,18 +6879,23 @@ void do_highlight(const char *line, const bool forceit, const bool init)
normal_bg = HL_TABLE()[idx].sg_rgb_bg;
}
} else if (strcmp(key, "GUISP") == 0) {
+ char_u **const namep = &HL_TABLE()[idx].sg_rgb_sp_name;
+
if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) {
if (!init) {
HL_TABLE()[idx].sg_set |= SG_GUI;
}
- xfree(HL_TABLE()[idx].sg_rgb_sp_name);
- if (strcmp(arg, "NONE") != 0) {
- HL_TABLE()[idx].sg_rgb_sp_name = (char_u *)xstrdup((char *)arg);
- HL_TABLE()[idx].sg_rgb_sp = name_to_color((const char_u *)arg);
- } else {
- HL_TABLE()[idx].sg_rgb_sp_name = NULL;
- HL_TABLE()[idx].sg_rgb_sp = -1;
+ if (*namep == NULL || STRCMP(*namep, arg) != 0) {
+ xfree(*namep);
+ if (strcmp(arg, "NONE") != 0) {
+ *namep = (char_u *)xstrdup(arg);
+ HL_TABLE()[idx].sg_rgb_sp = name_to_color((char_u *)arg);
+ } else {
+ *namep = NULL;
+ HL_TABLE()[idx].sg_rgb_sp = -1;
+ }
+ did_change = true;
}
}
@@ -6879,6 +6904,12 @@ void do_highlight(const char *line, const bool forceit, const bool init)
}
} else if (strcmp(key, "START") == 0 || strcmp(key, "STOP") == 0) {
// Ignored for now
+ } else if (strcmp(key, "BLEND") == 0) {
+ if (strcmp(arg, "NONE") != 0) {
+ HL_TABLE()[idx].sg_blend = strtol(arg, NULL, 10);
+ } else {
+ HL_TABLE()[idx].sg_blend = -1;
+ }
} else {
emsgf(_("E423: Illegal argument: %s"), key_start);
error = true;
@@ -6926,9 +6957,15 @@ void do_highlight(const char *line, const bool forceit, const bool init)
// Only call highlight_changed() once, after a sequence of highlight
// commands, and only if an attribute actually changed
- if (memcmp(&HL_TABLE()[idx], &item_before, sizeof(item_before)) != 0
+ if ((did_change
+ || memcmp(&HL_TABLE()[idx], &item_before, sizeof(item_before)) != 0)
&& !did_highlight_changed) {
- redraw_all_later(NOT_VALID);
+ // Do not trigger a redraw when highlighting is changed while
+ // redrawing. This may happen when evaluating 'statusline' changes the
+ // StatusLine group.
+ if (!updating_screen) {
+ redraw_all_later(NOT_VALID);
+ }
need_highlight_changed = true;
}
}
@@ -6993,6 +7030,7 @@ static void highlight_clear(int idx)
XFREE_CLEAR(HL_TABLE()[idx].sg_rgb_fg_name);
XFREE_CLEAR(HL_TABLE()[idx].sg_rgb_bg_name);
XFREE_CLEAR(HL_TABLE()[idx].sg_rgb_sp_name);
+ HL_TABLE()[idx].sg_blend = -1;
// Clear the script ID only when there is no link, since that is not
// cleared.
if (HL_TABLE()[idx].sg_link == 0) {
@@ -7013,6 +7051,10 @@ static void highlight_list_one(const int id)
struct hl_group *const sgp = &HL_TABLE()[id - 1]; // index is ID minus one
bool didh = false;
+ if (message_filtered(sgp->sg_name)) {
+ return;
+ }
+
didh = highlight_list_arg(id, didh, LIST_ATTR,
sgp->sg_cterm, NULL, "cterm");
didh = highlight_list_arg(id, didh, LIST_INT,
@@ -7029,8 +7071,11 @@ static void highlight_list_one(const int id)
didh = highlight_list_arg(id, didh, LIST_STRING,
0, sgp->sg_rgb_sp_name, "guisp");
+ didh = highlight_list_arg(id, didh, LIST_INT,
+ sgp->sg_blend+1, NULL, "blend");
+
if (sgp->sg_link && !got_int) {
- (void)syn_list_header(didh, 9999, id);
+ (void)syn_list_header(didh, 0, id, true);
didh = true;
msg_puts_attr("links to", HL_ATTR(HLF_D));
msg_putchar(' ');
@@ -7075,7 +7120,8 @@ static bool highlight_list_arg(
}
}
- (void)syn_list_header(didh, (int)(vim_strsize(ts) + STRLEN(name) + 1), id);
+ (void)syn_list_header(didh, (int)(vim_strsize(ts) + STRLEN(name) + 1), id,
+ false);
didh = true;
if (!got_int) {
if (*name != NUL) {
@@ -7193,12 +7239,14 @@ const char *highlight_color(const int id, const char *const what,
/// @param did_header did header already
/// @param outlen length of string that comes
/// @param id highlight group id
+/// @param force_newline always start a new line
/// @return true when started a new line.
static bool syn_list_header(const bool did_header, const int outlen,
- const int id)
+ const int id, bool force_newline)
{
int endcol = 19;
bool newline = true;
+ bool adjust = true;
if (!did_header) {
msg_putchar('\n');
@@ -7207,7 +7255,10 @@ static bool syn_list_header(const bool did_header, const int outlen,
}
msg_outtrans(HL_TABLE()[id - 1].sg_name);
endcol = 15;
- } else if (msg_col + outlen + 1 >= Columns) {
+ } else if ((ui_has(kUIMessages) || msg_silent) && !force_newline) {
+ msg_putchar(' ');
+ adjust = false;
+ } else if (msg_col + outlen + 1 >= Columns || force_newline) {
msg_putchar('\n');
if (got_int) {
return true;
@@ -7218,12 +7269,14 @@ static bool syn_list_header(const bool did_header, const int outlen,
}
}
- if (msg_col >= endcol) /* output at least one space */
- endcol = msg_col + 1;
- if (Columns <= endcol) /* avoid hang for tiny window */
- endcol = Columns - 1;
+ if (adjust) {
+ if (msg_col >= endcol) {
+ // output at least one space
+ endcol = msg_col + 1;
+ }
- msg_advance(endcol);
+ msg_advance(endcol);
+ }
/* Show "xxx" with the attributes. */
if (!did_header) {
@@ -7252,6 +7305,7 @@ static void set_hl_attr(int idx)
at_en.rgb_fg_color = sgp->sg_rgb_fg_name ? sgp->sg_rgb_fg : -1;
at_en.rgb_bg_color = sgp->sg_rgb_bg_name ? sgp->sg_rgb_bg : -1;
at_en.rgb_sp_color = sgp->sg_rgb_sp_name ? sgp->sg_rgb_sp : -1;
+ at_en.hl_blend = sgp->sg_blend;
sgp->sg_attr = hl_get_syn_attr(idx+1, at_en);
@@ -7377,6 +7431,7 @@ static int syn_add_group(char_u *name)
hlgp->sg_rgb_bg = -1;
hlgp->sg_rgb_fg = -1;
hlgp->sg_rgb_sp = -1;
+ hlgp->sg_blend = -1;
hlgp->sg_name_u = vim_strsave_up(name);
return highlight_ga.ga_len; /* ID is index plus one */
@@ -7476,6 +7531,12 @@ void highlight_changed(void)
highlight_attr[hlf] = hl_get_ui_attr(hlf, final_id,
hlf == (int)HLF_INACTIVE);
+
+ if (highlight_attr[hlf] != highlight_attr_last[hlf]) {
+ ui_call_hl_group_set(cstr_as_string((char *)hlf_names[hlf]),
+ highlight_attr[hlf]);
+ highlight_attr_last[hlf] = highlight_attr[hlf];
+ }
}
/* Setup the user highlights
diff --git a/src/nvim/tag.c b/src/nvim/tag.c
index 6e883a1c3d..88676abc2e 100644
--- a/src/nvim/tag.c
+++ b/src/nvim/tag.c
@@ -1303,8 +1303,9 @@ find_tags (
}
}
- if ((fp = mch_fopen((char *)tag_fname, "r")) == NULL)
+ if ((fp = os_fopen((char *)tag_fname, "r")) == NULL) {
continue;
+ }
if (p_verbose >= 5) {
verbose_enter();
@@ -2235,8 +2236,13 @@ parse_match (
p = tagp->command;
if (find_extra(&p) == OK) {
tagp->command_end = p;
- p += 2; /* skip ";\"" */
- if (*p++ == TAB)
+ if (p > tagp->command && p[-1] == '|') {
+ tagp->command_end = p - 1; // drop trailing bar
+ } else {
+ tagp->command_end = p;
+ }
+ p += 2; // skip ";\""
+ if (*p++ == TAB) {
while (ASCII_ISALPHA(*p)) {
if (STRNCMP(p, "kind:", 5) == 0) {
tagp->tagkind = p + 5;
@@ -2252,6 +2258,7 @@ parse_match (
break;
p = pt + 1;
}
+ }
}
if (tagp->tagkind != NULL) {
for (p = tagp->tagkind;
@@ -2676,22 +2683,30 @@ static int find_extra(char_u **pp)
{
char_u *str = *pp;
- /* Repeat for addresses separated with ';' */
+ // Repeat for addresses separated with ';'
for (;; ) {
- if (ascii_isdigit(*str))
+ if (ascii_isdigit(*str)) {
str = skipdigits(str);
- else if (*str == '/' || *str == '?') {
- str = skip_regexp(str + 1, *str, FALSE, NULL);
- if (*str != **pp)
+ } else if (*str == '/' || *str == '?') {
+ str = skip_regexp(str + 1, *str, false, NULL);
+ if (*str != **pp) {
str = NULL;
- else
- ++str;
- } else
- str = NULL;
+ } else {
+ str++;
+ }
+ } else {
+ // not a line number or search string, look for terminator.
+ str = (char_u *)strstr((char *)str, "|;\"");
+ if (str != NULL) {
+ str++;
+ break;
+ }
+ }
if (str == NULL || *str != ';'
- || !(ascii_isdigit(str[1]) || str[1] == '/' || str[1] == '?'))
+ || !(ascii_isdigit(str[1]) || str[1] == '/' || str[1] == '?')) {
break;
- ++str; /* skip ';' */
+ }
+ str++; // skip ';'
}
if (str != NULL && STRNCMP(str, ";\"", 2) == 0) {
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
index d8d529d0f6..5aeea3223b 100644
--- a/src/nvim/terminal.c
+++ b/src/nvim/terminal.c
@@ -53,6 +53,7 @@
#include "nvim/macros.h"
#include "nvim/mbyte.h"
#include "nvim/buffer.h"
+#include "nvim/change.h"
#include "nvim/ascii.h"
#include "nvim/getchar.h"
#include "nvim/ui.h"
@@ -1306,7 +1307,7 @@ static void refresh_screen(Terminal *term, buf_T *buf)
static void adjust_topline(Terminal *term, buf_T *buf, long added)
{
- FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ FOR_ALL_TAB_WINDOWS(tp, wp) {
if (wp->w_buffer == buf) {
linenr_T ml_end = buf->b_ml.ml_line_count;
bool following = ml_end == wp->w_cursor.lnum + added; // cursor at end?
diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile
index 7b1f0f59cc..8b43d91e25 100644
--- a/src/nvim/testdir/Makefile
+++ b/src/nvim/testdir/Makefile
@@ -32,14 +32,12 @@ NEW_TESTS_ALOT := test_alot_utf8 test_alot
NEW_TESTS_IN_ALOT := $(shell sed '/^source/ s/^source //;s/\.vim$$//' test_alot*.vim)
# Ignored tests.
# test_alot_latin: Nvim does not allow setting encoding.
-# test_arglist: ported to Lua, but kept for easier merging.
# test_autochdir: ported to Lua, but kept for easier merging.
# test_eval_func: used as include in old-style test (test_eval.in).
# test_listlbr: Nvim does not allow setting encoding.
# test_largefile: uses too much resources to run on CI.
NEW_TESTS_IGNORE := $(NEW_TESTS_IN_ALOT) $(NEW_TESTS_ALOT) \
test_alot_latin \
- test_arglist \
test_autochdir \
test_eval_func \
test_listlbr \
@@ -101,23 +99,24 @@ RM_ON_RUN := test.out X* viminfo
RM_ON_START := test.ok
RUN_VIM := $(TOOL) $(NVIM_PRG) -u unix.vim -U NONE -i viminfo --headless --noplugin -s dotest.in
+CLEAN_FILES := *.out \
+ *.failed \
+ *.res \
+ *.rej \
+ *.orig \
+ *.tlog \
+ test.log \
+ messages \
+ $(RM_ON_RUN) \
+ $(RM_ON_START) \
+ valgrind.* \
+ .*.swp \
+ .*.swo \
+ .gdbinit \
+ $(TMPDIR) \
+ del
clean:
- -rm -rf *.out \
- *.failed \
- *.res \
- *.rej \
- *.orig \
- *.tlog \
- test.log \
- messages \
- $(RM_ON_RUN) \
- $(RM_ON_START) \
- valgrind.* \
- .*.swp \
- .*.swo \
- .gdbinit \
- $(TMPDIR) \
- del
+ $(RM) -rf $(CLEAN_FILES)
test1.out: .gdbinit test1.in
@echo "[OLDTEST-PREP] Running test1"
diff --git a/src/nvim/testdir/runnvim.vim b/src/nvim/testdir/runnvim.vim
index 396a3a6477..52e05cfbeb 100644
--- a/src/nvim/testdir/runnvim.vim
+++ b/src/nvim/testdir/runnvim.vim
@@ -20,7 +20,7 @@ function Main()
let results = jobwait([job], 5 * 60 * 1000)
" TODO(ZyX-I): Get colors
let screen = getline(1, '$')
- bwipeout!
+ bwipeout! " kills the job always.
let stringified_events = map(s:logger.d_events,
\'v:val[0] . ": " . ' .
\'join(map(v:val[1], '.
@@ -43,9 +43,6 @@ function Main()
\])
write
if results[0] != 0
- if results[0] != -1
- call jobstop(job)
- endif
cquit
else
qall
diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim
index aaddc90c99..c8161b1f9b 100644
--- a/src/nvim/testdir/runtest.vim
+++ b/src/nvim/testdir/runtest.vim
@@ -35,8 +35,11 @@ if &lines < 24 || &columns < 80
echoerr error
split test.log
$put =error
- w
- cquit
+ write
+ split messages
+ call append(line('$'), error)
+ write
+ qa!
endif
" Common with all tests on all systems.
@@ -121,7 +124,10 @@ func RunTheTest(test)
exe 'call ' . a:test
else
try
+ let s:test = a:test
+ au VimLeavePre * call EarlyExit(s:test)
exe 'call ' . a:test
+ au! VimLeavePre
catch /^\cskipped/
call add(s:messages, ' Skipped')
call add(s:skipped, 'SKIPPED ' . a:test . ': ' . substitute(v:exception, '^\S*\s\+', '', ''))
@@ -175,6 +181,15 @@ func AfterTheTest()
endif
endfunc
+func EarlyExit(test)
+ " It's OK for the test we use to test the quit detection.
+ if a:test != 'Test_zz_quit_detected()'
+ call add(v:errors, 'Test caused Vim to exit: ' . a:test)
+ endif
+
+ call FinishTesting()
+endfunc
+
" This function can be called by a test if it wants to abort testing.
func FinishTesting()
call AfterTheTest()
@@ -200,7 +215,11 @@ func FinishTesting()
write
endif
- let message = 'Executed ' . s:done . (s:done > 1 ? ' tests' : ' test')
+ if s:done == 0
+ let message = 'NO tests executed'
+ else
+ let message = 'Executed ' . s:done . (s:done > 1 ? ' tests' : ' test')
+ endif
echo message
call add(s:messages, message)
if s:fail > 0
@@ -244,7 +263,8 @@ else
endif
" Names of flaky tests.
-let s:flaky = [
+let s:flaky_tests = [
+ \ 'Test_cursorhold_insert()',
\ 'Test_exit_callback_interval()',
\ 'Test_oneshot()',
\ 'Test_out_cb()',
@@ -258,9 +278,11 @@ let s:flaky = [
\ 'Test_terminal_redir_file()',
\ 'Test_terminal_tmap()',
\ 'Test_with_partial_callback()',
- \ 'Test_lambda_with_timer()',
\ ]
+" Pattern indicating a common flaky test failure.
+let s:flaky_errors_re = 'StopVimInTerminal\|VerifyScreenDump'
+
" Locate Test_ functions and execute them.
redir @q
silent function /^Test_
@@ -285,7 +307,9 @@ for s:test in sort(s:tests)
" Repeat a flaky test. Give up when:
" - it fails again with the same message
" - it fails five times (with a different mesage)
- if len(v:errors) > 0 && index(s:flaky, s:test) >= 0
+ if len(v:errors) > 0
+ \ && (index(s:flaky_tests, s:test) >= 0
+ \ || v:errors[0] =~ s:flaky_errors_re)
while 1
call add(s:messages, 'Found errors in ' . s:test . ':')
call extend(s:messages, v:errors)
diff --git a/src/nvim/testdir/samples/memfile_test.c b/src/nvim/testdir/samples/memfile_test.c
index 3c8f108255..c71a5c8f40 100644
--- a/src/nvim/testdir/samples/memfile_test.c
+++ b/src/nvim/testdir/samples/memfile_test.c
@@ -70,7 +70,7 @@ test_mf_hash(void)
assert(mf_hash_find(&ht, key) == NULL);
/* allocate and add new item */
- item = (mf_hashitem_T *)lalloc_clear(sizeof(mf_hashtab_T), FALSE);
+ item = (mf_hashitem_T *)lalloc_clear(sizeof(*item), FALSE);
assert(item != NULL);
item->mhi_key = key;
mf_hash_add_item(&ht, item);
diff --git a/src/nvim/testdir/setup.vim b/src/nvim/testdir/setup.vim
index c75b00d5de..f79fb9e518 100644
--- a/src/nvim/testdir/setup.vim
+++ b/src/nvim/testdir/setup.vim
@@ -7,16 +7,19 @@ endif
let s:did_load = 1
" Align Nvim defaults to Vim.
-set sidescroll=0
-set directory^=.
-set undodir^=.
set backspace=
-set nrformats+=octal
-set nohidden smarttab noautoindent noautoread complete-=i noruler noshowcmd
-set listchars=eol:$
+set directory^=.
set fillchars=vert:\|,fold:-
-set shortmess-=F
set laststatus=1
+set listchars=eol:$
+set nohidden smarttab noautoindent noautoread complete-=i noruler noshowcmd
+set nrformats+=octal
+set shortmess-=F
+set sidescroll=0
+set tags=./tags,tags
+set undodir^=.
+set wildoptions=
+
" Prevent Nvim log from writing to stderr.
let $NVIM_LOG_FILE = exists($NVIM_LOG_FILE) ? $NVIM_LOG_FILE : 'Xnvim.log'
diff --git a/src/nvim/testdir/shared.vim b/src/nvim/testdir/shared.vim
index eb6798f353..6cc2d06a36 100644
--- a/src/nvim/testdir/shared.vim
+++ b/src/nvim/testdir/shared.vim
@@ -133,7 +133,11 @@ func s:kill_server(cmd)
endif
endfunc
-" Wait for up to a second for "expr" to become true.
+" Wait for up to a second for "expr" to become true. "expr" can be a
+" stringified expression to evaluate, or a funcref without arguments.
+"
+" A second argument can be used to specify a different timeout in msec.
+"
" Return time slept in milliseconds. With the +reltime feature this can be
" more than the actual waiting time. Without +reltime it can also be less.
func WaitFor(expr, ...)
@@ -144,8 +148,13 @@ func WaitFor(expr, ...)
else
let slept = 0
endif
+ if type(a:expr) == v:t_func
+ let Test = a:expr
+ else
+ let Test = {-> eval(a:expr) }
+ endif
for i in range(timeout / 10)
- if eval(a:expr)
+ if Test()
if has('reltime')
return float2nr(reltimefloat(reltime(start)) * 1000)
endif
@@ -156,7 +165,7 @@ func WaitFor(expr, ...)
endif
sleep 10m
endfor
- return timeout
+ throw 'WaitFor() timed out after ' . timeout . ' msec'
endfunc
" Wait for up to a given milliseconds.
@@ -188,12 +197,18 @@ func s:feedkeys(timer)
endfunc
" Get the command to run Vim, with -u NONE and --headless arguments.
+" If there is an argument use it instead of "NONE".
" Returns an empty string on error.
-func GetVimCommand()
+func GetVimCommand(...)
+ if a:0 == 0
+ let name = 'NONE'
+ else
+ let name = a:1
+ endif
let cmd = v:progpath
- let cmd = substitute(cmd, '-u \f\+', '-u NONE', '')
- if cmd !~ '-u NONE'
- let cmd = cmd . ' -u NONE'
+ let cmd = substitute(cmd, '-u \f\+', '-u ' . name, '')
+ if cmd !~ '-u '. name
+ let cmd = cmd . ' -u ' . name
endif
let cmd .= ' --headless -i NONE'
let cmd = substitute(cmd, 'VIMRUNTIME=.*VIMRUNTIME;', '', '')
@@ -246,3 +261,7 @@ func! Screenline(lnum)
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/test49.vim b/src/nvim/testdir/test49.vim
index 467abcd9b9..837e55ebca 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: 2016 Feb 07
+" Last Change: 2019 May 24
"-------------------------------------------------------------------------------
" Test environment {{{1
@@ -9005,5 +9005,4 @@ Xcheck 50443995
"-------------------------------------------------------------------------------
" Modelines {{{1
" vim: ts=8 sw=4 tw=80 fdm=marker
-" vim: fdt=substitute(substitute(foldtext(),\ '\\%(^+--\\)\\@<=\\(\\s*\\)\\(.\\{-}\\)\:\ \\%(\"\ \\)\\=\\(Test\ \\d*\\)\:\\s*',\ '\\3\ (\\2)\:\ \\1',\ \"\"),\ '\\(Test\\s*\\)\\(\\d\\)\\D\\@=',\ '\\1\ \\2',\ "")
"-------------------------------------------------------------------------------
diff --git a/src/nvim/testdir/test_arabic.vim b/src/nvim/testdir/test_arabic.vim
index 17e925ee7f..d67f875f97 100644
--- a/src/nvim/testdir/test_arabic.vim
+++ b/src/nvim/testdir/test_arabic.vim
@@ -524,54 +524,6 @@ func Test_shape_final()
bwipe!
endfunc
-func Test_shape_final_to_medial()
- new
- set arabicshape
-
- " Shaping arabic {testchar} arabic Tests chg_c_f2m().
- " This does not test much...
- " pair[0] = testchar, pair[1] = current-result
- for pair in [[s:a_f_YEH_HAMZA, s:a_f_BEH],
- \[s:a_f_WAW_HAMZA, s:a_s_BEH],
- \[s:a_f_ALEF, s:a_s_BEH],
- \[s:a_f_TEH_MARBUTA, s:a_s_BEH],
- \[s:a_f_DAL, s:a_s_BEH],
- \[s:a_f_THAL, s:a_s_BEH],
- \[s:a_f_REH, s:a_s_BEH],
- \[s:a_f_ZAIN, s:a_s_BEH],
- \[s:a_f_WAW, s:a_s_BEH],
- \[s:a_f_ALEF_MAKSURA, s:a_s_BEH],
- \[s:a_f_BEH, s:a_f_BEH],
- \[s:a_f_TEH, s:a_f_BEH],
- \[s:a_f_THEH, s:a_f_BEH],
- \[s:a_f_JEEM, s:a_f_BEH],
- \[s:a_f_HAH, s:a_f_BEH],
- \[s:a_f_KHAH, s:a_f_BEH],
- \[s:a_f_SEEN, s:a_f_BEH],
- \[s:a_f_SHEEN, s:a_f_BEH],
- \[s:a_f_SAD, s:a_f_BEH],
- \[s:a_f_DAD, s:a_f_BEH],
- \[s:a_f_TAH, s:a_f_BEH],
- \[s:a_f_ZAH, s:a_f_BEH],
- \[s:a_f_AIN, s:a_f_BEH],
- \[s:a_f_GHAIN, s:a_f_BEH],
- \[s:a_f_FEH, s:a_f_BEH],
- \[s:a_f_QAF, s:a_f_BEH],
- \[s:a_f_KAF, s:a_f_BEH],
- \[s:a_f_LAM, s:a_f_BEH],
- \[s:a_f_MEEM, s:a_f_BEH],
- \[s:a_f_NOON, s:a_f_BEH],
- \[s:a_f_HEH, s:a_f_BEH],
- \[s:a_f_YEH, s:a_f_BEH],
- \ ]
- call setline(1, ' ' . s:a_BEH . pair[0])
- call assert_equal([' ' . pair[1] . pair[0]], ScreenLines(1, 3))
- endfor
-
- set arabicshape&
- bwipe!
-endfunc
-
func Test_shape_combination_final()
new
set arabicshape
diff --git a/src/nvim/testdir/test_arglist.vim b/src/nvim/testdir/test_arglist.vim
index 6468819198..df72ff0a32 100644
--- a/src/nvim/testdir/test_arglist.vim
+++ b/src/nvim/testdir/test_arglist.vim
@@ -74,7 +74,6 @@ func Test_argadd()
call assert_equal(1, len(argv()))
call assert_equal('some file', get(argv(), 0, ''))
- call delete('Xargadd')
%argd
new
arga
@@ -122,10 +121,7 @@ func Test_argument()
call assert_equal(['d', 'c', 'b', 'a', 'c'], g:buffers)
- redir => result
- ar
- redir END
- call assert_true(result =~# 'a b \[c] d')
+ call assert_equal("\na b [c] d ", execute(':args'))
.argd
call assert_equal(['a', 'b', 'd'], argv())
@@ -154,6 +150,25 @@ func Test_argument()
let &hidden = save_hidden
+ let save_columns = &columns
+ let &columns = 79
+ exe 'args ' .. join(range(1, 81))
+ call assert_equal(join([
+ \ '',
+ \ '[1] 6 11 16 21 26 31 36 41 46 51 56 61 66 71 76 81 ',
+ \ '2 7 12 17 22 27 32 37 42 47 52 57 62 67 72 77 ',
+ \ '3 8 13 18 23 28 33 38 43 48 53 58 63 68 73 78 ',
+ \ '4 9 14 19 24 29 34 39 44 49 54 59 64 69 74 79 ',
+ \ '5 10 15 20 25 30 35 40 45 50 55 60 65 70 75 80 ',
+ \ ], "\n"),
+ \ execute('args'))
+
+ " No trailing newline with one item per row.
+ let long_arg = repeat('X', 81)
+ exe 'args ' .. long_arg
+ call assert_equal("\n[".long_arg.']', execute('args'))
+ let &columns = save_columns
+
" Setting argument list should fail when the current buffer has unsaved
" changes
%argd
@@ -171,6 +186,34 @@ func Test_argument()
call assert_fails('argument', 'E163:')
endfunc
+func Test_list_arguments()
+ " Clean the argument list
+ arga a | %argd
+
+ " four args half the screen width makes two lines with two columns
+ let aarg = repeat('a', &columns / 2 - 4)
+ let barg = repeat('b', &columns / 2 - 4)
+ let carg = repeat('c', &columns / 2 - 4)
+ let darg = repeat('d', &columns / 2 - 4)
+ exe 'argadd ' aarg barg carg darg
+
+ redir => result
+ args
+ redir END
+ call assert_match('\[' . aarg . '] \+' . carg . '\n' . barg . ' \+' . darg, trim(result))
+
+ " if one arg is longer than half the screen make one column
+ exe 'argdel' aarg
+ let aarg = repeat('a', &columns / 2 + 2)
+ exe '0argadd' aarg
+ redir => result
+ args
+ redir END
+ call assert_match(aarg . '\n\[' . barg . ']\n' . carg . '\n' . darg, trim(result))
+
+ %argdelete
+endfunc
+
func Test_args_with_quote()
" Only on Unix can a file name include a double quote.
if has('unix')
@@ -427,3 +470,19 @@ func Test_arg_all_expand()
call assert_equal('notexist Xx\ x runtest.vim', expand('##'))
call delete('Xx x')
endfunc
+
+func Test_large_arg()
+ " Argument longer or equal to the number of columns used to cause
+ " access to invalid memory.
+ exe 'argadd ' .repeat('x', &columns)
+ args
+endfunc
+
+func Test_argdo()
+ next! Xa.c Xb.c Xc.c
+ new
+ let l = []
+ argdo call add(l, expand('%'))
+ call assert_equal(['Xa.c', 'Xb.c', 'Xc.c'], l)
+ bwipe Xa.c Xb.c Xc.c
+endfunc
diff --git a/src/nvim/testdir/test_assert.vim b/src/nvim/testdir/test_assert.vim
new file mode 100644
index 0000000000..a4c8ce7e43
--- /dev/null
+++ b/src/nvim/testdir/test_assert.vim
@@ -0,0 +1,13 @@
+" Test that the methods used for testing work.
+
+func Test_assert_fails_in_try_block()
+ try
+ call assert_equal(0, assert_fails('throw "error"'))
+ endtry
+endfunc
+
+" Must be last.
+func Test_zz_quit_detected()
+ " Verify that if a test function ends Vim the test script detects this.
+ quit
+endfunc
diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim
index d66e237e45..b686d0292f 100644
--- a/src/nvim/testdir/test_autocmd.vim
+++ b/src/nvim/testdir/test_autocmd.vim
@@ -635,7 +635,8 @@ func Test_OptionSet()
" Cleanup
au! OptionSet
- for opt in ['nu', 'ai', 'acd', 'ar', 'bs', 'backup', 'cul', 'cp']
+ " set tags&
+ for opt in ['nu', 'ai', 'acd', 'ar', 'bs', 'backup', 'cul', 'cp', 'tags']
exe printf(":set %s&vim", opt)
endfor
call test_override('starting', 0)
@@ -1672,6 +1673,25 @@ func Test_ReadWrite_Autocmds()
call delete('test.out')
endfunc
+func Test_throw_in_BufWritePre()
+ new
+ call setline(1, ['one', 'two', 'three'])
+ call assert_false(filereadable('Xthefile'))
+ augroup throwing
+ au BufWritePre X* throw 'do not write'
+ augroup END
+ try
+ w Xthefile
+ catch
+ let caught = 1
+ endtry
+ call assert_equal(1, caught)
+ call assert_false(filereadable('Xthefile'))
+
+ bwipe!
+ au! throwing
+endfunc
+
func Test_FileChangedShell_reload()
if !has('unix')
return
diff --git a/src/nvim/testdir/test_bufline.vim b/src/nvim/testdir/test_bufline.vim
new file mode 100644
index 0000000000..b886e99506
--- /dev/null
+++ b/src/nvim/testdir/test_bufline.vim
@@ -0,0 +1,67 @@
+" Tests for setbufline() and getbufline()
+
+source shared.vim
+
+func Test_setbufline_getbufline()
+ new
+ let b = bufnr('%')
+ hide
+ call assert_equal(0, setbufline(b, 1, ['foo', 'bar']))
+ call assert_equal(['foo'], getbufline(b, 1))
+ call assert_equal(['bar'], getbufline(b, 2))
+ call assert_equal(['foo', 'bar'], getbufline(b, 1, 2))
+ exe "bd!" b
+ call assert_equal([], getbufline(b, 1, 2))
+
+ split Xtest
+ call setline(1, ['a', 'b', 'c'])
+ let b = bufnr('%')
+ wincmd w
+ call assert_equal(1, setbufline(b, 5, ['x']))
+ call assert_equal(1, setbufline(1234, 1, ['x']))
+ call assert_equal(0, setbufline(b, 4, ['d', 'e']))
+ call assert_equal(['c'], getbufline(b, 3))
+ call assert_equal(['d'], getbufline(b, 4))
+ call assert_equal(['e'], getbufline(b, 5))
+ call assert_equal([], getbufline(b, 6))
+ exe "bwipe! " . b
+endfunc
+
+func Test_setbufline_getbufline_fold()
+ split Xtest
+ setlocal foldmethod=expr foldexpr=0
+ let b = bufnr('%')
+ new
+ call assert_equal(0, setbufline(b, 1, ['foo', 'bar']))
+ call assert_equal(['foo'], getbufline(b, 1))
+ call assert_equal(['bar'], getbufline(b, 2))
+ call assert_equal(['foo', 'bar'], getbufline(b, 1, 2))
+ exe "bwipe!" b
+ bwipe!
+endfunc
+
+func Test_setbufline_getbufline_fold_tab()
+ split Xtest
+ setlocal foldmethod=expr foldexpr=0
+ let b = bufnr('%')
+ tab new
+ call assert_equal(0, setbufline(b, 1, ['foo', 'bar']))
+ call assert_equal(['foo'], getbufline(b, 1))
+ call assert_equal(['bar'], getbufline(b, 2))
+ call assert_equal(['foo', 'bar'], getbufline(b, 1, 2))
+ exe "bwipe!" b
+ bwipe!
+endfunc
+
+func Test_setline_startup()
+ let cmd = GetVimCommand('Xscript')
+ if cmd == ''
+ return
+ endif
+ call writefile(['call setline(1, "Hello")', 'silent w Xtest', 'q!'], 'Xscript')
+ call system(cmd)
+ call assert_equal(['Hello'], readfile('Xtest'))
+
+ call delete('Xscript')
+ call delete('Xtest')
+endfunc
diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim
index 75832a798c..4b333e444a 100644
--- a/src/nvim/testdir/test_cmdline.vim
+++ b/src/nvim/testdir/test_cmdline.vim
@@ -44,6 +44,42 @@ func Test_map_completion()
call assert_equal('"map <special> <nowait>', getreg(':'))
call feedkeys(":map <silent> <sp\<Tab>\<Home>\"\<CR>", 'xt')
call assert_equal('"map <silent> <special>', getreg(':'))
+
+ map ,f commaf
+ map ,g commaf
+ call feedkeys(":map ,\<Tab>\<Home>\"\<CR>", 'xt')
+ call assert_equal('"map ,f', getreg(':'))
+ call feedkeys(":map ,\<Tab>\<Tab>\<Home>\"\<CR>", 'xt')
+ call assert_equal('"map ,g', getreg(':'))
+ unmap ,f
+ unmap ,g
+
+ set cpo-=< cpo-=B cpo-=k
+ map <Left> left
+ call feedkeys(":map <L\<Tab>\<Home>\"\<CR>", 'xt')
+ call assert_equal('"map <Left>', getreg(':'))
+ unmap <Left>
+
+ " set cpo+=<
+ map <Left> left
+ call feedkeys(":map <L\<Tab>\<Home>\"\<CR>", 'xt')
+ call assert_equal('"map <Left>', getreg(':'))
+ unmap <Left>
+ set cpo-=<
+
+ set cpo+=B
+ map <Left> left
+ call feedkeys(":map <L\<Tab>\<Home>\"\<CR>", 'xt')
+ call assert_equal('"map <Left>', getreg(':'))
+ unmap <Left>
+ set cpo-=B
+
+ " set cpo+=k
+ map <Left> left
+ call feedkeys(":map <L\<Tab>\<Home>\"\<CR>", 'xt')
+ call assert_equal('"map <Left>', getreg(':'))
+ unmap <Left>
+ " set cpo-=k
endfunc
func Test_match_completion()
@@ -232,6 +268,15 @@ func Test_getcompletion()
let l = getcompletion('not', 'mapclear')
call assert_equal([], l)
+ let l = getcompletion('.', 'shellcmd')
+ call assert_equal(['./', '../'], l[0:1])
+ call assert_equal(-1, match(l[2:], '^\.\.\?/$'))
+ let root = has('win32') ? 'C:\\' : '/'
+ let l = getcompletion(root, 'shellcmd')
+ let expected = map(filter(glob(root . '*', 0, 1),
+ \ 'isdirectory(v:val) || executable(v:val)'), 'isdirectory(v:val) ? v:val . ''/'' : v:val')
+ call assert_equal(expected, l)
+
if has('cscope')
let l = getcompletion('', 'cscope')
let cmds = ['add', 'find', 'help', 'kill', 'reset', 'show']
@@ -273,8 +318,7 @@ func Test_getcompletion()
call assert_equal([], l)
" For others test if the name is recognized.
- let names = ['buffer', 'environment', 'file_in_path',
- \ 'mapping', 'shellcmd', 'tag', 'tag_listfiles', 'user']
+ let names = ['buffer', 'environment', 'file_in_path', 'mapping', 'tag', 'tag_listfiles', 'user']
if has('cmdline_hist')
call add(names, 'history')
endif
@@ -294,6 +338,7 @@ func Test_getcompletion()
endfor
call delete('Xtags')
+ set tags&
call assert_fails('call getcompletion("", "burp")', 'E475:')
endfunc
diff --git a/src/nvim/testdir/test_compiler.vim b/src/nvim/testdir/test_compiler.vim
index 4600a28da5..46c14d8bc3 100644
--- a/src/nvim/testdir/test_compiler.vim
+++ b/src/nvim/testdir/test_compiler.vim
@@ -33,9 +33,9 @@ endfunc
func Test_compiler_without_arg()
let a=split(execute('compiler'))
- call assert_match(expand('^.*runtime/compiler/ant.vim$'), a[0])
- call assert_match(expand('^.*runtime/compiler/bcc.vim$'), a[1])
- call assert_match(expand('^.*runtime/compiler/xmlwf.vim$'), a[-1])
+ 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_const.vim b/src/nvim/testdir/test_const.vim
new file mode 100644
index 0000000000..06062c5e58
--- /dev/null
+++ b/src/nvim/testdir/test_const.vim
@@ -0,0 +1,237 @@
+
+" Test for :const
+
+func s:noop()
+endfunc
+
+func Test_define_var_with_lock()
+ const i = 1
+ const f = 1.1
+ const s = 'vim'
+ const F = funcref('s:noop')
+ const l = [1, 2, 3]
+ const d = {'foo': 10}
+ if has('channel')
+ const j = test_null_job()
+ const c = test_null_channel()
+ endif
+ const b = v:true
+ const n = v:null
+
+ call assert_true(exists('i'))
+ call assert_true(exists('f'))
+ call assert_true(exists('s'))
+ call assert_true(exists('F'))
+ call assert_true(exists('l'))
+ call assert_true(exists('d'))
+ if has('channel')
+ call assert_true(exists('j'))
+ call assert_true(exists('c'))
+ endif
+ call assert_true(exists('b'))
+ call assert_true(exists('n'))
+
+ call assert_fails('let i = 1', 'E741:')
+ call assert_fails('let f = 1.1', 'E741:')
+ call assert_fails('let s = "vim"', 'E741:')
+ call assert_fails('let F = funcref("s:noop")', 'E741:')
+ call assert_fails('let l = [1, 2, 3]', 'E741:')
+ call assert_fails('let d = {"foo": 10}', 'E741:')
+ if has('channel')
+ call assert_fails('let j = test_null_job()', 'E741:')
+ call assert_fails('let c = test_null_channel()', 'E741:')
+ endif
+ call assert_fails('let b = v:true', 'E741:')
+ call assert_fails('let n = v:null', 'E741:')
+
+ " Unlet
+ unlet i
+ unlet f
+ unlet s
+ unlet F
+ unlet l
+ unlet d
+ if has('channel')
+ unlet j
+ unlet c
+ endif
+ unlet b
+ unlet n
+endfunc
+
+func Test_define_l_var_with_lock()
+ " With l: prefix
+ const l:i = 1
+ const l:f = 1.1
+ const l:s = 'vim'
+ const l:F = funcref('s:noop')
+ const l:l = [1, 2, 3]
+ const l:d = {'foo': 10}
+ if has('channel')
+ const l:j = test_null_job()
+ const l:c = test_null_channel()
+ endif
+ const l:b = v:true
+ const l:n = v:null
+
+ call assert_fails('let l:i = 1', 'E741:')
+ call assert_fails('let l:f = 1.1', 'E741:')
+ call assert_fails('let l:s = "vim"', 'E741:')
+ call assert_fails('let l:F = funcref("s:noop")', 'E741:')
+ call assert_fails('let l:l = [1, 2, 3]', 'E741:')
+ call assert_fails('let l:d = {"foo": 10}', 'E741:')
+ if has('channel')
+ call assert_fails('let l:j = test_null_job()', 'E741:')
+ call assert_fails('let l:c = test_null_channel()', 'E741:')
+ endif
+ call assert_fails('let l:b = v:true', 'E741:')
+ call assert_fails('let l:n = v:null', 'E741:')
+
+ " Unlet
+ unlet l:i
+ unlet l:f
+ unlet l:s
+ unlet l:F
+ unlet l:l
+ unlet l:d
+ if has('channel')
+ unlet l:j
+ unlet l:c
+ endif
+ unlet l:b
+ unlet l:n
+endfunc
+
+func Test_define_script_var_with_lock()
+ const s:x = 0
+ call assert_fails('let s:x = 1', 'E741:')
+ unlet s:x
+endfunc
+
+func Test_descructuring_with_lock()
+ const [a, b, c] = [1, 1.1, 'vim']
+
+ call assert_fails('let a = 1', 'E741:')
+ call assert_fails('let b = 1.1', 'E741:')
+ call assert_fails('let c = "vim"', 'E741:')
+
+ const [d; e] = [1, 1.1, 'vim']
+ call assert_fails('let d = 1', 'E741:')
+ call assert_fails('let e = [2.2, "a"]', 'E741:')
+endfunc
+
+func Test_cannot_modify_existing_variable()
+ let i = 1
+ let f = 1.1
+ let s = 'vim'
+ let F = funcref('s:noop')
+ let l = [1, 2, 3]
+ let d = {'foo': 10}
+ if has('channel')
+ let j = test_null_job()
+ let c = test_null_channel()
+ endif
+ let b = v:true
+ let n = v:null
+
+ call assert_fails('const i = 1', 'E995:')
+ call assert_fails('const f = 1.1', 'E995:')
+ call assert_fails('const s = "vim"', 'E995:')
+ call assert_fails('const F = funcref("s:noop")', 'E995:')
+ call assert_fails('const l = [1, 2, 3]', 'E995:')
+ call assert_fails('const d = {"foo": 10}', 'E995:')
+ if has('channel')
+ call assert_fails('const j = test_null_job()', 'E995:')
+ call assert_fails('const c = test_null_channel()', 'E995:')
+ endif
+ call assert_fails('const b = v:true', 'E995:')
+ call assert_fails('const n = v:null', 'E995:')
+ call assert_fails('const [i, f, s] = [1, 1.1, "vim"]', 'E995:')
+
+ const i2 = 1
+ const f2 = 1.1
+ const s2 = 'vim'
+ const F2 = funcref('s:noop')
+ const l2 = [1, 2, 3]
+ const d2 = {'foo': 10}
+ if has('channel')
+ const j2 = test_null_job()
+ const c2 = test_null_channel()
+ endif
+ const b2 = v:true
+ const n2 = v:null
+
+ call assert_fails('const i2 = 1', 'E995:')
+ call assert_fails('const f2 = 1.1', 'E995:')
+ call assert_fails('const s2 = "vim"', 'E995:')
+ call assert_fails('const F2 = funcref("s:noop")', 'E995:')
+ call assert_fails('const l2 = [1, 2, 3]', 'E995:')
+ call assert_fails('const d2 = {"foo": 10}', 'E995:')
+ if has('channel')
+ call assert_fails('const j2 = test_null_job()', 'E995:')
+ call assert_fails('const c2 = test_null_channel()', 'E995:')
+ endif
+ call assert_fails('const b2 = v:true', 'E995:')
+ call assert_fails('const n2 = v:null', 'E995:')
+ call assert_fails('const [i2, f2, s2] = [1, 1.1, "vim"]', 'E995:')
+endfunc
+
+func Test_const_with_index_access()
+ let l = [1, 2, 3]
+ call assert_fails('const l[0] = 4', 'E996:')
+ call assert_fails('const l[0:1] = [1, 2]', 'E996:')
+
+ let d = {'aaa': 0}
+ call assert_fails("const d['aaa'] = 4", 'E996:')
+ call assert_fails("const d.aaa = 4", 'E996:')
+endfunc
+
+func Test_const_with_compound_assign()
+ let i = 0
+ call assert_fails('const i += 4', 'E995:')
+ call assert_fails('const i -= 4', 'E995:')
+ call assert_fails('const i *= 4', 'E995:')
+ call assert_fails('const i /= 4', 'E995:')
+ call assert_fails('const i %= 4', 'E995:')
+
+ let s = 'a'
+ call assert_fails('const s .= "b"', 'E995:')
+
+ let [a, b, c] = [1, 2, 3]
+ call assert_fails('const [a, b, c] += [4, 5, 6]', 'E995:')
+
+ let [d; e] = [1, 2, 3]
+ call assert_fails('const [d; e] += [4, 5, 6]', 'E995:')
+endfunc
+
+func Test_const_with_special_variables()
+ call assert_fails('const $FOO = "hello"', 'E996:')
+ call assert_fails('const @a = "hello"', 'E996:')
+ call assert_fails('const &filetype = "vim"', 'E996:')
+ call assert_fails('const &l:filetype = "vim"', 'E996:')
+ call assert_fails('const &g:encoding = "utf-8"', 'E996:')
+endfunc
+
+func Test_const_with_eval_name()
+ let s = 'foo'
+
+ " eval name with :const should work
+ const abc_{s} = 1
+ const {s}{s} = 1
+
+ let s2 = 'abc_foo'
+ call assert_fails('const {s2} = "bar"', 'E995:')
+endfunc
+
+func Test_lock_depth_is_1()
+ const l = [1, 2, 3]
+ const d = {'foo': 10}
+
+ " Modify list
+ call add(l, 4)
+ let l[0] = 42
+
+ " Modify dict
+ let d['bar'] = 'hello'
+ let d.foo = 44
+endfunc
diff --git a/src/nvim/testdir/test_cscope.vim b/src/nvim/testdir/test_cscope.vim
index bcb0d0dec5..e5dab05511 100644
--- a/src/nvim/testdir/test_cscope.vim
+++ b/src/nvim/testdir/test_cscope.vim
@@ -36,7 +36,7 @@ func Test_cscopeWithCscopeConnections()
" Test 1: Find this C-Symbol
for cmd in ['cs find s main', 'cs find 0 main']
- let a=execute(cmd)
+ let a = execute(cmd)
" Test 1.1 test where it moves the cursor
call assert_equal('main(void)', getline('.'))
" Test 1.2 test the output of the :cs command
@@ -51,21 +51,21 @@ func Test_cscopeWithCscopeConnections()
" Test 3: Find functions called by this function
for cmd in ['cs find d test_mf_hash', 'cs find 2 test_mf_hash']
- let a=execute(cmd)
+ let a = execute(cmd)
call assert_match('\n(1 of 42): <<mf_hash_init>> mf_hash_init(&ht);', a)
call assert_equal(' mf_hash_init(&ht);', getline('.'))
endfor
" Test 4: Find functions calling this function
for cmd in ['cs find c test_mf_hash', 'cs find 3 test_mf_hash']
- let a=execute(cmd)
+ let a = execute(cmd)
call assert_match('\n(1 of 1): <<main>> test_mf_hash();', a)
call assert_equal(' test_mf_hash();', getline('.'))
endfor
" Test 5: Find this text string
for cmd in ['cs find t Bram', 'cs find 4 Bram']
- let a=execute(cmd)
+ let a = execute(cmd)
call assert_match('(1 of 1): <<<unknown>>> \* VIM - Vi IMproved^Iby Bram Moolenaar', a)
call assert_equal(' * VIM - Vi IMproved by Bram Moolenaar', getline('.'))
endfor
@@ -73,7 +73,7 @@ func Test_cscopeWithCscopeConnections()
" Test 6: Find this egrep pattern
" test all matches returned by cscope
for cmd in ['cs find e ^\#includ.', 'cs find 6 ^\#includ.']
- let a=execute(cmd)
+ let a = execute(cmd)
call assert_match('\n(1 of 3): <<<unknown>>> #include <assert.h>', a)
call assert_equal('#include <assert.h>', getline('.'))
cnext
@@ -84,7 +84,7 @@ func Test_cscopeWithCscopeConnections()
endfor
" Test 7: Find the same egrep pattern using lcscope this time.
- let a=execute('lcs find e ^\#includ.')
+ let a = execute('lcs find e ^\#includ.')
call assert_match('\n(1 of 3): <<<unknown>>> #include <assert.h>', a)
call assert_equal('#include <assert.h>', getline('.'))
lnext
@@ -96,7 +96,7 @@ func Test_cscopeWithCscopeConnections()
" Test 8: Find this file
for cmd in ['cs find f Xmemfile_test.c', 'cs find 7 Xmemfile_test.c']
enew
- let a=execute(cmd)
+ let a = execute(cmd)
call assert_true(a =~ '"Xmemfile_test.c" \d\+L, \d\+C')
call assert_equal('Xmemfile_test.c', @%)
endfor
@@ -104,7 +104,7 @@ func Test_cscopeWithCscopeConnections()
" Test 9: Find files #including this file
for cmd in ['cs find i assert.h', 'cs find 8 assert.h']
enew
- let a=execute(cmd)
+ let a = execute(cmd)
let alines = split(a, '\n', 1)
call assert_equal('', alines[0])
call assert_true(alines[1] =~ '"Xmemfile_test.c" \d\+L, \d\+C')
@@ -118,13 +118,13 @@ func Test_cscopeWithCscopeConnections()
" Test 11: Find places where this symbol is assigned a value
" this needs a cscope >= 15.8
" unfortunately, Travis has cscope version 15.7
- let cscope_version=systemlist('cscope --version')[0]
- let cs_version=str2float(matchstr(cscope_version, '\d\+\(\.\d\+\)\?'))
+ let cscope_version = systemlist('cscope --version')[0]
+ let cs_version = str2float(matchstr(cscope_version, '\d\+\(\.\d\+\)\?'))
if cs_version >= 15.8
for cmd in ['cs find a item', 'cs find 9 item']
- let a=execute(cmd)
- call assert_equal(['', '(1 of 4): <<test_mf_hash>> item = (mf_hashitem_T *)lalloc_clear(sizeof(mf_hashtab_T), FALSE);'], split(a, '\n', 1))
- call assert_equal(' item = (mf_hashitem_T *)lalloc_clear(sizeof(mf_hashtab_T), FALSE);', getline('.'))
+ let a = execute(cmd)
+ call assert_equal(['', '(1 of 4): <<test_mf_hash>> item = (mf_hashitem_T *)lalloc_clear(sizeof(*item), FALSE);'], split(a, '\n', 1))
+ call assert_equal(' item = (mf_hashitem_T *)lalloc_clear(sizeof(*item), FALSE);', getline('.'))
cnext
call assert_equal(' item = mf_hash_find(&ht, key);', getline('.'))
cnext
@@ -135,18 +135,18 @@ func Test_cscopeWithCscopeConnections()
endif
" Test 12: leading whitespace is not removed for cscope find text
- let a=execute('cscope find t test_mf_hash')
+ let a = execute('cscope find t test_mf_hash')
call assert_equal(['', '(1 of 1): <<<unknown>>> test_mf_hash();'], split(a, '\n', 1))
call assert_equal(' test_mf_hash();', getline('.'))
" Test 13: test with scscope
- let a=execute('scs find t Bram')
+ let a = execute('scs find t Bram')
call assert_match('(1 of 1): <<<unknown>>> \* VIM - Vi IMproved^Iby Bram Moolenaar', a)
call assert_equal(' * VIM - Vi IMproved by Bram Moolenaar', getline('.'))
" Test 14: cscope help
for cmd in ['cs', 'cs help', 'cs xxx']
- let a=execute(cmd)
+ let a = execute(cmd)
call assert_match('^cscope commands:\n', a)
call assert_match('\nadd :', a)
call assert_match('\nfind :', a)
@@ -155,25 +155,25 @@ func Test_cscopeWithCscopeConnections()
call assert_match('\nreset: Reinit all connections', a)
call assert_match('\nshow : Show connections', a)
endfor
- let a=execute('scscope help')
+ let a = execute('scscope help')
call assert_match('This cscope command does not support splitting the window\.', a)
" Test 15: reset connections
- let a=execute('cscope reset')
+ let a = execute('cscope reset')
call assert_match('\nAdded cscope database.*Xcscope.out (#0)', a)
call assert_match('\nAll cscope databases reset', a)
" Test 16: cscope show
- let a=execute('cscope show')
+ let a = execute('cscope show')
call assert_match('\n 0 \d\+.*Xcscope.out\s*<none>', a)
" Test 17: cstag and 'csto' option
set csto=0
- let a=execute('cstag TEST_COUNT')
+ let a = execute('cstag TEST_COUNT')
call assert_match('(1 of 1): <<TEST_COUNT>> #define TEST_COUNT 50000', a)
call assert_equal('#define TEST_COUNT 50000', getline('.'))
set csto=1
- let a=execute('cstag index_to_key')
+ let a = execute('cstag index_to_key')
call assert_match('(1 of 1): <<index_to_key>> #define index_to_key(i) ((i) ^ 15167)', a)
call assert_equal('#define index_to_key(i) ((i) ^ 15167)', getline('.'))
call assert_fails('cstag xxx', 'E257:')
@@ -183,10 +183,10 @@ func Test_cscopeWithCscopeConnections()
set nocst
call assert_fails('tag TEST_COUNT', 'E426:')
set cst
- let a=execute('tag TEST_COUNT')
+ let a = execute('tag TEST_COUNT')
call assert_match('(1 of 1): <<TEST_COUNT>> #define TEST_COUNT 50000', a)
call assert_equal('#define TEST_COUNT 50000', getline('.'))
- let a=execute('tags')
+ let a = execute('tags')
call assert_match('1 1 TEST_COUNT\s\+\d\+\s\+#define index_to_key', a)
" Test 19: this should trigger call to cs_print_tags()
@@ -198,17 +198,17 @@ func Test_cscopeWithCscopeConnections()
call assert_fails('cscope kill 2', 'E261:')
call assert_fails('cscope kill xxx', 'E261:')
- let a=execute('cscope kill 0')
+ let a = execute('cscope kill 0')
call assert_match('cscope connection 0 closed', a)
cscope add Xcscope.out
- let a=execute('cscope kill Xcscope.out')
+ let a = execute('cscope kill Xcscope.out')
call assert_match('cscope connection Xcscope.out closed', a)
cscope add Xcscope.out .
- let a=execute('cscope kill -1')
+ let a = execute('cscope kill -1')
call assert_match('cscope connection .*Xcscope.out closed', a)
- let a=execute('cscope kill -1')
+ let a = execute('cscope kill -1')
call assert_equal('', a)
" Test 21: 'csprg' option
@@ -220,7 +220,7 @@ func Test_cscopeWithCscopeConnections()
" Test 22: multiple cscope connections
cscope add Xcscope.out
cscope add Xcscope2.out . -C
- let a=execute('cscope show')
+ let a = execute('cscope show')
call assert_match('\n 0 \d\+.*Xcscope.out\s*<none>', a)
call assert_match('\n 1 \d\+.*Xcscope2.out\s*\.', a)
@@ -294,7 +294,7 @@ func Test_withoutCscopeConnection()
call assert_equal(cscope_connection(), 0)
call assert_fails('cscope find s main', 'E567:')
- let a=execute('cscope show')
+ let a = execute('cscope show')
call assert_match('no cscope connections', a)
endfunc
diff --git a/src/nvim/testdir/test_cursor_func.vim b/src/nvim/testdir/test_cursor_func.vim
index e1b9651c84..6bc9535aaf 100644
--- a/src/nvim/testdir/test_cursor_func.vim
+++ b/src/nvim/testdir/test_cursor_func.vim
@@ -44,3 +44,23 @@ func Test_curswant_with_autocommand()
quit!
endfunc
+" Tests for behavior of curswant with cursorcolumn/line
+func Test_curswant_with_cursorcolumn()
+ new
+ call setline(1, ['01234567', ''])
+ exe "normal! ggf6j"
+ call assert_equal(6, winsaveview().curswant)
+ set cursorcolumn
+ call assert_equal(6, winsaveview().curswant)
+ quit!
+endfunc
+
+func Test_curswant_with_cursorline()
+ new
+ call setline(1, ['01234567', ''])
+ exe "normal! ggf6j"
+ call assert_equal(6, winsaveview().curswant)
+ set cursorline
+ call assert_equal(6, winsaveview().curswant)
+ quit!
+endfunc
diff --git a/src/nvim/testdir/test_debugger.vim b/src/nvim/testdir/test_debugger.vim
index c923b22836..bb87ef9c58 100644
--- a/src/nvim/testdir/test_debugger.vim
+++ b/src/nvim/testdir/test_debugger.vim
@@ -52,16 +52,16 @@ func Test_Debugger()
let buf = RunVimInTerminal('-S Xtest.vim', {})
" Start the Vim debugger
- call RunDbgCmd(buf, ':debug echo Foo()')
+ call RunDbgCmd(buf, ':debug echo Foo()', ['cmd: echo Foo()'])
" Create a few stack frames by stepping through functions
- call RunDbgCmd(buf, 'step')
- call RunDbgCmd(buf, 'step')
- call RunDbgCmd(buf, 'step')
- call RunDbgCmd(buf, 'step')
- call RunDbgCmd(buf, 'step')
- call RunDbgCmd(buf, 'step')
- call RunDbgCmd(buf, 'step')
+ call RunDbgCmd(buf, 'step', ['line 1: let var1 = 1'])
+ call RunDbgCmd(buf, 'step', ['line 2: let var2 = Bar(var1) + 9'])
+ call RunDbgCmd(buf, 'step', ['line 1: let var1 = 2 + a:var'])
+ call RunDbgCmd(buf, 'step', ['line 2: let var2 = Bazz(var1) + 4'])
+ call RunDbgCmd(buf, 'step', ['line 1: try'])
+ call RunDbgCmd(buf, 'step', ['line 2: let var1 = 3 + a:var'])
+ call RunDbgCmd(buf, 'step', ['line 3: let var3 = "another var"'])
" check backtrace
call RunDbgCmd(buf, 'backtrace', [
diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim
index 00f4563f3d..1ba36ca8e9 100644
--- a/src/nvim/testdir/test_diffmode.vim
+++ b/src/nvim/testdir/test_diffmode.vim
@@ -752,6 +752,9 @@ func Test_diff_of_diff()
if !CanRunVimInTerminal()
return
endif
+ if !has("rightleft")
+ throw 'Skipped: rightleft not supported'
+ endif
call writefile([
\ 'call setline(1, ["aa","bb","cc","@@ -3,2 +5,7 @@","dd","ee","ff"])',
diff --git a/src/nvim/testdir/test_edit.vim b/src/nvim/testdir/test_edit.vim
index 7f3994300f..48376d7922 100644
--- a/src/nvim/testdir/test_edit.vim
+++ b/src/nvim/testdir/test_edit.vim
@@ -643,11 +643,11 @@ func! Test_edit_CTRL_L()
call feedkeys("cct\<c-x>\<c-l>\<c-n>\<esc>", 'tnix')
call assert_equal(['one', 'two', 'three', 't', '', '', ''], getline(1, '$'))
call feedkeys("cct\<c-x>\<c-l>\<c-n>\<c-n>\<esc>", 'tnix')
- call assert_equal(['one', 'two', 'three', 't', '', '', ''], getline(1, '$'))
- call feedkeys("cct\<c-x>\<c-l>\<c-n>\<c-n>\<c-n>\<esc>", 'tnix')
call assert_equal(['one', 'two', 'three', 'two', '', '', ''], getline(1, '$'))
- call feedkeys("cct\<c-x>\<c-l>\<c-n>\<c-n>\<c-n>\<c-n>\<esc>", 'tnix')
+ call feedkeys("cct\<c-x>\<c-l>\<c-n>\<c-n>\<c-n>\<esc>", 'tnix')
call assert_equal(['one', 'two', 'three', 'three', '', '', ''], getline(1, '$'))
+ call feedkeys("cct\<c-x>\<c-l>\<c-n>\<c-n>\<c-n>\<c-n>\<esc>", 'tnix')
+ call assert_equal(['one', 'two', 'three', 't', '', '', ''], getline(1, '$'))
call feedkeys("cct\<c-x>\<c-l>\<c-p>\<esc>", 'tnix')
call assert_equal(['one', 'two', 'three', 'two', '', '', ''], getline(1, '$'))
call feedkeys("cct\<c-x>\<c-l>\<c-p>\<c-p>\<esc>", 'tnix')
@@ -1408,7 +1408,6 @@ func Test_edit_complete_very_long_name()
let save_columns = &columns
" Need at least about 1100 columns to reproduce the problem.
set columns=2000
- call assert_equal(2000, &columns)
set noswapfile
let longfilename = longdirname . '/' . repeat('a', 255)
diff --git a/src/nvim/testdir/test_environ.vim b/src/nvim/testdir/test_environ.vim
new file mode 100644
index 0000000000..21bb09a690
--- /dev/null
+++ b/src/nvim/testdir/test_environ.vim
@@ -0,0 +1,44 @@
+scriptencoding utf-8
+
+func Test_environ()
+ unlet! $TESTENV
+ call assert_equal(0, has_key(environ(), 'TESTENV'))
+ let $TESTENV = 'foo'
+ call assert_equal(1, has_key(environ(), 'TESTENV'))
+ let $TESTENV = 'こんにちわ'
+ call assert_equal('こんにちわ', environ()['TESTENV'])
+endfunc
+
+func Test_getenv()
+ unlet! $TESTENV
+ call assert_equal(v:null, getenv('TESTENV'))
+ let $TESTENV = 'foo'
+ call assert_equal('foo', getenv('TESTENV'))
+endfunc
+
+func Test_setenv()
+ unlet! $TESTENV
+ call setenv('TEST ENV', 'foo')
+ call assert_equal('foo', getenv('TEST ENV'))
+ call setenv('TEST ENV', v:null)
+ call assert_equal(v:null, getenv('TEST ENV'))
+endfunc
+
+func Test_external_env()
+ call setenv('FOO', 'HelloWorld')
+ if has('win32')
+ let result = system('echo %FOO%')
+ else
+ let result = system('echo $FOO')
+ endif
+ let result = substitute(result, '[ \r\n]', '', 'g')
+ call assert_equal('HelloWorld', result)
+
+ call setenv('FOO', v:null)
+ if has('win32')
+ let result = system('set | findstr "^FOO="')
+ else
+ let result = system('env | grep ^FOO=')
+ endif
+ call assert_equal('', result)
+endfunc
diff --git a/src/nvim/testdir/test_excmd.vim b/src/nvim/testdir/test_excmd.vim
new file mode 100644
index 0000000000..f5ce979208
--- /dev/null
+++ b/src/nvim/testdir/test_excmd.vim
@@ -0,0 +1,10 @@
+" Tests for various Ex commands.
+
+func Test_ex_delete()
+ new
+ call setline(1, ['a', 'b', 'c'])
+ 2
+ " :dl is :delete with the "l" flag, not :dlist
+ .dl
+ call assert_equal(['a', 'c'], getline(1, 2))
+endfunc
diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim
index 7a99a37be4..a9ade9155a 100644
--- a/src/nvim/testdir/test_filetype.vim
+++ b/src/nvim/testdir/test_filetype.vim
@@ -44,8 +44,10 @@ endfunc
" Filetypes detected just from matching the file name.
let s:filename_checks = {
+ \ '8th': ['file.8th'],
\ 'a2ps': ['/etc/a2ps.cfg', '/etc/a2ps/file.cfg', 'a2psrc', '.a2psrc'],
\ 'a65': ['file.a65'],
+ \ 'aap': ['file.aap'],
\ 'abap': ['file.abap'],
\ 'abc': ['file.abc'],
\ 'abel': ['file.abl'],
@@ -56,7 +58,8 @@ let s:filename_checks = {
\ 'aml': ['file.aml'],
\ 'ampl': ['file.run'],
\ 'ant': ['build.xml'],
- \ 'apache': ['.htaccess', '/etc/httpd/file.conf'],
+ \ 'apache': ['.htaccess', '/etc/httpd/file.conf', '/etc/apache2/sites-2/file.com', '/etc/apache2/some.config', '/etc/apache2/conf.file/conf', '/etc/apache2/mods-some/file', '/etc/apache2/sites-some/file', '/etc/httpd/conf.d/file.config'],
+ \ 'apachestyle': ['/etc/proftpd/file.config,/etc/proftpd/conf.file/file'],
\ 'applescript': ['file.scpt'],
\ 'aptconf': ['apt.conf', '/.aptitude/config'],
\ 'arch': ['.arch-inventory'],
@@ -81,7 +84,7 @@ let s:filename_checks = {
\ 'c': ['enlightenment/file.cfg', 'file.qc', 'file.c'],
\ 'cabal': ['file.cabal'],
\ 'calendar': ['calendar'],
- \ 'catalog': ['catalog'],
+ \ 'catalog': ['catalog', 'sgml.catalogfile'],
\ 'cdl': ['file.cdl'],
\ 'cdrdaoconf': ['/etc/cdrdao.conf', '/etc/defaults/cdrdao', '/etc/default/cdrdao', '.cdrdao'],
\ 'cdrtoc': ['file.toc'],
@@ -102,7 +105,7 @@ let s:filename_checks = {
\ 'coco': ['file.atg'],
\ 'conaryrecipe': ['file.recipe'],
\ 'conf': ['auto.master'],
- \ 'config': ['configure.in', 'configure.ac'],
+ \ 'config': ['configure.in', 'configure.ac', 'Pipfile'],
\ 'context': ['tex/context/any/file.tex', 'file.mkii', 'file.mkiv', 'file.mkvi'],
\ 'cpp': ['file.cxx', 'file.c++', 'file.hh', 'file.hxx', 'file.hpp', 'file.ipp', 'file.moc', 'file.tcc', 'file.inl', 'file.tlh'],
\ 'crm': ['file.crm'],
@@ -223,7 +226,7 @@ let s:filename_checks = {
\ 'jgraph': ['file.jgr'],
\ 'jovial': ['file.jov', 'file.j73', 'file.jovial'],
\ 'jproperties': ['file.properties', 'file.properties_xx', 'file.properties_xx_xx'],
- \ 'json': ['file.json', 'file.jsonp', 'file.webmanifest'],
+ \ 'json': ['file.json', 'file.jsonp', 'file.webmanifest', 'Pipfile.lock'],
\ 'jsp': ['file.jsp'],
\ 'kconfig': ['Kconfig', 'Kconfig.debug'],
\ 'kivy': ['file.kv'],
@@ -424,7 +427,7 @@ let s:filename_checks = {
\ 'svg': ['file.svg'],
\ 'svn': ['svn-commitfile.tmp'],
\ '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'],
+ \ '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'],
\ 'systemverilog': ['file.sv', 'file.svh'],
\ 'tags': ['tags'],
\ 'tak': ['file.tak'],
@@ -467,13 +470,14 @@ 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'],
+ \ 'vhdl': ['file.hdl', 'file.vhd', 'file.vhdl', 'file.vbe', 'file.vst', 'file.vhdl_123'],
\ 'vim': ['file.vim', 'file.vba', '.exrc', '_exrc'],
\ 'viminfo': ['.viminfo', '_viminfo'],
\ 'vmasm': ['file.mar'],
\ 'voscm': ['file.cm'],
\ 'vrml': ['file.wrl'],
\ 'vroom': ['file.vroom'],
+ \ 'vue': ['file.vue'],
\ 'wast': ['file.wast', 'file.wat'],
\ 'webmacro': ['file.wm'],
\ 'wget': ['.wgetrc', 'wgetrc'],
@@ -501,7 +505,6 @@ let s:filename_checks = {
\ 'zimbutempl': ['file.zut'],
\ 'zsh': ['.zprofile', '/etc/zprofile', '.zfbfmarks', 'file.zsh'],
\
- \ 'aap': ['file.aap'],
\ 'help': [$VIMRUNTIME . '/doc/help.txt'],
\ 'xpm': ['file.xpm'],
\ }
@@ -515,11 +518,15 @@ func CheckItems(checks)
for i in range(0, len(names) - 1)
new
try
- exe 'edit ' . names[i]
+ exe 'edit ' . fnameescape(names[i])
catch
- call assert_report('cannot edit "' . names[i] . '": ' . v:errmsg)
+ call assert_report('cannot edit "' . names[i] . '": ' . v:exception)
endtry
- call assert_equal(ft, &filetype, 'with file name: ' . names[i])
+ if &filetype == '' && &readonly
+ " File exists but not able to edit it (permission denied)
+ else
+ call assert_equal(ft, &filetype, 'with file name: ' . names[i])
+ endif
bwipe!
endfor
endfor
@@ -581,6 +588,8 @@ let s:script_checks = {
\ 'cfengine': [['#!/path/cfengine']],
\ 'erlang': [['#!/path/escript']],
\ 'haskell': [['#!/path/haskell']],
+ \ 'cpp': [['// Standard iostream objects -*- C++ -*-'],
+ \ ['// -*- C++ -*-']],
\ }
func Test_script_detection()
diff --git a/src/nvim/testdir/test_filter_cmd.vim b/src/nvim/testdir/test_filter_cmd.vim
index 86347ab77f..0c45db049b 100644
--- a/src/nvim/testdir/test_filter_cmd.vim
+++ b/src/nvim/testdir/test_filter_cmd.vim
@@ -87,3 +87,61 @@ func Test_filter_cmd_with_filter()
call assert_equal('a|b', out)
set shelltemp&
endfunction
+
+func Test_filter_commands()
+ let g:test_filter_a = 1
+ let b:test_filter_b = 2
+ let test_filter_c = 3
+
+ " Test filtering :let command
+ let res = split(execute("filter /^test_filter/ let"), "\n")
+ call assert_equal(["test_filter_a #1"], res)
+
+ let res = split(execute("filter /\\v^(b:)?test_filter/ let"), "\n")
+ call assert_equal(["test_filter_a #1", "b:test_filter_b #2"], res)
+
+ unlet g:test_filter_a
+ unlet b:test_filter_b
+ unlet test_filter_c
+
+ " Test filtering :set command
+ let helplang=&helplang
+ set helplang=en
+ let res = join(split(execute("filter /^help/ set"), "\n")[1:], " ")
+ call assert_match('^\s*helplang=\w*$', res)
+ let &helplang=helplang
+
+ " Test filtering :llist command
+ call setloclist(0, [{"filename": "/path/vim.c"}, {"filename": "/path/vim.h"}, {"module": "Main.Test"}])
+ let res = split(execute("filter /\\.c$/ llist"), "\n")
+ call assert_equal([" 1 /path/vim.c: "], res)
+
+ let res = split(execute("filter /\\.Test$/ llist"), "\n")
+ call assert_equal([" 3 Main.Test: "], res)
+
+ " Test filtering :jump command
+ e file.c
+ e file.h
+ e file.hs
+ let res = split(execute("filter /\.c$/ jumps"), "\n")[1:]
+ call assert_equal([" 2 1 0 file.c", ">"], res)
+
+ " Test filtering :marks command
+ b file.c
+ mark A
+ b file.h
+ mark B
+ let res = split(execute("filter /\.c$/ marks"), "\n")[1:]
+ call assert_equal([" A 1 0 file.c"], res)
+
+ call setline(1, ['one', 'two', 'three'])
+ 1mark a
+ 2mark b
+ 3mark c
+ let res = split(execute("filter /two/ marks abc"), "\n")[1:]
+ call assert_equal([" b 2 0 two"], res)
+
+ bwipe! file.c
+ bwipe! file.h
+ bwipe! file.hs
+endfunc
diff --git a/src/nvim/testdir/test_filter_map.vim b/src/nvim/testdir/test_filter_map.vim
index c8d64ce0a4..1dd3a5b29f 100644
--- a/src/nvim/testdir/test_filter_map.vim
+++ b/src/nvim/testdir/test_filter_map.vim
@@ -79,3 +79,8 @@ func Test_filter_map_dict_expr_funcref()
endfunc
call assert_equal({"foo": "f", "bar": "b", "baz": "b"}, map(copy(dict), function('s:filter4')))
endfunc
+
+func Test_map_fails()
+ call assert_fails('call map([1], "42 +")', 'E15:')
+ call assert_fails('call filter([1], "42 +")', 'E15:')
+endfunc
diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim
index ed9c70403e..fab1d7790d 100644
--- a/src/nvim/testdir/test_functions.vim
+++ b/src/nvim/testdir/test_functions.vim
@@ -1,4 +1,5 @@
" Tests for various functions.
+source shared.vim
" Must be done first, since the alternate buffer must be unset.
func Test_00_bufexists()
@@ -886,6 +887,14 @@ func Test_Executable()
elseif has('unix')
call assert_equal(1, executable('cat'))
call assert_equal(0, executable('nodogshere'))
+
+ " get "cat" path and remove the leading /
+ let catcmd = exepath('cat')[1:]
+ new
+ lcd /
+ call assert_equal(1, executable(catcmd))
+ call assert_equal('/' .. catcmd, exepath(catcmd))
+ bwipe
endif
endfunc
@@ -1140,3 +1149,92 @@ func Test_reg_executing_and_recording()
delfunc s:save_reg_stat
unlet s:reg_stat
endfunc
+
+func Test_libcall_libcallnr()
+ if !has('libcall')
+ return
+ endif
+
+ if has('win32')
+ let libc = 'msvcrt.dll'
+ elseif has('mac')
+ let libc = 'libSystem.B.dylib'
+ elseif system('uname -s') =~ 'SunOS'
+ " Set the path to libc.so according to the architecture.
+ let test_bits = system('file ' . GetVimProg())
+ let test_arch = system('uname -p')
+ if test_bits =~ '64-bit' && test_arch =~ 'sparc'
+ let libc = '/usr/lib/sparcv9/libc.so'
+ elseif test_bits =~ '64-bit' && test_arch =~ 'i386'
+ let libc = '/usr/lib/amd64/libc.so'
+ else
+ let libc = '/usr/lib/libc.so'
+ endif
+ else
+ " On Unix, libc.so can be in various places.
+ " Interestingly, using an empty string for the 1st argument of libcall
+ " allows to call functions from libc which is not documented.
+ let libc = ''
+ endif
+
+ if has('win32')
+ call assert_equal($USERPROFILE, libcall(libc, 'getenv', 'USERPROFILE'))
+ else
+ call assert_equal($HOME, libcall(libc, 'getenv', 'HOME'))
+ endif
+
+ " If function returns NULL, libcall() should return an empty string.
+ call assert_equal('', libcall(libc, 'getenv', 'X_ENV_DOES_NOT_EXIT'))
+
+ " Test libcallnr() with string and integer argument.
+ call assert_equal(4, libcallnr(libc, 'strlen', 'abcd'))
+ call assert_equal(char2nr('A'), libcallnr(libc, 'toupper', char2nr('a')))
+
+ call assert_fails("call libcall(libc, 'Xdoesnotexist_', '')", 'E364:')
+ call assert_fails("call libcallnr(libc, 'Xdoesnotexist_', '')", 'E364:')
+
+ call assert_fails("call libcall('Xdoesnotexist_', 'getenv', 'HOME')", 'E364:')
+ call assert_fails("call libcallnr('Xdoesnotexist_', 'strlen', 'abcd')", 'E364:')
+endfunc
+
+func Test_bufadd_bufload()
+ call assert_equal(0, bufexists('someName'))
+ let buf = bufadd('someName')
+ call assert_notequal(0, buf)
+ call assert_equal(1, bufexists('someName'))
+ call assert_equal(0, getbufvar(buf, '&buflisted'))
+ call assert_equal(0, bufloaded(buf))
+ call bufload(buf)
+ call assert_equal(1, bufloaded(buf))
+ call assert_equal([''], getbufline(buf, 1, '$'))
+
+ let curbuf = bufnr('')
+ call writefile(['some', 'text'], 'otherName')
+ let buf = bufadd('otherName')
+ call assert_notequal(0, buf)
+ call assert_equal(1, bufexists('otherName'))
+ call assert_equal(0, getbufvar(buf, '&buflisted'))
+ call assert_equal(0, bufloaded(buf))
+ call bufload(buf)
+ call assert_equal(1, bufloaded(buf))
+ call assert_equal(['some', 'text'], getbufline(buf, 1, '$'))
+ call assert_equal(curbuf, bufnr(''))
+
+ let buf1 = bufadd('')
+ let buf2 = bufadd('')
+ call assert_notequal(0, buf1)
+ call assert_notequal(0, buf2)
+ call assert_notequal(buf1, buf2)
+ call assert_equal(1, bufexists(buf1))
+ call assert_equal(1, bufexists(buf2))
+ call assert_equal(0, bufloaded(buf1))
+ exe 'bwipe ' .. buf1
+ call assert_equal(0, bufexists(buf1))
+ call assert_equal(1, bufexists(buf2))
+ exe 'bwipe ' .. buf2
+ call assert_equal(0, bufexists(buf2))
+
+ bwipe someName
+ bwipe otherName
+ call assert_equal(0, bufexists('someName'))
+endfunc
diff --git a/src/nvim/testdir/test_getvar.vim b/src/nvim/testdir/test_getvar.vim
index d6b6b69aa8..3b61d68ebc 100644
--- a/src/nvim/testdir/test_getvar.vim
+++ b/src/nvim/testdir/test_getvar.vim
@@ -1,4 +1,5 @@
-" Tests for getwinvar(), gettabvar() and gettabwinvar().
+" Tests for getwinvar(), gettabvar(), gettabwinvar() and get().
+
func Test_var()
" Use strings to test for memory leaks. First, check that in an empty
" window, gettabvar() returns the correct value
@@ -102,3 +103,44 @@ func Test_gettabvar_in_tabline()
close
redrawstatus!
endfunc
+
+" Test get() function using default value.
+
+" get({dict}, {key} [, {default}])
+func Test_get_dict()
+ let d = {'foo': 42}
+ call assert_equal(42, get(d, 'foo', 99))
+ call assert_equal(999, get(d, 'bar', 999))
+endfunc
+
+" get({list}, {idx} [, {default}])
+func Test_get_list()
+ let l = [1,2,3]
+ call assert_equal(1, get(l, 0, 999))
+ call assert_equal(3, get(l, -1, 999))
+ call assert_equal(999, get(l, 3, 999))
+endfunc
+
+" get({blob}, {idx} [, {default}]) - in test_blob.vim
+
+" get({lambda}, {what} [, {default}])
+func Test_get_lambda()
+ let l:L = {-> 42}
+ call assert_match('^<lambda>', get(l:L, 'name'))
+ call assert_equal(l:L, get(l:L, 'func'))
+ call assert_equal({'lambda has': 'no dict'}, get(l:L, 'dict', {'lambda has': 'no dict'}))
+ call assert_equal(0, get(l:L, 'dict'))
+ call assert_equal([], get(l:L, 'args'))
+endfunc
+
+" get({func}, {what} [, {default}])
+func Test_get_func()
+ let l:F = function('tr')
+ call assert_equal('tr', get(l:F, 'name'))
+ call assert_equal(l:F, get(l:F, 'func'))
+ call assert_equal({'func has': 'no dict'}, get(l:F, 'dict', {'func has': 'no dict'}))
+ call assert_equal(0, get(l:F, 'dict'))
+ call assert_equal([], get(l:F, 'args'))
+endfunc
+
+" get({partial}, {what} [, {default}]) - in test_partial.vim
diff --git a/src/nvim/testdir/test_ins_complete.vim b/src/nvim/testdir/test_ins_complete.vim
index d3429617d0..7f52481ba8 100644
--- a/src/nvim/testdir/test_ins_complete.vim
+++ b/src/nvim/testdir/test_ins_complete.vim
@@ -249,3 +249,39 @@ func Test_omni_dash()
delfunc Omni
set omnifunc=
endfunc
+
+func Test_completefunc_args()
+ let s:args = []
+ func! CompleteFunc(findstart, base)
+ let s:args += [[a:findstart, empty(a:base)]]
+ endfunc
+ new
+
+ set completefunc=CompleteFunc
+ call feedkeys("i\<C-X>\<C-U>\<Esc>", 'x')
+ call assert_equal([1, 1], s:args[0])
+ call assert_equal(0, s:args[1][0])
+ set completefunc=
+
+ let s:args = []
+ set omnifunc=CompleteFunc
+ call feedkeys("i\<C-X>\<C-O>\<Esc>", 'x')
+ call assert_equal([1, 1], s:args[0])
+ call assert_equal(0, s:args[1][0])
+ set omnifunc=
+
+ bwipe!
+ unlet s:args
+ delfunc CompleteFunc
+endfunc
+
+" Check that when using feedkeys() typeahead does not interrupt searching for
+" completions.
+func Test_compl_feedkeys()
+ new
+ set completeopt=menuone,noselect
+ call feedkeys("ajump ju\<C-X>\<C-N>\<C-P>\<ESC>", "tx")
+ call assert_equal("jump jump", getline(1))
+ bwipe!
+ set completeopt&
+endfunc
diff --git a/src/nvim/testdir/test_lambda.vim b/src/nvim/testdir/test_lambda.vim
index bc7817cef8..bfbb3e5c5b 100644
--- a/src/nvim/testdir/test_lambda.vim
+++ b/src/nvim/testdir/test_lambda.vim
@@ -23,30 +23,36 @@ function! Test_lambda_with_timer()
return
endif
- source load.vim
-
let s:n = 0
let s:timer_id = 0
- function! s:Foo()
- "let n = 0
- let s:timer_id = timer_start(50, {-> execute("let s:n += 1 | echo s:n", "")}, {"repeat": -1})
- endfunction
+ func! s:Foo()
+ let s:timer_id = timer_start(10, {-> execute("let s:n += 1 | echo s:n", "")}, {"repeat": -1})
+ endfunc
call s:Foo()
- sleep 210m
+ " check timer works
+ for i in range(0, 10)
+ if s:n > 0
+ break
+ endif
+ sleep 10m
+ endfor
+
" do not collect lambda
call test_garbagecollect_now()
- let m = LoadAdjust(s:n)
- sleep 230m
- call timer_stop(s:timer_id)
- let n = LoadAdjust(s:n)
- let nine = LoadAdjust(9)
+ " check timer still works
+ let m = s:n
+ for i in range(0, 10)
+ if s:n > m
+ break
+ endif
+ sleep 10m
+ endfor
- call assert_true(m > 1)
- call assert_true(n > m + 1)
- call assert_true(n < nine)
-endfunction
+ call timer_stop(s:timer_id)
+ call assert_true(s:n > m)
+endfunc
function! Test_lambda_with_partial()
let l:Cb = function({... -> ['zero', a:1, a:2, a:3]}, ['one', 'two'])
diff --git a/src/nvim/testdir/test_makeencoding.py b/src/nvim/testdir/test_makeencoding.py
index 041edadc0a..f6dc0f8d1c 100644
--- a/src/nvim/testdir/test_makeencoding.py
+++ b/src/nvim/testdir/test_makeencoding.py
@@ -8,6 +8,7 @@ import locale
import io
import sys
+
def set_output_encoding(enc=None):
"""Set the encoding of stdout and stderr
@@ -20,7 +21,7 @@ def set_output_encoding(enc=None):
def get_text_writer(fo, **kwargs):
kw = dict(kwargs)
- kw.setdefault('errors', 'backslashreplace') # use \uXXXX style
+ kw.setdefault('errors', 'backslashreplace') # use \uXXXX style
kw.setdefault('closefd', False)
if sys.version_info[0] < 3:
@@ -29,6 +30,7 @@ def set_output_encoding(enc=None):
writer = io.open(fo.fileno(), mode='w', newline='', **kw)
write = writer.write # save the original write() function
enc = locale.getpreferredencoding()
+
def convwrite(s):
if isinstance(s, bytes):
write(s.decode(enc)) # convert to unistr
diff --git a/src/nvim/testdir/test_maparg.vim b/src/nvim/testdir/test_maparg.vim
index 0fb878b04a..ee16a22398 100644
--- a/src/nvim/testdir/test_maparg.vim
+++ b/src/nvim/testdir/test_maparg.vim
@@ -27,6 +27,10 @@ function Test_maparg()
call assert_equal({'silent': 0, 'noremap': 0, 'lhs': 'foo', 'mode': ' ',
\ 'nowait': 1, 'expr': 0, 'sid': sid, 'rhs': 'bar', 'buffer': 1},
\ maparg('foo', '', 0, 1))
+ tmap baz foo
+ call assert_equal({'silent': 0, 'noremap': 0, 'lhs': 'baz', 'mode': 't',
+ \ 'nowait': 0, 'expr': 0, 'sid': sid, 'rhs': 'foo', 'buffer': 0},
+ \ maparg('baz', 't', 0, 1))
map abc x<char-114>x
call assert_equal("xrx", maparg('abc'))
diff --git a/src/nvim/testdir/test_mapping.vim b/src/nvim/testdir/test_mapping.vim
index 84a118aef2..9f253604ed 100644
--- a/src/nvim/testdir/test_mapping.vim
+++ b/src/nvim/testdir/test_mapping.vim
@@ -187,9 +187,32 @@ func Test_map_cursor()
imapclear
endfunc
+func Test_map_cursor_ctrl_gU()
+ " <c-g>U<cursor> works only within a single line
+ nnoremap c<* *Ncgn<C-r>"<C-G>U<S-Left>
+ call setline(1, ['foo', 'foobar', '', 'foo'])
+ call cursor(1,2)
+ call feedkeys("c<*PREFIX\<esc>.", 'xt')
+ call assert_equal(['PREFIXfoo', 'foobar', '', 'PREFIXfoo'], getline(1,'$'))
+ " break undo manually
+ set ul=1000
+ exe ":norm! uu"
+ call assert_equal(['foo', 'foobar', '', 'foo'], getline(1,'$'))
+
+ " Test that it does not work if the cursor moves to the previous line
+ " 2 times <S-Left> move to the previous line
+ nnoremap c<* *Ncgn<C-r>"<C-G>U<S-Left><C-G>U<S-Left>
+ call setline(1, ['', ' foo', 'foobar', '', 'foo'])
+ call cursor(2,3)
+ call feedkeys("c<*PREFIX\<esc>.", 'xt')
+ call assert_equal(['PREFIXPREFIX', ' foo', 'foobar', '', 'foo'], getline(1,'$'))
+ nmapclear
+endfunc
+
+
" This isn't actually testing a mapping, but similar use of CTRL-G U as above.
func Test_break_undo()
- :set whichwrap=<,>,[,]
+ set whichwrap=<,>,[,]
call feedkeys("G4o2k", "xt")
exe ":norm! iTest3: text with a (parenthesis here\<C-G>U\<Right>new line here\<esc>\<up>\<up>."
call assert_equal('new line here', getline(line('$') - 3))
diff --git a/src/nvim/testdir/test_mksession.vim b/src/nvim/testdir/test_mksession.vim
index 7c5b95ae96..d77dac69c7 100644
--- a/src/nvim/testdir/test_mksession.vim
+++ b/src/nvim/testdir/test_mksession.vim
@@ -238,6 +238,17 @@ func Test_mkview_no_file_name()
%bwipe
endfunc
+" A clean session (one empty buffer, one window, and one tab) should not
+" set any error messages when sourced because no commands should fail.
+func Test_mksession_no_errmsg()
+ let v:errmsg = ''
+ %bwipe!
+ mksession! Xtest_mks.out
+ source Xtest_mks.out
+ call assert_equal('', v:errmsg)
+ call delete('Xtest_mks.out')
+endfunc
+
func Test_mksession_quote_in_filename()
if !has('unix')
" only Unix can handle this weird filename
diff --git a/src/nvim/testdir/test_modeline.vim b/src/nvim/testdir/test_modeline.vim
index 091a833774..1e196e07f0 100644
--- a/src/nvim/testdir/test_modeline.vim
+++ b/src/nvim/testdir/test_modeline.vim
@@ -60,14 +60,17 @@ func Test_modeline_keymap()
set keymap= iminsert=0 imsearch=-1
endfunc
-func s:modeline_fails(what, text)
+func s:modeline_fails(what, text, error)
+ if !exists('+' . a:what)
+ return
+ endif
let fname = "Xmodeline_fails_" . a:what
call writefile(['vim: set ' . a:text . ' :', 'nothing'], fname)
let modeline = &modeline
set modeline
filetype plugin on
syntax enable
- call assert_fails('split ' . fname, 'E474:')
+ call assert_fails('split ' . fname, a:error)
call assert_equal("", &filetype)
call assert_equal("", &syntax)
@@ -79,16 +82,92 @@ func s:modeline_fails(what, text)
endfunc
func Test_modeline_filetype_fails()
- call s:modeline_fails('filetype', 'ft=evil$CMD')
+ call s:modeline_fails('filetype', 'ft=evil$CMD', 'E474:')
endfunc
func Test_modeline_syntax_fails()
- call s:modeline_fails('syntax', 'syn=evil$CMD')
+ call s:modeline_fails('syntax', 'syn=evil$CMD', 'E474:')
endfunc
func Test_modeline_keymap_fails()
- if !has('keymap')
- return
- endif
- call s:modeline_fails('keymap', 'keymap=evil$CMD')
+ call s:modeline_fails('keymap', 'keymap=evil$CMD', 'E474:')
+endfunc
+
+func Test_modeline_fails_always()
+ call s:modeline_fails('backupdir', 'backupdir=Something()', 'E520:')
+ call s:modeline_fails('cdpath', 'cdpath=Something()', 'E520:')
+ call s:modeline_fails('charconvert', 'charconvert=Something()', 'E520:')
+ call s:modeline_fails('completefunc', 'completefunc=Something()', 'E520:')
+ call s:modeline_fails('cscopeprg', 'cscopeprg=Something()', 'E520:')
+ call s:modeline_fails('diffexpr', 'diffexpr=Something()', 'E520:')
+ call s:modeline_fails('directory', 'directory=Something()', 'E520:')
+ call s:modeline_fails('equalprg', 'equalprg=Something()', 'E520:')
+ call s:modeline_fails('errorfile', 'errorfile=Something()', 'E520:')
+ call s:modeline_fails('exrc', 'exrc=Something()', 'E520:')
+ call s:modeline_fails('formatprg', 'formatprg=Something()', 'E520:')
+ call s:modeline_fails('fsync', 'fsync=Something()', 'E520:')
+ call s:modeline_fails('grepprg', 'grepprg=Something()', 'E520:')
+ call s:modeline_fails('helpfile', 'helpfile=Something()', 'E520:')
+ call s:modeline_fails('imactivatefunc', 'imactivatefunc=Something()', 'E520:')
+ call s:modeline_fails('imstatusfunc', 'imstatusfunc=Something()', 'E520:')
+ call s:modeline_fails('imstyle', 'imstyle=Something()', 'E520:')
+ call s:modeline_fails('keywordprg', 'keywordprg=Something()', 'E520:')
+ call s:modeline_fails('langmap', 'langmap=Something()', 'E520:')
+ call s:modeline_fails('luadll', 'luadll=Something()', 'E520:')
+ call s:modeline_fails('makeef', 'makeef=Something()', 'E520:')
+ call s:modeline_fails('makeprg', 'makeprg=Something()', 'E520:')
+ call s:modeline_fails('mkspellmem', 'mkspellmem=Something()', 'E520:')
+ call s:modeline_fails('mzschemedll', 'mzschemedll=Something()', 'E520:')
+ call s:modeline_fails('mzschemegcdll', 'mzschemegcdll=Something()', 'E520:')
+ call s:modeline_fails('modelineexpr', 'modelineexpr=Something()', 'E520:')
+ call s:modeline_fails('omnifunc', 'omnifunc=Something()', 'E520:')
+ call s:modeline_fails('operatorfunc', 'operatorfunc=Something()', 'E520:')
+ call s:modeline_fails('perldll', 'perldll=Something()', 'E520:')
+ call s:modeline_fails('printdevice', 'printdevice=Something()', 'E520:')
+ call s:modeline_fails('patchexpr', 'patchexpr=Something()', 'E520:')
+ call s:modeline_fails('printexpr', 'printexpr=Something()', 'E520:')
+ call s:modeline_fails('pythondll', 'pythondll=Something()', 'E520:')
+ call s:modeline_fails('pythonhome', 'pythonhome=Something()', 'E520:')
+ call s:modeline_fails('pythonthreedll', 'pythonthreedll=Something()', 'E520:')
+ call s:modeline_fails('pythonthreehome', 'pythonthreehome=Something()', 'E520:')
+ call s:modeline_fails('pyxversion', 'pyxversion=Something()', 'E520:')
+ call s:modeline_fails('rubydll', 'rubydll=Something()', 'E520:')
+ call s:modeline_fails('runtimepath', 'runtimepath=Something()', 'E520:')
+ call s:modeline_fails('secure', 'secure=Something()', 'E520:')
+ call s:modeline_fails('shell', 'shell=Something()', 'E520:')
+ call s:modeline_fails('shellcmdflag', 'shellcmdflag=Something()', 'E520:')
+ call s:modeline_fails('shellpipe', 'shellpipe=Something()', 'E520:')
+ call s:modeline_fails('shellquote', 'shellquote=Something()', 'E520:')
+ call s:modeline_fails('shellredir', 'shellredir=Something()', 'E520:')
+ call s:modeline_fails('shellxquote', 'shellxquote=Something()', 'E520:')
+ call s:modeline_fails('spellfile', 'spellfile=Something()', 'E520:')
+ call s:modeline_fails('spellsuggest', 'spellsuggest=Something()', 'E520:')
+ call s:modeline_fails('tcldll', 'tcldll=Something()', 'E520:')
+ call s:modeline_fails('titleold', 'titleold=Something()', 'E520:')
+ call s:modeline_fails('viewdir', 'viewdir=Something()', 'E520:')
+ call s:modeline_fails('viminfo', 'viminfo=Something()', 'E520:')
+ call s:modeline_fails('viminfofile', 'viminfofile=Something()', 'E520:')
+ call s:modeline_fails('winptydll', 'winptydll=Something()', 'E520:')
+ call s:modeline_fails('undodir', 'undodir=Something()', 'E520:')
+ " only check a few terminal options
+ " Skip these since nvim doesn't support termcodes as options
+ "call s:modeline_fails('t_AB', 't_AB=Something()', 'E520:')
+ "call s:modeline_fails('t_ce', 't_ce=Something()', 'E520:')
+ "call s:modeline_fails('t_sr', 't_sr=Something()', 'E520:')
+ "call s:modeline_fails('t_8b', 't_8b=Something()', 'E520:')
+endfunc
+
+func Test_modeline_fails_modelineexpr()
+ call s:modeline_fails('balloonexpr', 'balloonexpr=Something()', 'E992:')
+ call s:modeline_fails('foldexpr', 'foldexpr=Something()', 'E992:')
+ call s:modeline_fails('foldtext', 'foldtext=Something()', 'E992:')
+ call s:modeline_fails('formatexpr', 'formatexpr=Something()', 'E992:')
+ call s:modeline_fails('guitablabel', 'guitablabel=Something()', 'E992:')
+ call s:modeline_fails('iconstring', 'iconstring=Something()', 'E992:')
+ call s:modeline_fails('includeexpr', 'includeexpr=Something()', 'E992:')
+ call s:modeline_fails('indentexpr', 'indentexpr=Something()', 'E992:')
+ call s:modeline_fails('rulerformat', 'rulerformat=Something()', 'E992:')
+ call s:modeline_fails('statusline', 'statusline=Something()', 'E992:')
+ call s:modeline_fails('tabline', 'tabline=Something()', 'E992:')
+ call s:modeline_fails('titlestring', 'titlestring=Something()', 'E992:')
endfunc
diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim
index ef17209f74..945cd5a617 100644
--- a/src/nvim/testdir/test_normal.vim
+++ b/src/nvim/testdir/test_normal.vim
@@ -2552,6 +2552,21 @@ func Test_delete_until_paragraph()
bwipe!
endfunc
+func Test_message_when_using_ctrl_c()
+ " Make sure no buffers are changed.
+ %bwipe!
+
+ exe "normal \<C-C>"
+ call assert_match("Type :qa and press <Enter> to exit Nvim", Screenline(&lines))
+
+ new
+ cal setline(1, 'hi!')
+ exe "normal \<C-C>"
+ call assert_match("Type :qa! and press <Enter> to abandon all changes and exit Nvim", Screenline(&lines))
+
+ bwipe!
+endfunc
+
" Test for '[m', ']m', '[M' and ']M'
" Jumping to beginning and end of methods in Java-like languages
func Test_java_motion()
diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim
index 78afa929d0..a6ebd7b023 100644
--- a/src/nvim/testdir/test_options.vim
+++ b/src/nvim/testdir/test_options.vim
@@ -92,9 +92,6 @@ function! Test_path_keep_commas()
endfunction
func Test_filetype_valid()
- if !has('autocmd')
- return
- endif
set ft=valid_name
call assert_equal("valid_name", &filetype)
set ft=valid-name
@@ -180,6 +177,15 @@ func Test_thesaurus()
call Check_dir_option('thesaurus')
endfun
+func Test_complete()
+ " Trailing single backslash used to cause invalid memory access.
+ set complete=s\
+ new
+ call feedkeys("i\<C-N>\<Esc>", 'xt')
+ bwipe!
+ set complete&
+endfun
+
func Test_set_completion()
call feedkeys(":set di\<C-A>\<C-B>\"\<CR>", 'tx')
call assert_equal('"set dictionary diff diffexpr diffopt digraph directory display', @:)
@@ -216,6 +222,7 @@ func Test_set_completion()
call feedkeys(":set tags=./\\\\ dif\<C-A>\<C-B>\"\<CR>", 'tx')
call assert_equal('"set tags=./\\ diff diffexpr diffopt', @:)
+ set tags&
let &shellslash = shellslash
endfunc
@@ -224,7 +231,7 @@ func Test_set_errors()
call assert_fails('set backupcopy=', 'E474:')
call assert_fails('set regexpengine=3', 'E474:')
call assert_fails('set history=10001', 'E474:')
- call assert_fails('set numberwidth=11', 'E474:')
+ call assert_fails('set numberwidth=21', 'E474:')
call assert_fails('set colorcolumn=-a')
call assert_fails('set colorcolumn=a')
call assert_fails('set colorcolumn=1,')
@@ -305,14 +312,23 @@ func Test_set_ttytype()
endif
endfunc
-func Test_complete()
- " Trailing single backslash used to cause invalid memory access.
- set complete=s\
- new
- call feedkeys("i\<C-N>\<Esc>", 'xt')
- bwipe!
- set complete&
-endfun
+func Test_set_all()
+ set tw=75
+ set iskeyword=a-z,A-Z
+ set nosplitbelow
+ let out = execute('set all')
+ call assert_match('textwidth=75', out)
+ call assert_match('iskeyword=a-z,A-Z', out)
+ call assert_match('nosplitbelow', out)
+ set tw& iskeyword& splitbelow&
+endfunc
+
+func Test_set_values()
+ " The file is only generated when running "make test" in the src directory.
+ if filereadable('opt_test.vim')
+ source opt_test.vim
+ endif
+endfunc
func ResetIndentexpr()
set indentexpr=
@@ -327,6 +343,65 @@ func Test_set_indentexpr()
bwipe!
endfunc
+func Test_backupskip()
+ " Option 'backupskip' may contain several comma-separated path
+ " specifications if one or more of the environment variables TMPDIR, TMP,
+ " or TEMP is defined. To simplify testing, convert the string value into a
+ " list.
+ let bsklist = split(&bsk, ',')
+
+ if has("mac")
+ let found = (index(bsklist, '/private/tmp/*') >= 0)
+ call assert_true(found, '/private/tmp not in option bsk: ' . &bsk)
+ elseif has("unix")
+ let found = (index(bsklist, '/tmp/*') >= 0)
+ call assert_true(found, '/tmp not in option bsk: ' . &bsk)
+ endif
+
+ " If our test platform is Windows, the path(s) in option bsk will use
+ " backslash for the path separator and the components could be in short
+ " (8.3) format. As such, we need to replace the backslashes with forward
+ " slashes and convert the path components to long format. The expand()
+ " function will do this but it cannot handle comma-separated paths. This is
+ " why bsk was converted from a string into a list of strings above.
+ "
+ " One final complication is that the wildcard "/*" is at the end of each
+ " path and so expand() might return a list of matching files. To prevent
+ " this, we need to remove the wildcard before calling expand() and then
+ " append it afterwards.
+ if has('win32')
+ let item_nbr = 0
+ while item_nbr < len(bsklist)
+ let path_spec = bsklist[item_nbr]
+ let path_spec = strcharpart(path_spec, 0, strlen(path_spec)-2)
+ let path_spec = substitute(expand(path_spec), '\\', '/', 'g')
+ let bsklist[item_nbr] = path_spec . '/*'
+ let item_nbr += 1
+ endwhile
+ endif
+
+ " Option bsk will also include these environment variables if defined.
+ " If they're defined, verify they appear in the option value.
+ for var in ['$TMPDIR', '$TMP', '$TEMP']
+ if exists(var)
+ let varvalue = substitute(expand(var), '\\', '/', 'g')
+ let varvalue = substitute(varvalue, '/$', '', '')
+ let varvalue .= '/*'
+ let found = (index(bsklist, varvalue) >= 0)
+ call assert_true(found, var . ' (' . varvalue . ') not in option bsk: ' . &bsk)
+ endif
+ endfor
+
+ " Duplicates should be filtered out (option has P_NODUP)
+ let backupskip = &backupskip
+ set backupskip=
+ set backupskip+=/test/dir
+ set backupskip+=/other/dir
+ set backupskip+=/test/dir
+ call assert_equal('/test/dir,/other/dir', &backupskip)
+ let &backupskip = backupskip
+endfunc
+
func Test_copy_winopt()
set hidden
@@ -395,24 +470,6 @@ func Test_shortmess_F()
bwipe
endfunc
-func Test_set_all()
- set tw=75
- set iskeyword=a-z,A-Z
- set nosplitbelow
- let out = execute('set all')
- call assert_match('textwidth=75', out)
- call assert_match('iskeyword=a-z,A-Z', out)
- call assert_match('nosplitbelow', out)
- set tw& iskeyword& splitbelow&
-endfunc
-
-func Test_set_values()
- " The file is only generated when running "make test" in the src directory.
- if filereadable('opt_test.vim')
- source opt_test.vim
- endif
-endfunc
-
func Test_shortmess_F2()
e file1
e file2
diff --git a/src/nvim/testdir/test_partial.vim b/src/nvim/testdir/test_partial.vim
index de5c26c2dd..590e18e024 100644
--- a/src/nvim/testdir/test_partial.vim
+++ b/src/nvim/testdir/test_partial.vim
@@ -285,6 +285,11 @@ func Test_get_partial_items()
call assert_equal('MyDictFunc', get(Func, 'name'))
call assert_equal([], get(Func, 'args'))
call assert_true(empty( get(Func, 'dict')))
+
+ let P = function('substitute', ['hello there', 'there'])
+ let dict = {'partial has': 'no dict'}
+ call assert_equal(dict, get(P, 'dict', dict))
+ call assert_equal(0, get(l:P, 'dict'))
endfunc
func Test_compare_partials()
diff --git a/src/nvim/testdir/test_popup.vim b/src/nvim/testdir/test_popup.vim
index 96f4bfc71b..98e9de9ffb 100644
--- a/src/nvim/testdir/test_popup.vim
+++ b/src/nvim/testdir/test_popup.vim
@@ -641,7 +641,7 @@ func Test_popup_and_preview_autocommand()
norm! gt
call assert_equal(0, &previewwindow)
norm! gT
- call assert_equal(12, tabpagenr('$'))
+ call assert_equal(10, tabpagenr('$'))
tabonly
pclose
augroup MyBufAdd
@@ -841,4 +841,38 @@ func Test_popup_complete_info_02()
bwipe!
endfunc
+func Test_CompleteChanged()
+ new
+ call setline(1, ['foo', 'bar', 'foobar', ''])
+ set complete=. completeopt=noinsert,noselect,menuone
+ function! OnPumChange()
+ let g:event = copy(v:event)
+ let g:item = get(v:event, 'completed_item', {})
+ let g:word = get(g:item, 'word', v:null)
+ endfunction
+ augroup AAAAA_Group
+ au!
+ autocmd CompleteChanged * :call OnPumChange()
+ augroup END
+ 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 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')
+ call assert_equal('foobar', g:word)
+ call feedkeys("a\<C-N>\<C-N>\<C-N>\<C-N>\<C-E>", 'tx')
+ call assert_equal(v:null, g:word)
+ call feedkeys("a\<C-N>\<C-N>\<C-N>\<C-N>\<C-P>", 'tx')
+ call assert_equal('foobar', g:word)
+
+ autocmd! AAAAA_Group
+ set complete& completeopt&
+ delfunc! OnPumchange
+ bw!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_profile.vim b/src/nvim/testdir/test_profile.vim
index 8996e86b43..1aa4d5eaf8 100644
--- a/src/nvim/testdir/test_profile.vim
+++ b/src/nvim/testdir/test_profile.vim
@@ -5,6 +5,8 @@ endif
func Test_profile_func()
let lines = [
+ \ 'profile start Xprofile_func.log',
+ \ 'profile func Foo*"',
\ "func! Foo1()",
\ "endfunc",
\ "func! Foo2()",
@@ -33,9 +35,7 @@ func Test_profile_func()
call writefile(lines, 'Xprofile_func.vim')
call system(v:progpath
- \ . ' -es -u NONE -U NONE -i NONE --noplugin'
- \ . ' -c "profile start Xprofile_func.log"'
- \ . ' -c "profile func Foo*"'
+ \ . ' -es --clean'
\ . ' -c "so Xprofile_func.vim"'
\ . ' -c "qall!"')
call assert_equal(0, v:shell_error)
@@ -97,7 +97,7 @@ func Test_profile_file()
call writefile(lines, 'Xprofile_file.vim')
call system(v:progpath
- \ . ' -es -u NONE -U NONE -i NONE --noplugin'
+ \ . ' -es --clean'
\ . ' -c "profile start Xprofile_file.log"'
\ . ' -c "profile file Xprofile_file.vim"'
\ . ' -c "so Xprofile_file.vim"'
@@ -152,17 +152,17 @@ func Test_profile_file_with_cont()
let lines = readfile('Xprofile_file.log')
call assert_equal(11, len(lines))
- call assert_match('^SCRIPT .*Xprofile_file.vim$', lines[0])
- call assert_equal('Sourced 1 time', lines[1])
- call assert_match('^Total time:\s\+\d\+\.\d\+$', lines[2])
- call assert_match('^ Self time:\s\+\d\+\.\d\+$', lines[3])
- call assert_equal('', lines[4])
- call assert_equal('count total (s) self (s)', lines[5])
- call assert_match(' 1 0.\d\+ echo "hello', lines[6])
- call assert_equal(' \ world"', lines[7])
- call assert_match(' 1 0.\d\+ echo "foo ', lines[8])
- call assert_equal(' \bar"', lines[9])
- call assert_equal('', lines[10])
+ call assert_match('^SCRIPT .*Xprofile_file.vim$', lines[0])
+ call assert_equal('Sourced 1 time', lines[1])
+ call assert_match('^Total time:\s\+\d\+\.\d\+$', lines[2])
+ call assert_match('^ Self time:\s\+\d\+\.\d\+$', lines[3])
+ call assert_equal('', lines[4])
+ call assert_equal('count total (s) self (s)', lines[5])
+ call assert_match(' 1 0.\d\+ echo "hello', lines[6])
+ call assert_equal(' \ world"', lines[7])
+ call assert_match(' 1 0.\d\+ echo "foo ', lines[8])
+ call assert_equal(' \bar"', lines[9])
+ call assert_equal('', lines[10])
call delete('Xprofile_file.vim')
call delete('Xprofile_file.log')
@@ -222,3 +222,73 @@ func Test_profile_truncate_mbyte()
call delete('Xprofile_file.vim')
call delete('Xprofile_file.log')
endfunc
+
+func Test_profdel_func()
+ let lines = [
+ \ 'profile start Xprofile_file.log',
+ \ 'func! Foo1()',
+ \ 'endfunc',
+ \ 'func! Foo2()',
+ \ 'endfunc',
+ \ 'func! Foo3()',
+ \ 'endfunc',
+ \ '',
+ \ 'profile func Foo1',
+ \ 'profile func Foo2',
+ \ 'call Foo1()',
+ \ 'call Foo2()',
+ \ '',
+ \ 'profile func Foo3',
+ \ 'profdel func Foo2',
+ \ 'profdel func Foo3',
+ \ 'call Foo1()',
+ \ 'call Foo2()',
+ \ 'call Foo3()' ]
+ call writefile(lines, 'Xprofile_file.vim')
+ call system(v:progpath . ' -es --clean -c "so Xprofile_file.vim" -c q')
+ call assert_equal(0, v:shell_error)
+
+ let lines = readfile('Xprofile_file.log')
+ call assert_equal(24, len(lines))
+
+ " Check that:
+ " - Foo1() is called twice (profdel not invoked)
+ " - Foo2() is called once (profdel invoked after it was called)
+ " - Foo3() is not called (profdel invoked before it was called)
+ call assert_equal('FUNCTION Foo1()', lines[0])
+ call assert_equal('Called 2 times', lines[1])
+ call assert_equal('FUNCTION Foo2()', lines[7])
+ call assert_equal('Called 1 time', lines[8])
+ call assert_equal('FUNCTIONS SORTED ON TOTAL TIME', lines[14])
+ call assert_equal('FUNCTIONS SORTED ON SELF TIME', lines[19])
+
+ call delete('Xprofile_file.vim')
+ call delete('Xprofile_file.log')
+endfunc
+
+func Test_profdel_star()
+ " Foo() is invoked once before and once after 'profdel *'.
+ " So profiling should report it only once.
+ let lines = [
+ \ 'profile start Xprofile_file.log',
+ \ 'func! Foo()',
+ \ 'endfunc',
+ \ 'profile func Foo',
+ \ 'call Foo()',
+ \ 'profdel *',
+ \ 'call Foo()' ]
+ call writefile(lines, 'Xprofile_file.vim')
+ call system(v:progpath . ' -es --clean -c "so Xprofile_file.vim" -c q')
+ call assert_equal(0, v:shell_error)
+
+ let lines = readfile('Xprofile_file.log')
+ call assert_equal(15, len(lines))
+
+ call assert_equal('FUNCTION Foo()', lines[0])
+ call assert_equal('Called 1 time', lines[1])
+ call assert_equal('FUNCTIONS SORTED ON TOTAL TIME', lines[7])
+ call assert_equal('FUNCTIONS SORTED ON SELF TIME', lines[11])
+
+ call delete('Xprofile_file.vim')
+ call delete('Xprofile_file.log')
+endfunc
diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim
index 16fb86ea08..ce0b8f1be8 100644
--- a/src/nvim/testdir/test_quickfix.vim
+++ b/src/nvim/testdir/test_quickfix.vim
@@ -3372,3 +3372,49 @@ func Test_lbuffer_with_bwipe()
au!
augroup END
endfunc
+
+" Tests for the ':filter /pat/ clist' command
+func Test_filter_clist()
+ cexpr ['Xfile1:10:10:Line 10', 'Xfile2:15:15:Line 15']
+ call assert_equal([' 2 Xfile2:15 col 15: Line 15'],
+ \ split(execute('filter /Line 15/ clist'), "\n"))
+ call assert_equal([' 1 Xfile1:10 col 10: Line 10'],
+ \ split(execute('filter /Xfile1/ clist'), "\n"))
+ call assert_equal([], split(execute('filter /abc/ clist'), "\n"))
+
+ call setqflist([{'module' : 'abc', 'pattern' : 'pat1'},
+ \ {'module' : 'pqr', 'pattern' : 'pat2'}], ' ')
+ call assert_equal([' 2 pqr:pat2: '],
+ \ split(execute('filter /pqr/ clist'), "\n"))
+ call assert_equal([' 1 abc:pat1: '],
+ \ split(execute('filter /pat1/ clist'), "\n"))
+endfunc
+
+func Test_setloclist_in_aucmd()
+ " This was using freed memory.
+ augroup nasty
+ au * * call setloclist(0, [], 'f')
+ augroup END
+ lexpr "x"
+ augroup nasty
+ au!
+ augroup END
+endfunc
+
+" Tests for the "CTRL-W <CR>" command.
+func Xview_result_split_tests(cchar)
+ call s:setup_commands(a:cchar)
+
+ " Test that "CTRL-W <CR>" in a qf/ll window fails with empty list.
+ call g:Xsetlist([])
+ Xopen
+ let l:win_count = winnr('$')
+ call assert_fails('execute "normal! \<C-W>\<CR>"', 'E42')
+ call assert_equal(l:win_count, winnr('$'))
+ Xclose
+endfunc
+
+func Test_view_result_split()
+ call Xview_result_split_tests('c')
+ call Xview_result_split_tests('l')
+endfunc
diff --git a/src/nvim/testdir/test_recover.vim b/src/nvim/testdir/test_recover.vim
index beecb4cd0d..09c8d1cda6 100644
--- a/src/nvim/testdir/test_recover.vim
+++ b/src/nvim/testdir/test_recover.vim
@@ -6,6 +6,13 @@ func Test_recover_root_dir()
set dir=/
call assert_fails('recover', 'E305:')
close!
+
+ if has('win32')
+ " can write in / directory on MS-Windows
+ let &directory = 'F:\\'
+ elseif filewritable('/') == 2
+ set dir=/notexist/
+ endif
call assert_fails('split Xtest', 'E303:')
set dir&
endfunc
diff --git a/src/nvim/testdir/test_regexp_latin.vim b/src/nvim/testdir/test_regexp_latin.vim
index de209fa9ec..b5e99b0ed3 100644
--- a/src/nvim/testdir/test_regexp_latin.vim
+++ b/src/nvim/testdir/test_regexp_latin.vim
@@ -85,3 +85,16 @@ func Test_multi_failure()
call assert_fails('/a\{a}', 'E870:')
set re=0
endfunc
+
+func Test_recursive_addstate()
+ " This will call addstate() recursively until it runs into the limit.
+ let lnum = search('\v((){328}){389}')
+ call assert_equal(0, lnum)
+endfunc
+
+func Test_out_of_memory()
+ new
+ s/^/,n
+ " This will be slow...
+ call assert_fails('call search("\\v((n||<)+);")', 'E363:')
+endfunc
diff --git a/src/nvim/testdir/test_regexp_utf8.vim b/src/nvim/testdir/test_regexp_utf8.vim
index 97638e9aac..e06c7d6368 100644
--- a/src/nvim/testdir/test_regexp_utf8.vim
+++ b/src/nvim/testdir/test_regexp_utf8.vim
@@ -183,3 +183,12 @@ func Test_large_class()
call assert_equal(1, "\u3042" =~# '[\u3000-\u4000]')
set re=0
endfunc
+
+func Test_optmatch_toolong()
+ set re=1
+ " Can only handle about 8000 characters.
+ let pat = '\\%[' .. repeat('x', 9000) .. ']'
+ call assert_fails('call match("abc def", "' .. pat .. '")', 'E339:')
+ set re=0
+endfunc
+
diff --git a/src/nvim/testdir/test_registers.vim b/src/nvim/testdir/test_registers.vim
index d7b6de5652..298268a994 100644
--- a/src/nvim/testdir/test_registers.vim
+++ b/src/nvim/testdir/test_registers.vim
@@ -63,3 +63,22 @@ func Test_display_registers()
bwipe!
endfunc
+
+" Check that replaying a typed sequence does not use an Esc and following
+" characters as an escape sequence.
+func Test_recording_esc_sequence()
+ new
+ try
+ let save_F2 = &t_F2
+ catch
+ endtry
+ let t_F2 = "\<Esc>OQ"
+ call feedkeys("qqiTest\<Esc>", "xt")
+ call feedkeys("OQuirk\<Esc>q", "xt")
+ call feedkeys("Go\<Esc>@q", "xt")
+ call assert_equal(['Quirk', 'Test', 'Quirk', 'Test'], getline(1, 4))
+ bwipe!
+ if exists('save_F2')
+ let &t_F2 = save_F2
+ endif
+endfunc
diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim
index 87cad241e2..8e284ba042 100644
--- a/src/nvim/testdir/test_search.vim
+++ b/src/nvim/testdir/test_search.vim
@@ -1,5 +1,7 @@
" Test for the search command
+source shared.vim
+
func Test_search_cmdline()
" See test/functional/legacy/search_spec.lua
throw 'skipped: Nvim does not support test_override()'
@@ -288,16 +290,53 @@ func Test_searchpair()
new
call setline(1, ['other code here', '', '[', '" cursor here', ']'])
4
- let a=searchpair('\[','',']','bW')
+ let a = searchpair('\[','',']','bW')
call assert_equal(3, a)
set nomagic
4
- let a=searchpair('\[','',']','bW')
+ let a = searchpair('\[','',']','bW')
call assert_equal(3, a)
set magic
q!
endfunc
+func Test_searchpair_errors()
+ call assert_fails("call searchpair([0], 'middle', 'end', 'bW', 'skip', 99, 100)", 'E730: using List as a String')
+ call assert_fails("call searchpair('start', {-> 0}, 'end', 'bW', 'skip', 99, 100)", 'E729: using Funcref as a String')
+ call assert_fails("call searchpair('start', 'middle', {'one': 1}, 'bW', 'skip', 99, 100)", 'E731: using Dictionary as a String')
+ call assert_fails("call searchpair('start', 'middle', 'end', 'flags', 'skip', 99, 100)", 'E475: Invalid argument: flags')
+ call assert_fails("call searchpair('start', 'middle', 'end', 'bW', 0, 99, 100)", 'E475: Invalid argument: 0')
+ call assert_fails("call searchpair('start', 'middle', 'end', 'bW', 'func', -99, 100)", 'E475: Invalid argument: -99')
+ call assert_fails("call searchpair('start', 'middle', 'end', 'bW', 'func', 99, -100)", 'E475: Invalid argument: -100')
+endfunc
+
+func Test_searchpair_skip()
+ func Zero()
+ return 0
+ endfunc
+ func Partial(x)
+ return a:x
+ endfunc
+ new
+ call setline(1, ['{', 'foo', 'foo', 'foo', '}'])
+ 3 | call assert_equal(1, searchpair('{', '', '}', 'bWn', ''))
+ 3 | call assert_equal(1, searchpair('{', '', '}', 'bWn', '0'))
+ 3 | call assert_equal(1, searchpair('{', '', '}', 'bWn', {-> 0}))
+ 3 | call assert_equal(1, searchpair('{', '', '}', 'bWn', function('Zero')))
+ 3 | call assert_equal(1, searchpair('{', '', '}', 'bWn', function('Partial', [0])))
+ bw!
+endfunc
+
+func Test_searchpair_leak()
+ new
+ call setline(1, 'if one else another endif')
+
+ " The error in the skip expression caused memory to leak.
+ call assert_fails("call searchpair('\\<if\\>', '\\<else\\>', '\\<endif\\>', '', '\"foo\" 2')", 'E15:')
+
+ bwipe!
+endfunc
+
func Test_searchc()
" These commands used to cause memory overflow in searchc().
new
diff --git a/src/nvim/testdir/test_source.vim b/src/nvim/testdir/test_source.vim
new file mode 100644
index 0000000000..09baec0b7d
--- /dev/null
+++ b/src/nvim/testdir/test_source.vim
@@ -0,0 +1,48 @@
+" Tests for the :source command.
+
+func Test_source_autocmd()
+ call writefile([
+ \ 'let did_source = 1',
+ \ ], 'Xsourced')
+ au SourcePre *source* let did_source_pre = 1
+ au SourcePost *source* let did_source_post = 1
+
+ source Xsourced
+
+ call assert_equal(g:did_source, 1)
+ call assert_equal(g:did_source_pre, 1)
+ call assert_equal(g:did_source_post, 1)
+
+ call delete('Xsourced')
+ au! SourcePre
+ au! SourcePost
+ unlet g:did_source
+ unlet g:did_source_pre
+ unlet g:did_source_post
+endfunc
+
+func Test_source_cmd()
+ au SourceCmd *source* let did_source = expand('<afile>')
+ au SourcePre *source* let did_source_pre = 2
+ au SourcePost *source* let did_source_post = 2
+
+ source Xsourced
+
+ call assert_equal(g:did_source, 'Xsourced')
+ call assert_false(exists('g:did_source_pre'))
+ call assert_equal(g:did_source_post, 2)
+
+ au! SourceCmd
+ au! SourcePre
+ au! SourcePost
+endfunc
+
+func Test_source_sandbox()
+ new
+ call writefile(["Ohello\<Esc>"], 'Xsourcehello')
+ source! Xsourcehello | echo
+ call assert_equal('hello', getline(1))
+ call assert_fails('sandbox source! Xsourcehello', 'E48:')
+ bwipe!
+ call delete('Xsourcehello')
+endfunc
diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim
index b3438cc649..230cb72335 100644
--- a/src/nvim/testdir/test_spell.vim
+++ b/src/nvim/testdir/test_spell.vim
@@ -1,4 +1,5 @@
" Test spell checking
+" Note: this file uses latin1 encoding, but is used with utf-8 encoding.
if !has('spell')
finish
@@ -68,6 +69,47 @@ func Test_z_equal_on_invalid_utf8_word()
bwipe!
endfunc
+" Test spellbadword() with argument
+func Test_spellbadword()
+ set spell
+
+ call assert_equal(['bycycle', 'bad'], spellbadword('My bycycle.'))
+ call assert_equal(['another', 'caps'], spellbadword('A sentence. another sentence'))
+
+ set spelllang=en
+ call assert_equal(['', ''], spellbadword('centre'))
+ call assert_equal(['', ''], spellbadword('center'))
+ set spelllang=en_us
+ call assert_equal(['centre', 'local'], spellbadword('centre'))
+ call assert_equal(['', ''], spellbadword('center'))
+ set spelllang=en_gb
+ call assert_equal(['', ''], spellbadword('centre'))
+ call assert_equal(['center', 'local'], spellbadword('center'))
+
+ " Create a small word list to test that spellbadword('...')
+ " can return ['...', 'rare'].
+ e Xwords
+ insert
+foo
+foobar/?
+.
+ w!
+ mkspell! Xwords.spl Xwords
+ set spelllang=Xwords.spl
+ call assert_equal(['foobar', 'rare'], spellbadword('foo foobar'))
+
+ " Typo should not be detected without the 'spell' option.
+ set spelllang=en_gb nospell
+ call assert_equal(['', ''], spellbadword('centre'))
+ call assert_equal(['', ''], spellbadword('My bycycle.'))
+ call assert_equal(['', ''], spellbadword('A sentence. another sentence'))
+
+ call delete('Xwords.spl')
+ call delete('Xwords')
+ set spelllang&
+ set spell&
+endfunc
+
func Test_spellreall()
new
set spell
@@ -351,6 +393,18 @@ func Test_zeq_crash()
bwipe!
endfunc
+" Check handling a word longer than MAXWLEN.
+func Test_spell_long_word()
+ set enc=utf-8
+ new
+ call setline(1, "d\xCC\xB4\xCC\xBD\xCD\x88\xCD\x94a\xCC\xB5\xCD\x84\xCD\x84\xCC\xA8\xCD\x9Cr\xCC\xB5\xCC\x8E\xCD\x85\xCD\x85k\xCC\xB6\xCC\x89\xCC\x9D \xCC\xB6\xCC\x83\xCC\x8F\xCC\xA4\xCD\x8Ef\xCC\xB7\xCC\x81\xCC\x80\xCC\xA9\xCC\xB0\xCC\xAC\xCC\xA2\xCD\x95\xCD\x87\xCD\x8D\xCC\x9E\xCD\x99\xCC\xAD\xCC\xAB\xCC\x97\xCC\xBBo\xCC\xB6\xCC\x84\xCC\x95\xCC\x8C\xCC\x8B\xCD\x9B\xCD\x9C\xCC\xAFr\xCC\xB7\xCC\x94\xCD\x83\xCD\x97\xCC\x8C\xCC\x82\xCD\x82\xCD\x80\xCD\x91\xCC\x80\xCC\xBE\xCC\x82\xCC\x8F\xCC\xA3\xCD\x85\xCC\xAE\xCD\x8D\xCD\x99\xCC\xBC\xCC\xAB\xCC\xA7\xCD\x88c\xCC\xB7\xCD\x83\xCC\x84\xCD\x92\xCC\x86\xCC\x83\xCC\x88\xCC\x92\xCC\x94\xCC\xBE\xCC\x9D\xCC\xAF\xCC\x98\xCC\x9D\xCC\xBB\xCD\x8E\xCC\xBB\xCC\xB3\xCC\xA3\xCD\x8E\xCD\x99\xCC\xA5\xCC\xAD\xCC\x99\xCC\xB9\xCC\xAE\xCC\xA5\xCC\x9E\xCD\x88\xCC\xAE\xCC\x9E\xCC\xA9\xCC\x97\xCC\xBC\xCC\x99\xCC\xA5\xCD\x87\xCC\x97\xCD\x8E\xCD\x94\xCC\x99\xCC\x9D\xCC\x96\xCD\x94\xCC\xAB\xCC\xA7\xCC\xA5\xCC\x98\xCC\xBB\xCC\xAF\xCC\xABe\xCC\xB7\xCC\x8E\xCC\x82\xCD\x86\xCD\x9B\xCC\x94\xCD\x83\xCC\x85\xCD\x8A\xCD\x8C\xCC\x8B\xCD\x92\xCD\x91\xCC\x8F\xCC\x81\xCD\x95\xCC\xA2\xCC\xB9\xCC\xB2\xCD\x9C\xCC\xB1\xCC\xA6\xCC\xB3\xCC\xAF\xCC\xAE\xCC\x9C\xCD\x99s\xCC\xB8\xCC\x8C\xCC\x8E\xCC\x87\xCD\x81\xCD\x82\xCC\x86\xCD\x8C\xCD\x8C\xCC\x8B\xCC\x84\xCC\x8C\xCD\x84\xCD\x9B\xCD\x86\xCC\x93\xCD\x90\xCC\x85\xCC\x94\xCD\x98\xCD\x84\xCD\x92\xCD\x8B\xCC\x90\xCC\x83\xCC\x8F\xCD\x84\xCD\x81\xCD\x9B\xCC\x90\xCD\x81\xCC\x8F\xCC\xBD\xCC\x88\xCC\xBF\xCC\x88\xCC\x84\xCC\x8E\xCD\x99\xCD\x94\xCC\x99\xCD\x99\xCC\xB0\xCC\xA8\xCC\xA3\xCC\xA8\xCC\x96\xCC\x99\xCC\xAE\xCC\xBC\xCC\x99\xCD\x9A\xCC\xB2\xCC\xB1\xCC\x9F\xCC\xBB\xCC\xA6\xCD\x85\xCC\xAA\xCD\x89\xCC\x9D\xCC\x99\xCD\x96\xCC\xB1\xCC\xB1\xCC\x99\xCC\xA6\xCC\xA5\xCD\x95\xCC\xB2\xCC\xA0\xCD\x99 within")
+ set spell spelllang=en
+ redraw
+ redraw!
+ bwipe!
+ set nospell
+endfunc
+
func LoadAffAndDic(aff_contents, dic_contents)
throw 'skipped: Nvim does not support enc=latin1'
set enc=latin1
diff --git a/src/nvim/testdir/test_startup.vim b/src/nvim/testdir/test_startup.vim
index 873f2e8731..b5647b610d 100644
--- a/src/nvim/testdir/test_startup.vim
+++ b/src/nvim/testdir/test_startup.vim
@@ -339,6 +339,88 @@ func Test_A_F_H_arg()
call delete('Xtestout')
endfunc
+func Test_invalid_args()
+ if !has('unix') || has('gui_running')
+ " can't get output of Vim.
+ return
+ endif
+
+ for opt in ['-Y', '--does-not-exist']
+ let out = split(system(GetVimCommand() .. ' ' .. opt), "\n")
+ call assert_equal(1, v:shell_error)
+ call assert_equal('nvim: Unknown option argument: "' .. opt .. '"', out[0])
+ call assert_equal('More info with "nvim -h"', out[1])
+ endfor
+
+ for opt in ['-c', '-i', '-s', '-t', '-u', '-U', '-w', '-W', '--cmd', '--startuptime']
+ let out = split(system(GetVimCommand() .. ' ' .. opt), "\n")
+ call assert_equal(1, v:shell_error)
+ call assert_equal('nvim: Argument missing after: "' .. opt .. '"', out[0])
+ call assert_equal('More info with "nvim -h"', out[1])
+ endfor
+
+ if has('clientserver')
+ " FIXME: need to add --servername to this list
+ " but it causes vim-8.1.1282 to crash!
+ for opt in ['--remote', '--remote-send', '--remote-silent', '--remote-expr',
+ \ '--remote-tab', '--remote-tab-wait',
+ \ '--remote-tab-wait-silent', '--remote-tab-silent',
+ \ '--remote-wait', '--remote-wait-silent',
+ \ ]
+ let out = split(system(GetVimCommand() .. ' ' .. opt), "\n")
+ call assert_equal(1, v:shell_error)
+ call assert_match('^VIM - Vi IMproved .* (.*)$', out[0])
+ call assert_equal('Argument missing after: "' .. opt .. '"', out[1])
+ call assert_equal('More info with: "vim -h"', out[2])
+ endfor
+ endif
+
+ " FIXME: commented out as this causes vim-8.1.1282 to crash!
+ "if has('clipboard')
+ " let out = split(system(GetVimCommand() .. ' --display'), "\n")
+ " call assert_equal(1, v:shell_error)
+ " call assert_match('^VIM - Vi IMproved .* (.*)$', out[0])
+ " call assert_equal('Argument missing after: "--display"', out[1])
+ " call assert_equal('More info with: "vim -h"', out[2])
+ "endif
+
+ let out = split(system(GetVimCommand() .. ' -ix'), "\n")
+ call assert_equal(1, v:shell_error)
+ call assert_equal('nvim: Garbage after option argument: "-ix"', out[0])
+ call assert_equal('More info with "nvim -h"', out[1])
+
+ " Not an error in Nvim. The "-" file is allowed with -t, -q, or [file].
+ let out = split(system(GetVimCommand() .. ' - xxx -cq'), "\n")
+ call assert_equal(0, v:shell_error)
+
+ " Detect invalid repeated arguments '-t foo -t foo", '-q foo -q foo'.
+ for opt in ['-t', '-q']
+ let out = split(system(GetVimCommand() .. repeat(' ' .. opt .. ' foo', 2)), "\n")
+ call assert_equal(1, v:shell_error)
+ call assert_equal('nvim: Too many edit arguments: "' .. opt .. '"', out[0])
+ call assert_equal('More info with "nvim -h"', out[1])
+ endfor
+
+ for opt in [' -cq', ' --cmd q', ' +', ' -S foo']
+ let out = split(system(GetVimCommand() .. repeat(opt, 11)), "\n")
+ call assert_equal(1, v:shell_error)
+ " FIXME: The error message given by Vim is not ideal in case of repeated
+ " -S foo since it does not mention -S.
+ call assert_equal('nvim: Too many "+command", "-c command" or "--cmd command" arguments', out[0])
+ call assert_equal('More info with "nvim -h"', out[1])
+ endfor
+
+ if has('gui_gtk')
+ for opt in ['--socketid x', '--socketid 0xg']
+ let out = split(system(GetVimCommand() .. ' ' .. opt), "\n")
+ call assert_equal(1, v:shell_error)
+ call assert_match('^VIM - Vi IMproved .* (.*)$', out[0])
+ call assert_equal('Invalid argument for: "--socketid"', out[1])
+ call assert_equal('More info with: "vim -h"', out[2])
+ endfor
+ endif
+endfunc
+
func Test_file_args()
let after = [
\ 'call writefile(argv(), "Xtestout")',
diff --git a/src/nvim/testdir/test_statusline.vim b/src/nvim/testdir/test_statusline.vim
index b86340a23a..ea29da0262 100644
--- a/src/nvim/testdir/test_statusline.vim
+++ b/src/nvim/testdir/test_statusline.vim
@@ -86,8 +86,11 @@ func Test_statusline()
call assert_match('^Xstatusline\s*$', s:get_statusline())
" %F: Full path to the file in the buffer.
+ let shellslash = &shellslash
+ set shellslash
set statusline=%F
call assert_match('/testdir/Xstatusline\s*$', s:get_statusline())
+ let &shellslash = shellslash
" %h: Help buffer flag, text is "[help]".
" %H: Help buffer flag, text is ",HLP".
diff --git a/src/nvim/testdir/test_suspend.vim b/src/nvim/testdir/test_suspend.vim
index e569e49055..ef5a96bd72 100644
--- a/src/nvim/testdir/test_suspend.vim
+++ b/src/nvim/testdir/test_suspend.vim
@@ -2,6 +2,20 @@
source shared.vim
+func CheckSuspended(buf, fileExists)
+ call WaitForAssert({-> assert_match('[$#] $', term_getline(a:buf, '.'))})
+
+ if a:fileExists
+ call assert_equal(['foo'], readfile('Xfoo'))
+ else
+ " Without 'autowrite', buffer should not be written.
+ call assert_equal(0, filereadable('Xfoo'))
+ endif
+
+ call term_sendkeys(a:buf, "fg\<CR>\<C-L>")
+ call WaitForAssert({-> assert_equal(' 1 foo', term_getline(a:buf, '.'))})
+endfunc
+
func Test_suspend()
if !has('terminal') || !executable('/bin/sh')
return
@@ -26,13 +40,7 @@ func Test_suspend()
\ "\<C-Z>"]
" Suspend and wait for shell prompt.
call term_sendkeys(buf, suspend_cmd)
- call WaitForAssert({-> assert_match('[$#] $', term_getline(buf, '.'))})
-
- " Without 'autowrite', buffer should not be written.
- call assert_equal(0, filereadable('Xfoo'))
-
- call term_sendkeys(buf, "fg\<CR>")
- call WaitForAssert({-> assert_equal(' 1 foo', term_getline(buf, '.'))})
+ call CheckSuspended(buf, 0)
endfor
" Test that :suspend! with 'autowrite' writes content of buffers if modified.
@@ -40,14 +48,13 @@ func Test_suspend()
call assert_equal(0, filereadable('Xfoo'))
call term_sendkeys(buf, ":suspend\<CR>")
" Wait for shell prompt.
- call WaitForAssert({-> assert_match('[$#] $', term_getline(buf, '.'))})
- call assert_equal(['foo'], readfile('Xfoo'))
- call term_sendkeys(buf, "fg\<CR>")
- call WaitForAssert({-> assert_equal(' 1 foo', term_getline(buf, '.'))})
+ call CheckSuspended(buf, 1)
" Quit gracefully to dump coverage information.
call term_sendkeys(buf, ":qall!\<CR>")
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)
exe buf . 'bwipe!'
diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim
index 850947f89f..a3de879b2a 100644
--- a/src/nvim/testdir/test_syntax.vim
+++ b/src/nvim/testdir/test_syntax.vim
@@ -466,6 +466,8 @@ func Test_bg_detection()
set bg=dark
hi Normal ctermbg=12
call assert_equal('dark', &bg)
+
+ hi Normal ctermbg=NONE
endfunc
fun Test_synstack_synIDtrans()
diff --git a/src/nvim/testdir/test_tabline.vim b/src/nvim/testdir/test_tabline.vim
index 6c7a02d650..f24552088b 100644
--- a/src/nvim/testdir/test_tabline.vim
+++ b/src/nvim/testdir/test_tabline.vim
@@ -1,19 +1,22 @@
-function! TablineWithCaughtError()
+
+source shared.vim
+
+func TablineWithCaughtError()
let s:func_in_tabline_called = 1
try
call eval('unknown expression')
catch
endtry
return ''
-endfunction
+endfunc
-function! TablineWithError()
+func TablineWithError()
let s:func_in_tabline_called = 1
call eval('unknown expression')
return ''
-endfunction
+endfunc
-function! Test_caught_error_in_tabline()
+func Test_caught_error_in_tabline()
let showtabline_save = &showtabline
set showtabline=2
let s:func_in_tabline_called = 0
@@ -24,9 +27,9 @@ function! Test_caught_error_in_tabline()
call assert_equal(tabline, &tabline)
set tabline=
let &showtabline = showtabline_save
-endfunction
+endfunc
-function! Test_tabline_will_be_disabled_with_error()
+func Test_tabline_will_be_disabled_with_error()
let showtabline_save = &showtabline
set showtabline=2
let s:func_in_tabline_called = 0
@@ -40,4 +43,24 @@ function! Test_tabline_will_be_disabled_with_error()
call assert_equal('', &tabline)
set tabline=
let &showtabline = showtabline_save
-endfunction
+endfunc
+
+func Test_redrawtabline()
+ if has('gui')
+ set guioptions-=e
+ endif
+ let showtabline_save = &showtabline
+ set showtabline=2
+ set tabline=%{bufnr('$')}
+ edit Xtabline1
+ edit Xtabline2
+ redraw
+ call assert_match(bufnr('$') . '', Screenline(1))
+ au BufAdd * redrawtabline
+ badd Xtabline3
+ call assert_match(bufnr('$') . '', Screenline(1))
+
+ set tabline=
+ let &showtabline = showtabline_save
+ au! Bufadd
+endfunc
diff --git a/src/nvim/testdir/test_tagcase.vim b/src/nvim/testdir/test_tagcase.vim
index 833cb9f990..53c08ccf1e 100644
--- a/src/nvim/testdir/test_tagcase.vim
+++ b/src/nvim/testdir/test_tagcase.vim
@@ -44,6 +44,7 @@ func Test_tagcase()
endfor
call delete('Xtags')
+ set tags&
set ic&
setg tc&
setl tc&
diff --git a/src/nvim/testdir/test_tagjump.vim b/src/nvim/testdir/test_tagjump.vim
index 21ea00cab7..ce527a5e1d 100644
--- a/src/nvim/testdir/test_tagjump.vim
+++ b/src/nvim/testdir/test_tagjump.vim
@@ -20,6 +20,7 @@ func Test_cancel_ptjump()
call assert_equal(2, winnr('$'))
call delete('Xtags')
+ set tags&
quit
endfunc
@@ -104,6 +105,7 @@ func Test_tagjump_switchbuf()
enew | only
call delete('Xfile1')
call delete('Xtags')
+ set tags&
set switchbuf&vim
endfunc
@@ -424,7 +426,7 @@ func Test_tagnr_recall()
tag
call assert_equal(bufname('%'), 'Xtest.h')
- set tag&
+ set tags&
call delete('Xtags')
bwipe Xtest.h
bwipe Xtest.c
@@ -460,6 +462,7 @@ func Test_tag_line_toolong()
endtry
call assert_equal('Ignoring long line in tags file', split(execute('messages'), '\n')[-1])
call delete('Xtags')
+ set tags&
let &verbose = old_vbs
endfunc
diff --git a/src/nvim/testdir/test_taglist.vim b/src/nvim/testdir/test_taglist.vim
index ea0a6b9678..eb64e59509 100644
--- a/src/nvim/testdir/test_taglist.vim
+++ b/src/nvim/testdir/test_taglist.vim
@@ -5,7 +5,9 @@ func Test_taglist()
\ "FFoo\tXfoo\t1",
\ "FBar\tXfoo\t2",
\ "BFoo\tXbar\t1",
- \ "BBar\tXbar\t2"
+ \ "BBar\tXbar\t2",
+ \ "Kindly\tXbar\t3;\"\tv\tfile:",
+ \ "Command\tXbar\tcall cursor(3, 4)|;\"\td",
\ ], 'Xtags')
set tags=Xtags
split Xtext
@@ -15,7 +17,20 @@ 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 cmd = taglist("Command")
+ call assert_equal(1, len(cmd))
+ call assert_equal('d', cmd[0]['kind'])
+ call assert_equal('call cursor(3, 4)', cmd[0]['cmd'])
+
call delete('Xtags')
+ set tags&
bwipe
endfunc
@@ -36,6 +51,7 @@ func Test_taglist_native_etags()
\ map(taglist('set_signals'), {i, v -> [v.name, v.cmd]}))
call delete('Xtags')
+ set tags&
endfunc
func Test_taglist_ctags_etags()
@@ -55,6 +71,7 @@ func Test_taglist_ctags_etags()
\ map(taglist('set_signals'), {i, v -> [v.name, v.cmd]}))
call delete('Xtags')
+ set tags&
endfunc
func Test_tags_too_long()
@@ -62,19 +79,6 @@ func Test_tags_too_long()
tags
endfunc
-" For historical reasons we support a tags file where the last line is missing
-" the newline.
-func Test_tagsfile_without_trailing_newline()
- call writefile(["Foo\tfoo\t1"], 'Xtags', 'b')
- set tags=Xtags
-
- let tl = taglist('.*')
- call assert_equal(1, len(tl))
- call assert_equal('Foo', tl[0].name)
-
- call delete('Xtags')
-endfunc
-
func Test_tagfiles()
call assert_equal([], tagfiles())
@@ -90,10 +94,25 @@ func Test_tagfiles()
\ fnamemodify(tf[0], ':p:gs?\\?/?'))
helpclose
call assert_equal(['Xtags1', 'Xtags2'], tagfiles())
- set tags&
+ " Nvim: defaults to "./tags;,tags", which might cause false positives.
+ set tags=./tags,tags
call assert_equal([], tagfiles())
call delete('Xtags1')
call delete('Xtags2')
bd
endfunc
+
+" For historical reasons we support a tags file where the last line is missing
+" the newline.
+func Test_tagsfile_without_trailing_newline()
+ call writefile(["Foo\tfoo\t1"], 'Xtags', 'b')
+ set tags=Xtags
+
+ let tl = taglist('.*')
+ call assert_equal(1, len(tl))
+ call assert_equal('Foo', tl[0].name)
+
+ call delete('Xtags')
+ set tags&
+endfunc
diff --git a/src/nvim/testdir/test_usercommands.vim b/src/nvim/testdir/test_usercommands.vim
index 1520c2f32a..b23a4aa62f 100644
--- a/src/nvim/testdir/test_usercommands.vim
+++ b/src/nvim/testdir/test_usercommands.vim
@@ -73,6 +73,97 @@ function Test_cmdmods()
unlet g:mods
endfunction
+func SaveCmdArgs(...)
+ let g:args = a:000
+endfunc
+
+func Test_f_args()
+ command -nargs=* TestFArgs call SaveCmdArgs(<f-args>)
+
+ TestFArgs
+ call assert_equal([], g:args)
+
+ TestFArgs one two three
+ call assert_equal(['one', 'two', 'three'], g:args)
+
+ TestFArgs one\\two three
+ call assert_equal(['one\two', 'three'], g:args)
+
+ TestFArgs one\ two three
+ call assert_equal(['one two', 'three'], g:args)
+
+ TestFArgs one\"two three
+ call assert_equal(['one\"two', 'three'], g:args)
+
+ delcommand TestFArgs
+endfunc
+
+func Test_q_args()
+ command -nargs=* TestQArgs call SaveCmdArgs(<q-args>)
+
+ TestQArgs
+ call assert_equal([''], g:args)
+
+ TestQArgs one two three
+ call assert_equal(['one two three'], g:args)
+
+ TestQArgs one\\two three
+ call assert_equal(['one\\two three'], g:args)
+
+ TestQArgs one\ two three
+ call assert_equal(['one\ two three'], g:args)
+
+ TestQArgs one\"two three
+ call assert_equal(['one\"two three'], g:args)
+
+ delcommand TestQArgs
+endfunc
+
+func Test_reg_arg()
+ command -nargs=* -reg TestRegArg call SaveCmdArgs("<reg>", "<register>")
+
+ TestRegArg
+ call assert_equal(['', ''], g:args)
+
+ TestRegArg x
+ call assert_equal(['x', 'x'], g:args)
+
+ delcommand TestRegArg
+endfunc
+
+func Test_no_arg()
+ command -nargs=* TestNoArg call SaveCmdArgs("<args>", "<>", "<x>", "<lt>")
+
+ TestNoArg
+ call assert_equal(['', '<>', '<x>', '<'], g:args)
+
+ TestNoArg one
+ call assert_equal(['one', '<>', '<x>', '<'], g:args)
+
+ delcommand TestNoArg
+endfunc
+
+func Test_range_arg()
+ command -range TestRangeArg call SaveCmdArgs(<range>, <line1>, <line2>)
+ new
+ call setline(1, range(100))
+ let lnum = line('.')
+
+ TestRangeArg
+ call assert_equal([0, lnum, lnum], g:args)
+
+ 99TestRangeArg
+ call assert_equal([1, 99, 99], g:args)
+
+ 88,99TestRangeArg
+ call assert_equal([2, 88, 99], g:args)
+
+ call assert_fails('102TestRangeArg', 'E16:')
+
+ bwipe!
+ delcommand TestRangeArg
+endfunc
+
func Test_Ambiguous()
command Doit let g:didit = 'yes'
command Dothat let g:didthat = 'also'
@@ -88,6 +179,9 @@ func Test_Ambiguous()
Do
call assert_equal('also', g:didthat)
delcommand Dothat
+
+ " Nvim removed the ":Ni!" easter egg in 87e107d92.
+ call assert_fails("\x4ei\041", 'E492: Not an editor command: Ni!')
endfunc
func Test_CmdUndefined()
@@ -111,6 +205,7 @@ func Test_CmdErrors()
call assert_fails('com! - DoCmd :', 'E175:')
call assert_fails('com! -xxx DoCmd :', 'E181:')
call assert_fails('com! -addr DoCmd :', 'E179:')
+ call assert_fails('com! -addr=asdf DoCmd :', 'E180:')
call assert_fails('com! -complete DoCmd :', 'E179:')
call assert_fails('com! -complete=xxx DoCmd :', 'E180:')
call assert_fails('com! -complete=custom DoCmd :', 'E467:')
diff --git a/src/nvim/testdir/test_vimscript.vim b/src/nvim/testdir/test_vimscript.vim
index 05abf04d65..f39e53d6dd 100644
--- a/src/nvim/testdir/test_vimscript.vim
+++ b/src/nvim/testdir/test_vimscript.vim
@@ -1,4 +1,4 @@
-" Test various aspects of the Vim language.
+" Test various aspects of the Vim script language.
" Most of this was formerly in test49.
"-------------------------------------------------------------------------------
@@ -21,7 +21,7 @@ com! -nargs=1 Xout call Xout(<args>)
"
" Create a script that consists of the body of the function a:funcname.
" Replace any ":return" by a ":finish", any argument variable by a global
-" variable, and and every ":call" by a ":source" for the next following argument
+" variable, and every ":call" by a ":source" for the next following argument
" in the variable argument list. This function is useful if similar tests are
" to be made for a ":return" from a function call or a ":finish" in a script
" file.
@@ -1310,6 +1310,43 @@ func Test_compound_assignment_operators()
let x .= 'n'
call assert_equal('2n', x)
+ " Test special cases: division or modulus with 0.
+ let x = 1
+ let x /= 0
+ if has('num64')
+ call assert_equal(0x7FFFFFFFFFFFFFFF, x)
+ else
+ call assert_equal(0x7fffffff, x)
+ endif
+
+ let x = -1
+ let x /= 0
+ if has('num64')
+ call assert_equal(-0x7FFFFFFFFFFFFFFF, x)
+ else
+ call assert_equal(-0x7fffffff, x)
+ endif
+
+ let x = 0
+ let x /= 0
+ if has('num64')
+ call assert_equal(-0x7FFFFFFFFFFFFFFF - 1, x)
+ else
+ call assert_equal(-0x7FFFFFFF - 1, x)
+ endif
+
+ let x = 1
+ let x %= 0
+ call assert_equal(0, x)
+
+ let x = -1
+ let x %= 0
+ call assert_equal(0, x)
+
+ let x = 0
+ let x %= 0
+ call assert_equal(0, x)
+
" Test for string
let x = 'str'
let x .= 'ing'
@@ -1375,5 +1412,4 @@ endfunc
"-------------------------------------------------------------------------------
" Modelines {{{1
" vim: ts=8 sw=4 tw=80 fdm=marker
-" vim: fdt=substitute(substitute(foldtext(),\ '\\%(^+--\\)\\@<=\\(\\s*\\)\\(.\\{-}\\)\:\ \\%(\"\ \\)\\=\\(Test\ \\d*\\)\:\\s*',\ '\\3\ (\\2)\:\ \\1',\ \"\"),\ '\\(Test\\s*\\)\\(\\d\\)\\D\\@=',\ '\\1\ \\2',\ "")
"-------------------------------------------------------------------------------
diff --git a/src/nvim/testdir/test_virtualedit.vim b/src/nvim/testdir/test_virtualedit.vim
index d49025237b..abe79f6a4a 100644
--- a/src/nvim/testdir/test_virtualedit.vim
+++ b/src/nvim/testdir/test_virtualedit.vim
@@ -42,6 +42,22 @@ func Test_paste_end_of_line()
set virtualedit=
endfunc
+func Test_replace_end_of_line()
+ new
+ set virtualedit=all
+ call setline(1, range(20))
+ exe "normal! gg2jv10lr-"
+ call assert_equal(["1", "-----------", "3"], getline(2,4))
+ if has('multi_byte')
+ call setline(1, range(20))
+ exe "normal! gg2jv10lr\<c-k>hh"
+ call assert_equal(["1", "───────────", "3"], getline(2,4))
+ endif
+
+ bwipe!
+ set virtualedit=
+endfunc
+
func Test_edit_CTRL_G()
new
set virtualedit=insert
diff --git a/src/nvim/testdir/test_window_cmd.vim b/src/nvim/testdir/test_window_cmd.vim
index 003a23ea7b..2a07a04401 100644
--- a/src/nvim/testdir/test_window_cmd.vim
+++ b/src/nvim/testdir/test_window_cmd.vim
@@ -518,6 +518,43 @@ func Test_winrestcmd()
only
endfunc
+function! Fun_RenewFile()
+ sleep 2
+ silent execute '!echo "1" > tmp.txt'
+ sp
+ wincmd p
+ edit! tmp.txt
+endfunction
+
+func Test_window_prevwin()
+ " Can we make this work on MS-Windows?
+ if !has('unix')
+ return
+ endif
+
+ set hidden autoread
+ call writefile(['2'], 'tmp.txt')
+ new tmp.txt
+ q
+ " Need to wait a bit for the timestamp to be older.
+ call Fun_RenewFile()
+ call assert_equal(2, winnr())
+ wincmd p
+ call assert_equal(1, winnr())
+ wincmd p
+ q
+ call Fun_RenewFile()
+ call assert_equal(2, winnr())
+ wincmd p
+ call assert_equal(1, winnr())
+ wincmd p
+ " reset
+ q
+ call delete('tmp.txt')
+ set hidden&vim autoread&vim
+ delfunc Fun_RenewFile
+endfunc
+
func Test_relative_cursor_position_in_one_line_window()
new
only
diff --git a/src/nvim/testdir/test_windows_home.vim b/src/nvim/testdir/test_windows_home.vim
index bbcbf96050..2e311b9aa5 100644
--- a/src/nvim/testdir/test_windows_home.vim
+++ b/src/nvim/testdir/test_windows_home.vim
@@ -86,7 +86,7 @@ func Test_WindowsHome()
let $HOME = '%USERPROFILE%\bar'
let $HOMEDRIVE = 'unused'
let $HOMEPATH = 'unused'
- " call CheckHome('C:\foo\bar', '%USERPROFILE%\bar')
+ call CheckHome('C:\foo\bar', '%USERPROFILE%\bar')
" Invalid $HOME is kept
let $USERPROFILE = 'C:\foo'
diff --git a/src/nvim/testdir/test_writefile.vim b/src/nvim/testdir/test_writefile.vim
index 9da9df2150..6d88c0d8cd 100644
--- a/src/nvim/testdir/test_writefile.vim
+++ b/src/nvim/testdir/test_writefile.vim
@@ -33,7 +33,7 @@ func Test_writefile_fails_gently()
endfunc
func Test_writefile_fails_conversion()
- if !has('multi_byte') || !has('iconv')
+ if !has('multi_byte') || !has('iconv') || system('uname -s') =~ 'SunOS'
return
endif
" Without a backup file the write won't happen if there is a conversion
diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c
index 7a725df0a1..fe8ffee8e0 100644
--- a/src/nvim/tui/input.c
+++ b/src/nvim/tui/input.c
@@ -32,23 +32,6 @@ void tinput_init(TermInput *input, Loop *loop)
uv_mutex_init(&input->key_buffer_mutex);
uv_cond_init(&input->key_buffer_cond);
- const char *term = os_getenv("TERM");
- if (!term) {
- term = ""; // termkey_new_abstract assumes non-null (#2745)
- }
-
-#if TERMKEY_VERSION_MAJOR > 0 || TERMKEY_VERSION_MINOR > 18
- input->tk = termkey_new_abstract(term,
- TERMKEY_FLAG_UTF8 | TERMKEY_FLAG_NOSTART);
- termkey_hook_terminfo_getstr(input->tk, input->tk_ti_hook_fn, NULL);
- termkey_start(input->tk);
-#else
- input->tk = termkey_new_abstract(term, TERMKEY_FLAG_UTF8);
-#endif
-
- int curflags = termkey_get_canonflags(input->tk);
- termkey_set_canonflags(input->tk, curflags | TERMKEY_CANON_DELBS);
-
// If stdin is not a pty, switch to stderr. For cases like:
// echo q | nvim -es
// ls *.md | xargs nvim
@@ -67,6 +50,24 @@ void tinput_init(TermInput *input, Loop *loop)
input->in_fd = 2;
}
#endif
+ input_global_fd_init(input->in_fd);
+
+ const char *term = os_getenv("TERM");
+ if (!term) {
+ term = ""; // termkey_new_abstract assumes non-null (#2745)
+ }
+
+#if TERMKEY_VERSION_MAJOR > 0 || TERMKEY_VERSION_MINOR > 18
+ input->tk = termkey_new_abstract(term,
+ TERMKEY_FLAG_UTF8 | TERMKEY_FLAG_NOSTART);
+ termkey_hook_terminfo_getstr(input->tk, input->tk_ti_hook_fn, NULL);
+ termkey_start(input->tk);
+#else
+ input->tk = termkey_new_abstract(term, TERMKEY_FLAG_UTF8);
+#endif
+
+ int curflags = termkey_get_canonflags(input->tk);
+ termkey_set_canonflags(input->tk, curflags | TERMKEY_CANON_DELBS);
// setup input handle
rstream_init_fd(loop, &input->read_stream, input->in_fd, 0xfff);
@@ -387,17 +388,20 @@ static void set_bg_deferred(void **argv)
// During startup, tui.c requests the background color (see `ext.get_bg`).
//
// Here in input.c, we watch for the terminal response `\e]11;COLOR\a`. If
-// COLOR matches `rgb:RRRR/GGGG/BBBB` where R, G, and B are hex digits, then
-// compute the luminance[1] of the RGB color and classify it as light/dark
+// COLOR matches `rgb:RRRR/GGGG/BBBB/AAAA` where R, G, B, and A are hex digits,
+// then compute the luminance[1] of the RGB color and classify it as light/dark
// accordingly. Note that the color components may have anywhere from one to
// four hex digits, and require scaling accordingly as values out of 4, 8, 12,
-// or 16 bits.
+// or 16 bits. Also note the A(lpha) component is optional, and is parsed but
+// ignored in the calculations.
//
// [1] https://en.wikipedia.org/wiki/Luma_%28video%29
static bool handle_background_color(TermInput *input)
{
size_t count = 0;
size_t component = 0;
+ size_t header_size = 0;
+ size_t num_components = 0;
uint16_t rgb[] = { 0, 0, 0 };
uint16_t rgb_max[] = { 0, 0, 0 };
bool eat_backslash = false;
@@ -405,48 +409,54 @@ static bool handle_background_color(TermInput *input)
bool bad = false;
if (rbuffer_size(input->read_stream.buffer) >= 9
&& !rbuffer_cmp(input->read_stream.buffer, "\x1b]11;rgb:", 9)) {
- rbuffer_consumed(input->read_stream.buffer, 9);
- RBUFFER_EACH(input->read_stream.buffer, c, i) {
- count = i + 1;
- if (eat_backslash) {
- done = true;
- break;
- } else if (c == '\x07') {
- done = true;
- break;
- } else if (c == '\x1b') {
- eat_backslash = true;
- } else if (bad) {
- // ignore
- } else if (c == '/') {
- if (component < 3) {
- component++;
- }
- } else if (ascii_isxdigit(c)) {
- if (component < 3 && rgb_max[component] != 0xffff) {
- rgb_max[component] = (uint16_t)((rgb_max[component] << 4) | 0xf);
- rgb[component] = (uint16_t)((rgb[component] << 4) | hex2nr(c));
- }
- } else {
- bad = true;
+ header_size = 9;
+ num_components = 3;
+ } else if (rbuffer_size(input->read_stream.buffer) >= 10
+ && !rbuffer_cmp(input->read_stream.buffer, "\x1b]11;rgba:", 10)) {
+ header_size = 10;
+ num_components = 4;
+ } else {
+ return false;
+ }
+ rbuffer_consumed(input->read_stream.buffer, header_size);
+ RBUFFER_EACH(input->read_stream.buffer, c, i) {
+ count = i + 1;
+ if (eat_backslash) {
+ done = true;
+ break;
+ } else if (c == '\x07') {
+ done = true;
+ break;
+ } else if (c == '\x1b') {
+ eat_backslash = true;
+ } else if (bad) {
+ // ignore
+ } else if ((c == '/') && (++component < num_components)) {
+ // work done in condition
+ } else if (ascii_isxdigit(c)) {
+ if (component < 3 && rgb_max[component] != 0xffff) {
+ rgb_max[component] = (uint16_t)((rgb_max[component] << 4) | 0xf);
+ rgb[component] = (uint16_t)((rgb[component] << 4) | hex2nr(c));
}
- }
- rbuffer_consumed(input->read_stream.buffer, count);
- if (done && !bad && rgb_max[0] && rgb_max[1] && rgb_max[2]) {
- double r = (double)rgb[0] / (double)rgb_max[0];
- double g = (double)rgb[1] / (double)rgb_max[1];
- double b = (double)rgb[2] / (double)rgb_max[2];
- double luminance = (0.299 * r) + (0.587 * g) + (0.114 * b); // CCIR 601
- char *bgvalue = luminance < 0.5 ? "dark" : "light";
- DLOG("bg response: %s", bgvalue);
- loop_schedule_deferred(&main_loop,
- event_create(set_bg_deferred, 1, bgvalue));
} else {
- DLOG("failed to parse bg response");
+ bad = true;
}
- return true;
}
- return false;
+ rbuffer_consumed(input->read_stream.buffer, count);
+ if (done && !bad && rgb_max[0] && rgb_max[1] && rgb_max[2]) {
+ double r = (double)rgb[0] / (double)rgb_max[0];
+ double g = (double)rgb[1] / (double)rgb_max[1];
+ double b = (double)rgb[2] / (double)rgb_max[2];
+ double luminance = (0.299 * r) + (0.587 * g) + (0.114 * b); // CCIR 601
+ char *bgvalue = luminance < 0.5 ? "dark" : "light";
+ DLOG("bg response: %s", bgvalue);
+ loop_schedule_deferred(&main_loop,
+ event_create(set_bg_deferred, 1, bgvalue));
+ } else {
+ DLOG("failed to parse bg response");
+ return false;
+ }
+ return true;
}
static void tinput_read_cb(Stream *stream, RBuffer *buf, size_t count_,
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index 42e5b9b270..519ef1cccd 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -222,15 +222,20 @@ static void terminfo_start(UI *ui)
#ifdef WIN32
os_tty_guess_term(&term, data->out_fd);
os_setenv("TERM", term, 1);
+ // Old os_getenv() pointer is invalid after os_setenv(), fetch it again.
+ term = os_getenv("TERM");
#endif
// Set up unibilium/terminfo.
- data->ut = unibi_from_env();
char *termname = NULL;
- if (!term || !data->ut) {
+ if (term) {
+ data->ut = unibi_from_term(term);
+ if (data->ut) {
+ termname = xstrdup(term);
+ }
+ }
+ if (!data->ut) {
data->ut = terminfo_from_builtin(term, &termname);
- } else {
- termname = xstrdup(term);
}
// Update 'term' option.
loop_schedule_deferred(&main_loop,
@@ -1585,6 +1590,11 @@ static void patch_terminfo_bugs(TUIData *data, const char *term,
unibi_set_if_empty(ut, unibi_set_left_margin_parm, "\x1b[%i%p1%ds");
unibi_set_if_empty(ut, unibi_set_right_margin_parm, "\x1b[%i;%p2%ds");
}
+
+#ifdef WIN32
+ // XXX: workaround libuv implicit LF => CRLF conversion. #10558
+ unibi_set_str(ut, unibi_cursor_down, "\x1b[B");
+#endif
} else if (rxvt) {
// 2017-04 terminfo.src lacks these. Unicode rxvt has them.
unibi_set_if_empty(ut, unibi_enter_italics_mode, "\x1b[3m");
diff --git a/src/nvim/ugrid.c b/src/nvim/ugrid.c
index 8adb421ee1..9e4aaff878 100644
--- a/src/nvim/ugrid.c
+++ b/src/nvim/ugrid.c
@@ -6,6 +6,7 @@
#include <stdio.h>
#include <limits.h>
+#include "nvim/assert.h"
#include "nvim/vim.h"
#include "nvim/ui.h"
#include "nvim/ugrid.h"
@@ -72,8 +73,9 @@ void ugrid_scroll(UGrid *grid, int top, int bot, int left, int right, int count)
for (i = start; i != stop; i += step) {
UCell *target_row = grid->cells[i] + left;
UCell *source_row = grid->cells[i + count] + left;
+ assert(right >= left && left >= 0);
memcpy(target_row, source_row,
- sizeof(UCell) * (size_t)(right - left + 1));
+ sizeof(UCell) * ((size_t)right - (size_t)left + 1));
}
}
diff --git a/src/nvim/ui.c b/src/nvim/ui.c
index 7dbb8ec790..fc4a3a403d 100644
--- a/src/nvim/ui.c
+++ b/src/nvim/ui.c
@@ -341,15 +341,15 @@ void ui_line(ScreenGrid *grid, int row, int startcol, int endcol, int clearcol,
flags, (const schar_T *)grid->chars + off,
(const sattr_T *)grid->attrs + off);
- if (p_wd) { // 'writedelay': flush & delay each time.
- int old_row = cursor_row, old_col = cursor_col;
- handle_T old_grid = cursor_grid_handle;
+ // 'writedelay': flush & delay each time.
+ if (p_wd && !(rdb_flags & RDB_COMPOSITOR)) {
// If 'writedelay' is active, set the cursor to indicate what was drawn.
- ui_grid_cursor_goto(grid->handle, row, MIN(clearcol, (int)Columns-1));
- ui_flush();
+ ui_call_grid_cursor_goto(grid->handle, row,
+ MIN(clearcol, (int)grid->Columns-1));
+ ui_call_flush();
uint64_t wd = (uint64_t)labs(p_wd);
os_microdelay(wd * 1000u, true);
- ui_grid_cursor_goto(old_grid, old_row, old_col);
+ pending_cursor_update = true; // restore the cursor later
}
}
@@ -372,6 +372,14 @@ void ui_grid_cursor_goto(handle_T grid_handle, int new_row, int new_col)
pending_cursor_update = true;
}
+/// moving the cursor grid will implicitly move the cursor
+void ui_check_cursor_grid(handle_T grid_handle)
+{
+ if (cursor_grid_handle == grid_handle) {
+ pending_cursor_update = true;
+ }
+}
+
void ui_mode_info_set(void)
{
pending_mode_info_update = true;
diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c
index f6573e7488..2cb3cf7ee7 100644
--- a/src/nvim/ui_compositor.c
+++ b/src/nvim/ui_compositor.c
@@ -49,6 +49,8 @@ static bool valid_screen = true;
static bool msg_scroll_mode = false;
static int msg_first_invalid = 0;
+static int dbghl_normal, dbghl_clear, dbghl_composed, dbghl_recompose;
+
void ui_comp_init(void)
{
if (compositor != NULL) {
@@ -81,6 +83,13 @@ void ui_comp_init(void)
ui_attach_impl(compositor);
}
+void ui_comp_syn_init(void)
+{
+ dbghl_normal = syn_check_group((char_u *)S_LEN("RedrawDebugNormal"));
+ dbghl_clear = syn_check_group((char_u *)S_LEN("RedrawDebugClear"));
+ dbghl_composed = syn_check_group((char_u *)S_LEN("RedrawDebugComposed"));
+ dbghl_recompose = syn_check_group((char_u *)S_LEN("RedrawDebugRecompose"));
+}
void ui_comp_attach(UI *ui)
{
@@ -290,10 +299,14 @@ static void compose_line(Integer row, Integer startcol, Integer endcol,
{
// in case we start on the right half of a double-width char, we need to
// check the left half. But skip it in output if it wasn't doublewidth.
- int skip = 0;
+ int skipstart = 0, skipend = 0;
if (startcol > 0 && (flags & kLineFlagInvalid)) {
startcol--;
- skip = 1;
+ skipstart = 1;
+ }
+ if (endcol < default_grid.Columns && (flags & kLineFlagInvalid)) {
+ endcol++;
+ skipend = 1;
}
int col = (int)startcol;
@@ -329,13 +342,24 @@ static void compose_line(Integer row, Integer startcol, Integer endcol,
memcpy(linebuf+(col-startcol), grid->chars+off, n * sizeof(*linebuf));
memcpy(attrbuf+(col-startcol), grid->attrs+off, n * sizeof(*attrbuf));
- // 'pumblend'
- if (grid == &pum_grid && p_pb) {
- for (int i = col-(int)startcol; i < until-startcol; i++) {
- bool thru = strequal((char *)linebuf[i], " "); // negative space
- attrbuf[i] = (sattr_T)hl_blend_attrs(bg_attrs[i], attrbuf[i], thru);
+ // 'pumblend' and 'winblend'
+ if (grid->blending) {
+ int width;
+ for (int i = col-(int)startcol; i < until-startcol; i += width) {
+ width = 1;
+ // negative space
+ bool thru = strequal((char *)linebuf[i], " ") && bg_line[i][0] != NUL;
+ if (i+1 < endcol-startcol && bg_line[i+1][0] == NUL) {
+ width = 2;
+ thru &= strequal((char *)linebuf[i+1], " ");
+ }
+ attrbuf[i] = (sattr_T)hl_blend_attrs(bg_attrs[i], attrbuf[i], &thru);
+ if (width == 2) {
+ attrbuf[i+1] = (sattr_T)hl_blend_attrs(bg_attrs[i+1],
+ attrbuf[i+1], &thru);
+ }
if (thru) {
- memcpy(linebuf[i], bg_line[i], sizeof(linebuf[i]));
+ memcpy(linebuf + i, bg_line + i, (size_t)width * sizeof(linebuf[i]));
}
}
}
@@ -345,20 +369,27 @@ static void compose_line(Integer row, Integer startcol, Integer endcol,
if (linebuf[col-startcol][0] == NUL) {
linebuf[col-startcol][0] = ' ';
linebuf[col-startcol][1] = NUL;
+ if (col == endcol-1) {
+ skipend = 0;
+ }
} else if (n > 1 && linebuf[col-startcol+1][0] == NUL) {
- skip = 0;
+ skipstart = 0;
}
if (grid->comp_col+grid->Columns > until
&& grid->chars[off+n][0] == NUL) {
linebuf[until-1-startcol][0] = ' ';
linebuf[until-1-startcol][1] = '\0';
if (col == startcol && n == 1) {
- skip = 0;
+ skipstart = 0;
}
}
col = until;
}
+ if (linebuf[endcol-startcol-1][0] == NUL) {
+ skipend = 0;
+ }
+
assert(endcol <= chk_width);
assert(row < chk_height);
@@ -368,14 +399,48 @@ static void compose_line(Integer row, Integer startcol, Integer endcol,
flags = flags & ~kLineFlagWrap;
}
- ui_composed_call_raw_line(1, row, startcol+skip, endcol, endcol, 0, flags,
- (const schar_T *)linebuf+skip,
- (const sattr_T *)attrbuf+skip);
+ ui_composed_call_raw_line(1, row, startcol+skipstart,
+ endcol-skipend, endcol-skipend, 0, flags,
+ (const schar_T *)linebuf+skipstart,
+ (const sattr_T *)attrbuf+skipstart);
}
+static void compose_debug(Integer startrow, Integer endrow, Integer startcol,
+ Integer endcol, int syn_id, bool delay)
+{
+ if (!(rdb_flags & RDB_COMPOSITOR)) {
+ return;
+ }
+
+ endrow = MIN(endrow, default_grid.Rows);
+ endcol = MIN(endcol, default_grid.Columns);
+ int attr = syn_id2attr(syn_id);
+
+ for (int row = (int)startrow; row < endrow; row++) {
+ ui_composed_call_raw_line(1, row, startcol, startcol, endcol, attr, false,
+ (const schar_T *)linebuf,
+ (const sattr_T *)attrbuf);
+ }
+
+
+ if (delay) {
+ debug_delay(endrow-startrow);
+ }
+}
+
+static void debug_delay(Integer lines)
+{
+ ui_call_flush();
+ uint64_t wd = (uint64_t)labs(p_wd);
+ uint64_t factor = (uint64_t)MAX(MIN(lines, 5), 1);
+ os_microdelay(factor * wd * 1000u, true);
+}
+
+
static void compose_area(Integer startrow, Integer endrow,
Integer startcol, Integer endcol)
{
+ compose_debug(startrow, endrow, startcol, endcol, dbghl_recompose, true);
endrow = MIN(endrow, default_grid.Rows);
endcol = MIN(endcol, default_grid.Columns);
if (endcol <= startcol) {
@@ -419,9 +484,12 @@ static void ui_comp_raw_line(UI *ui, Integer grid, Integer row,
assert(clearcol <= default_grid.Columns);
if (flags & kLineFlagInvalid
|| kv_size(layers) > curgrid->comp_index+1
- || (p_pb && curgrid == &pum_grid)) {
+ || curgrid->blending) {
+ compose_debug(row, row+1, startcol, clearcol, dbghl_composed, true);
compose_line(row, startcol, clearcol, flags);
} else {
+ compose_debug(row, row+1, startcol, endcol, dbghl_normal, false);
+ compose_debug(row, row+1, endcol, clearcol, dbghl_clear, true);
ui_composed_call_raw_line(1, row, startcol, endcol, clearcol, clearattr,
flags, chunk, attrs);
}
@@ -467,7 +535,8 @@ static void ui_comp_grid_scroll(UI *ui, Integer grid, Integer top,
bot += curgrid->comp_row;
left += curgrid->comp_col;
right += curgrid->comp_col;
- if (!msg_scroll_mode && kv_size(layers) > curgrid->comp_index+1) {
+ bool covered = kv_size(layers) > curgrid->comp_index+1 || curgrid->blending;
+ if (!msg_scroll_mode && covered) {
// TODO(bfredl):
// 1. check if rectangles actually overlap
// 2. calulate subareas that can scroll.
@@ -480,6 +549,9 @@ static void ui_comp_grid_scroll(UI *ui, Integer grid, Integer top,
} else {
msg_first_invalid = MIN(msg_first_invalid, (int)top);
ui_composed_call_grid_scroll(1, top, bot, left, right, rows, cols);
+ if (rdb_flags & RDB_COMPOSITOR) {
+ debug_delay(2);
+ }
}
}
diff --git a/src/nvim/undo.c b/src/nvim/undo.c
index 116bdd02b8..1a31f6a6c7 100644
--- a/src/nvim/undo.c
+++ b/src/nvim/undo.c
@@ -85,6 +85,7 @@
#include "nvim/buffer.h"
#include "nvim/ascii.h"
+#include "nvim/change.h"
#include "nvim/undo.h"
#include "nvim/cursor.h"
#include "nvim/edit.h"
@@ -1299,7 +1300,7 @@ void u_read_undo(char *name, char_u *hash, char_u *orig_name)
verbose_leave();
}
- FILE *fp = mch_fopen(file_name, "r");
+ FILE *fp = os_fopen(file_name, "r");
if (fp == NULL) {
if (name != NULL || p_verbose > 0) {
EMSG2(_("E822: Cannot open undo file for reading: %s"), file_name);
@@ -2961,10 +2962,23 @@ static char_u *u_save_line(linenr_T lnum)
///
/// @return true if the buffer has changed
bool bufIsChanged(buf_T *buf)
+ FUNC_ATTR_WARN_UNUSED_RESULT
{
return !bt_dontwrite(buf) && (buf->b_changed || file_ff_differs(buf, true));
}
+// Return true if any buffer has changes. Also buffers that are not written.
+bool anyBufIsChanged(void)
+ FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ FOR_ALL_BUFFERS(buf) {
+ if (bufIsChanged(buf)) {
+ return true;
+ }
+ }
+ return false;
+}
+
/// Check if the 'modified' flag is set, or 'ff' has changed (only need to
/// check the first character, because it can only be "dos", "unix" or "mac").
/// "nofile" and "scratch" type buffers are considered to always be unchanged.
diff --git a/src/nvim/version.c b/src/nvim/version.c
index be7a2ffcad..74a4852def 100644
--- a/src/nvim/version.c
+++ b/src/nvim/version.c
@@ -52,12 +52,8 @@ static char *features[] = {
"-acl",
#endif
-#if (defined(HAVE_ICONV_H) && defined(USE_ICONV)) || defined(DYNAMIC_ICONV)
-# ifdef DYNAMIC_ICONV
-"+iconv/dyn",
-# else
+#if defined(HAVE_ICONV)
"+iconv",
-# endif
#else
"-iconv",
#endif
@@ -76,7 +72,7 @@ static const int included_patches[] = {
// 1848,
1847,
// 1846,
- // 1845,
+ 1845,
// 1844,
1843,
// 1842,
@@ -137,7 +133,7 @@ static const int included_patches[] = {
1787,
// 1786,
1785,
- // 1784,
+ 1784,
// 1783,
1782,
1781,
@@ -165,7 +161,7 @@ static const int included_patches[] = {
// 1759,
1758,
1757,
- // 1756,
+ 1756,
1755,
// 1754,
// 1753,
@@ -186,7 +182,7 @@ static const int included_patches[] = {
// 1738,
1737,
1736,
- // 1735,
+ 1735,
1734,
// 1733,
// 1732,
@@ -195,10 +191,10 @@ static const int included_patches[] = {
// 1729,
// 1728,
1727,
- // 1726,
+ 1726,
// 1725,
// 1724,
- // 1723,
+ 1723,
// 1722,
// 1721,
// 1720,
@@ -217,9 +213,9 @@ static const int included_patches[] = {
1707,
// 1706,
1705,
- // 1704,
+ 1704,
// 1703,
- // 1702,
+ 1702,
1701,
1700,
1699,
@@ -232,7 +228,7 @@ static const int included_patches[] = {
1692,
// 1691,
// 1690,
- // 1689,
+ 1689,
// 1688,
1687,
1686,
@@ -252,7 +248,7 @@ static const int included_patches[] = {
1672,
// 1671,
// 1670,
- // 1669,
+ 1669,
// 1668,
// 1667,
// 1666,
@@ -372,7 +368,7 @@ static const int included_patches[] = {
// 1552,
// 1551,
// 1550,
- // 1549,
+ 1549,
1548,
1547,
// 1546,
@@ -403,9 +399,9 @@ static const int included_patches[] = {
// 1521,
// 1520,
1519,
- // 1518,
+ 1518,
1517,
- // 1516,
+ 1516,
// 1515,
1514,
1513,
@@ -507,9 +503,9 @@ static const int included_patches[] = {
1417,
1416,
1415,
- // 1414,
+ 1414,
1413,
- // 1412,
+ 1412,
1411,
1410,
1409,
@@ -537,7 +533,7 @@ static const int included_patches[] = {
1387,
// 1386,
1385,
- // 1384,
+ 1384,
1383,
// 1382,
// 1381,
@@ -616,7 +612,7 @@ static const int included_patches[] = {
1308,
// 1307,
1306,
- // 1305,
+ 1305,
1304,
1303,
1302,
@@ -643,7 +639,7 @@ static const int included_patches[] = {
1281,
1280,
1279,
- // 1278,
+ 1278,
// 1277,
// 1276,
1275,
@@ -676,7 +672,7 @@ static const int included_patches[] = {
1248,
1247,
// 1246,
- // 1245,
+ 1245,
// 1244,
1243,
1242,
@@ -968,7 +964,7 @@ static const int included_patches[] = {
// 956,
955,
954,
- // 953,
+ 953,
952,
951,
950,
@@ -988,9 +984,9 @@ static const int included_patches[] = {
// 936,
// 935,
// 934,
- // 933,
+ 933,
// 932,
- // 931,
+ 931,
// 930,
// 929,
928,
@@ -1008,7 +1004,7 @@ static const int included_patches[] = {
// 916,
915,
// 914,
- // 913,
+ 913,
// 912,
911,
// 910,
@@ -1022,43 +1018,43 @@ static const int included_patches[] = {
// 902,
901,
900,
- // 899,
- // 898,
+ 899,
+ 898,
897,
- // 896,
+ 896,
895,
894,
- // 893,
- // 892,
+ 893,
+ 892,
891,
890,
889,
888,
- // 887,
+ 887,
886,
- // 885,
- // 884,
+ 885,
+ 884,
883,
- // 882,
+ 882,
881,
880,
879,
878,
- // 877,
+ 877,
876,
875,
- // 874,
+ 874,
873,
872,
871,
- // 870,
- // 869,
+ 870,
+ 869,
868,
- // 867,
+ 867,
866,
865,
- // 864,
- // 863,
+ 864,
+ 863,
862,
861,
860,
@@ -1100,12 +1096,12 @@ static const int included_patches[] = {
824,
823,
822,
- // 821,
+ 821,
820,
819,
- // 818,
- // 817,
- // 816,
+ 818,
+ 817,
+ 816,
815,
814,
813,
@@ -1118,7 +1114,7 @@ static const int included_patches[] = {
806,
805,
804,
- // 803,
+ 803,
802,
801,
800,
@@ -2002,30 +1998,78 @@ void ex_version(exarg_T *eap)
}
}
-/// List all features aligned in columns, dictionary style.
+/// Output a string for the version message. If it's going to wrap, output a
+/// newline, unless the message is too long to fit on the screen anyway.
+/// When "wrap" is TRUE wrap the string in [].
+/// @param s
+/// @param wrap
+static void version_msg_wrap(char_u *s, int wrap)
+{
+ int len = (int)vim_strsize(s) + (wrap ? 2 : 0);
+
+ if (!got_int
+ && (len < Columns)
+ && (msg_col + len >= Columns)
+ && (*s != '\n')) {
+ msg_putchar('\n');
+ }
+
+ if (!got_int) {
+ if (wrap) {
+ msg_puts("[");
+ }
+ msg_puts((char *)s);
+ if (wrap) {
+ msg_puts("]");
+ }
+ }
+}
+
+static void version_msg(char *s)
+{
+ version_msg_wrap((char_u *)s, false);
+}
+
+/// List all features.
+/// This does not use list_in_columns (as in Vim), because there are only a
+/// few, and we do not start at a new line.
static void list_features(void)
{
- int nfeat = 0;
+ version_msg(_("\n\nFeatures: "));
+ for (int i = 0; features[i] != NULL; i++) {
+ version_msg(features[i]);
+ if (features[i+1] != NULL) {
+ version_msg(" ");
+ }
+ }
+ version_msg("\nSee \":help feature-compile\"\n\n");
+}
+
+/// List string items nicely aligned in columns.
+/// When "size" is < 0 then the last entry is marked with NULL.
+/// The entry with index "current" is inclosed in [].
+void list_in_columns(char_u **items, int size, int current)
+{
+ int item_count = 0;
int width = 0;
- // Find the length of the longest feature name, use that + 1 as the column
- // width
+ // Find the length of the longest item, use that + 1 as the column width.
int i;
- for (i = 0; features[i] != NULL; ++i) {
- int l = (int)STRLEN(features[i]);
+ for (i = 0; size < 0 ? items[i] != NULL : i < size; i++) {
+ int l = (int)vim_strsize(items[i]) + (i == current ? 2 : 0);
if (l > width) {
width = l;
}
- nfeat++;
+ item_count++;
}
width += 1;
if (Columns < width) {
// Not enough screen columns - show one per line
- for (i = 0; features[i] != NULL; ++i) {
- version_msg(features[i]);
- if (msg_col > 0) {
+ for (i = 0; i < item_count; i++) {
+ version_msg_wrap(items[i], i == current);
+ if (msg_col > 0 && i < item_count - 1) {
msg_putchar('\n');
}
}
@@ -2035,28 +2079,41 @@ static void list_features(void)
// The rightmost column doesn't need a separator.
// Sacrifice it to fit in one more column if possible.
int ncol = (int)(Columns + 1) / width;
- int nrow = nfeat / ncol + (nfeat % ncol ? 1 : 0);
+ int nrow = item_count / ncol + (item_count % ncol ? 1 : 0);
+ int cur_row = 1;
- // i counts columns then rows. idx counts rows then columns.
- for (i = 0; !got_int && i < nrow * ncol; ++i) {
+ // "i" counts columns then rows. "idx" counts rows then columns.
+ for (i = 0; !got_int && i < nrow * ncol; i++) {
int idx = (i / ncol) + (i % ncol) * nrow;
- if (idx < nfeat) {
+ if (idx < item_count) {
int last_col = (i + 1) % ncol == 0;
- msg_puts(features[idx]);
+ if (idx == current) {
+ msg_putchar('[');
+ }
+ msg_puts((char *)items[idx]);
+ if (idx == current) {
+ msg_putchar(']');
+ }
if (last_col) {
- if (msg_col > 0) {
+ if (msg_col > 0 && cur_row < nrow) {
msg_putchar('\n');
}
+ cur_row++;
} else {
- msg_putchar(' ');
+ while (msg_col % width) {
+ msg_putchar(' ');
+ }
}
} else {
+ // this row is out of items, thus at the end of the row
if (msg_col > 0) {
- msg_putchar('\n');
+ if (cur_row < nrow) {
+ msg_putchar('\n');
+ }
+ cur_row++;
}
}
}
- MSG_PUTS("See \":help feature-compile\"\n\n");
}
void list_lua_version(void)
@@ -2094,8 +2151,6 @@ void list_version(void)
}
#endif // ifdef HAVE_PATHDEF
- version_msg(_("\n\nFeatures: "));
-
list_features();
#ifdef SYS_VIMRC_FILE
@@ -2121,26 +2176,6 @@ void list_version(void)
version_msg("\nRun :checkhealth for more info");
}
-/// Output a string for the version message. If it's going to wrap, output a
-/// newline, unless the message is too long to fit on the screen anyway.
-///
-/// @param s
-static void version_msg(char *s)
-{
- int len = (int)STRLEN(s);
-
- if (!got_int
- && (len < (int)Columns)
- && (msg_col + len >= (int)Columns)
- && (*s != '\n')) {
- msg_putchar('\n');
- }
-
- if (!got_int) {
- MSG_PUTS(s);
- }
-}
-
/// Show the intro message when not editing a file.
void maybe_intro_message(void)
diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c
index dcc64db8a0..b4a0f57e99 100644
--- a/src/nvim/viml/parser/expressions.c
+++ b/src/nvim/viml/parser/expressions.c
@@ -2330,7 +2330,7 @@ viml_pexpr_parse_invalid_comma:
break;
} else if (eastnode_type == kExprNodeSubscript) {
is_subscript = true;
- can_be_ternary = false;
+ // can_be_ternary = false;
assert(!is_ternary);
break;
} else if (eastnode_type == kExprNodeColon) {
diff --git a/src/nvim/window.c b/src/nvim/window.c
index e6b19cf88d..22a8969b88 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -90,36 +90,47 @@ do_window (
else
Prenum1 = Prenum;
-# define CHECK_CMDWIN if (cmdwin_type != 0) { EMSG(_(e_cmdwin)); break; }
+# define CHECK_CMDWIN \
+ do { \
+ if (cmdwin_type != 0) { \
+ EMSG(_(e_cmdwin)); \
+ return; \
+ } \
+ } while (0)
switch (nchar) {
/* split current window in two parts, horizontally */
case 'S':
case Ctrl_S:
case 's':
- CHECK_CMDWIN reset_VIsual_and_resel(); /* stop Visual mode */
- /* When splitting the quickfix window open a new buffer in it,
- * don't replicate the quickfix buffer. */
- if (bt_quickfix(curbuf))
+ CHECK_CMDWIN;
+ reset_VIsual_and_resel(); // stop Visual mode
+ // When splitting the quickfix window open a new buffer in it,
+ // don't replicate the quickfix buffer.
+ if (bt_quickfix(curbuf)) {
goto newwindow;
+ }
(void)win_split((int)Prenum, 0);
break;
/* split current window in two parts, vertically */
case Ctrl_V:
case 'v':
- CHECK_CMDWIN reset_VIsual_and_resel(); /* stop Visual mode */
- /* When splitting the quickfix window open a new buffer in it,
- * don't replicate the quickfix buffer. */
- if (bt_quickfix(curbuf))
+ CHECK_CMDWIN;
+ reset_VIsual_and_resel(); // stop Visual mode
+ // When splitting the quickfix window open a new buffer in it,
+ // don't replicate the quickfix buffer.
+ if (bt_quickfix(curbuf)) {
goto newwindow;
+ }
(void)win_split((int)Prenum, WSP_VERT);
break;
/* split current window and edit alternate file */
case Ctrl_HAT:
case '^':
- CHECK_CMDWIN reset_VIsual_and_resel(); /* stop Visual mode */
+ CHECK_CMDWIN;
+ reset_VIsual_and_resel(); // stop Visual mode
cmd_with_count("split #", (char_u *)cbuf, sizeof(cbuf), Prenum);
do_cmdline_cmd(cbuf);
break;
@@ -127,7 +138,8 @@ do_window (
/* open new window */
case Ctrl_N:
case 'n':
- CHECK_CMDWIN reset_VIsual_and_resel(); /* stop Visual mode */
+ CHECK_CMDWIN;
+ reset_VIsual_and_resel(); // stop Visual mode
newwindow:
if (Prenum)
/* window height */
@@ -160,7 +172,8 @@ newwindow:
/* close preview window */
case Ctrl_Z:
case 'z':
- CHECK_CMDWIN reset_VIsual_and_resel(); /* stop Visual mode */
+ CHECK_CMDWIN;
+ reset_VIsual_and_resel(); // stop Visual mode
do_cmdline_cmd("pclose");
break;
@@ -183,7 +196,8 @@ newwindow:
/* close all but current window */
case Ctrl_O:
case 'o':
- CHECK_CMDWIN reset_VIsual_and_resel(); /* stop Visual mode */
+ CHECK_CMDWIN;
+ reset_VIsual_and_resel(); // stop Visual mode
cmd_with_count("only", (char_u *)cbuf, sizeof(cbuf), Prenum);
do_cmdline_cmd(cbuf);
break;
@@ -193,11 +207,11 @@ newwindow:
case 'w':
/* cursor to previous window with wrap around */
case 'W':
- CHECK_CMDWIN
- if (ONE_WINDOW && Prenum != 1) /* just one window */
+ CHECK_CMDWIN;
+ if (ONE_WINDOW && Prenum != 1) { // just one window
beep_flush();
- else {
- if (Prenum) { /* go to specified window */
+ } else {
+ if (Prenum) { // go to specified window
for (wp = firstwin; --Prenum > 0; ) {
if (wp->w_next == NULL)
break;
@@ -233,14 +247,16 @@ newwindow:
case 'j':
case K_DOWN:
case Ctrl_J:
- CHECK_CMDWIN win_goto_ver(FALSE, Prenum1);
+ CHECK_CMDWIN;
+ win_goto_ver(false, Prenum1);
break;
/* cursor to window above */
case 'k':
case K_UP:
case Ctrl_K:
- CHECK_CMDWIN win_goto_ver(TRUE, Prenum1);
+ CHECK_CMDWIN;
+ win_goto_ver(true, Prenum1);
break;
/* cursor to left window */
@@ -248,14 +264,16 @@ newwindow:
case K_LEFT:
case Ctrl_H:
case K_BS:
- CHECK_CMDWIN win_goto_hor(TRUE, Prenum1);
+ CHECK_CMDWIN;
+ win_goto_hor(true, Prenum1);
break;
/* cursor to right window */
case 'l':
case K_RIGHT:
case Ctrl_L:
- CHECK_CMDWIN win_goto_hor(FALSE, Prenum1);
+ CHECK_CMDWIN;
+ win_goto_hor(false, Prenum1);
break;
/* move window to new tab page */
@@ -309,20 +327,23 @@ newwindow:
/* exchange current and next window */
case 'x':
case Ctrl_X:
- CHECK_CMDWIN win_exchange(Prenum);
+ CHECK_CMDWIN;
+ win_exchange(Prenum);
break;
/* rotate windows downwards */
case Ctrl_R:
case 'r':
- CHECK_CMDWIN reset_VIsual_and_resel(); /* stop Visual mode */
- win_rotate(FALSE, (int)Prenum1); /* downwards */
+ CHECK_CMDWIN;
+ reset_VIsual_and_resel(); // stop Visual mode
+ win_rotate(false, (int)Prenum1); // downwards
break;
/* rotate windows upwards */
case 'R':
- CHECK_CMDWIN reset_VIsual_and_resel(); /* stop Visual mode */
- win_rotate(TRUE, (int)Prenum1); /* upwards */
+ CHECK_CMDWIN;
+ reset_VIsual_and_resel(); // stop Visual mode
+ win_rotate(true, (int)Prenum1); // upwards
break;
/* move window to the very top/bottom/left/right */
@@ -330,9 +351,10 @@ newwindow:
case 'J':
case 'H':
case 'L':
- CHECK_CMDWIN win_totop((int)Prenum,
- ((nchar == 'H' || nchar == 'L') ? WSP_VERT : 0)
- | ((nchar == 'H' || nchar == 'K') ? WSP_TOP : WSP_BOT));
+ CHECK_CMDWIN;
+ win_totop((int)Prenum,
+ ((nchar == 'H' || nchar == 'L') ? WSP_VERT : 0)
+ | ((nchar == 'H' || nchar == 'K') ? WSP_TOP : WSP_BOT));
break;
/* make all windows the same height */
@@ -353,7 +375,7 @@ newwindow:
/* set current window height */
case Ctrl__:
case '_':
- win_setheight(Prenum ? (int)Prenum : 9999);
+ win_setheight(Prenum ? (int)Prenum : Rows-1);
break;
/* increase current window width */
@@ -368,20 +390,21 @@ newwindow:
/* set current window width */
case '|':
- win_setwidth(Prenum != 0 ? (int)Prenum : 9999);
+ win_setwidth(Prenum != 0 ? (int)Prenum : Columns);
break;
/* jump to tag and split window if tag exists (in preview window) */
case '}':
- CHECK_CMDWIN
- if (Prenum)
+ CHECK_CMDWIN;
+ if (Prenum) {
g_do_tagpreview = Prenum;
- else
+ } else {
g_do_tagpreview = p_pvh;
+ }
FALLTHROUGH;
case ']':
case Ctrl_RSB:
- CHECK_CMDWIN
+ CHECK_CMDWIN;
// Keep visual mode, can select words to use as a tag.
if (Prenum)
postponed_split = Prenum;
@@ -402,7 +425,7 @@ newwindow:
case 'F':
case Ctrl_F:
wingotofile:
- CHECK_CMDWIN
+ CHECK_CMDWIN;
ptr = grab_file_name(Prenum1, &lnum);
if (ptr != NULL) {
@@ -434,26 +457,20 @@ wingotofile:
FALLTHROUGH;
case 'd': // Go to definition, using 'define'
case Ctrl_D:
- CHECK_CMDWIN
- if ((len = find_ident_under_cursor(&ptr, FIND_IDENT)) == 0)
+ CHECK_CMDWIN;
+ if ((len = find_ident_under_cursor(&ptr, FIND_IDENT)) == 0) {
break;
- find_pattern_in_path(ptr, 0, len, TRUE,
- Prenum == 0 ? TRUE : FALSE,
+ }
+ find_pattern_in_path(ptr, 0, len, true, Prenum == 0,
type, Prenum1, ACTION_SPLIT, 1, MAXLNUM);
curwin->w_set_curswant = TRUE;
break;
+ // Quickfix window only: view the result under the cursor in a new split.
case K_KENTER:
case CAR:
- /*
- * In a quickfix window a <CR> jumps to the error under the
- * cursor in a new window.
- */
if (bt_quickfix(curbuf)) {
- sprintf(cbuf, "split +%" PRId64 "%s",
- (int64_t)curwin->w_cursor.lnum,
- (curwin->w_llist_ref == NULL) ? "cc" : "ll");
- do_cmdline_cmd(cbuf);
+ qf_view_result(true);
}
break;
@@ -461,7 +478,7 @@ wingotofile:
/* CTRL-W g extended commands */
case 'g':
case Ctrl_G:
- CHECK_CMDWIN
+ CHECK_CMDWIN;
no_mapping++;
if (xchar == NUL) {
xchar = plain_vgetc();
@@ -567,15 +584,43 @@ win_T *win_new_float(win_T *wp, FloatConfig fconfig, Error *err)
wp->w_status_height = 0;
wp->w_vsep_width = 0;
- // TODO(bfredl): use set_option_to() after merging #9110 ?
- wp->w_p_nu = false;
- wp->w_allbuf_opt.wo_nu = false;
win_config_float(wp, fconfig);
wp->w_pos_changed = true;
redraw_win_later(wp, VALID);
return wp;
}
+void win_set_minimal_style(win_T *wp)
+{
+ wp->w_p_nu = false;
+ wp->w_p_rnu = false;
+ wp->w_p_cul = false;
+ wp->w_p_cuc = false;
+ wp->w_p_spell = false;
+ wp->w_p_list = false;
+
+ // Hide EOB region: use " " fillchar and cleared highlighting
+ if (wp->w_p_fcs_chars.eob != ' ') {
+ char_u *old = wp->w_p_fcs;
+ wp->w_p_fcs = ((*old == NUL)
+ ? (char_u *)xstrdup("eob: ")
+ : concat_str(old, (char_u *)",eob: "));
+ xfree(old);
+ }
+ if (wp->w_hl_ids[HLF_EOB] != -1) {
+ char_u *old = wp->w_p_winhl;
+ wp->w_p_winhl = ((*old == NUL)
+ ? (char_u *)xstrdup("EndOfBuffer:")
+ : concat_str(old, (char_u *)",EndOfBuffer:"));
+ xfree(old);
+ }
+
+ if (wp->w_p_scl[0] != 'a') {
+ xfree(wp->w_p_scl);
+ wp->w_p_scl = (char_u *)xstrdup("auto");
+ }
+}
+
void win_config_float(win_T *wp, FloatConfig fconfig)
{
wp->w_width = MAX(fconfig.width, 1);
@@ -649,6 +694,7 @@ static void ui_ext_win_position(win_T *wp)
bool on_top = (curwin == wp) || !curwin->w_floating;
ui_comp_put_grid(&wp->w_grid, comp_row, comp_col, wp->w_height,
wp->w_width, valid, on_top);
+ ui_check_cursor_grid(wp->w_grid.handle);
if (!valid) {
wp->w_grid.valid = false;
redraw_win_later(wp, NOT_VALID);
@@ -804,6 +850,20 @@ bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf,
"'focusable' key must be Boolean");
return false;
}
+ } else if (!strcmp(key, "style")) {
+ if (val.type != kObjectTypeString) {
+ api_set_error(err, kErrorTypeValidation,
+ "'style' key must be String");
+ return false;
+ }
+ if (val.data.string.data[0] == NUL) {
+ fconfig->style = kWinStyleUnused;
+ } else if (striequal(val.data.string.data, "minimal")) {
+ fconfig->style = kWinStyleMinimal;
+ } else {
+ api_set_error(err, kErrorTypeValidation,
+ "Invalid value of 'style' key");
+ }
} else {
api_set_error(err, kErrorTypeValidation,
"Invalid key '%s'", key);
@@ -964,7 +1024,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
for (frp = oldwin->w_frame->fr_parent; frp != NULL;
frp = frp->fr_parent) {
if (frp->fr_layout == FR_ROW) {
- for (frp2 = frp->fr_child; frp2 != NULL; frp2 = frp2->fr_next) {
+ FOR_ALL_FRAMES(frp2, frp->fr_child) {
if (frp2 != prevfrp) {
minwidth += frame_minwidth(frp2, NOWIN);
}
@@ -1042,7 +1102,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
for (frp = oldwin->w_frame->fr_parent; frp != NULL;
frp = frp->fr_parent) {
if (frp->fr_layout == FR_COL) {
- for (frp2 = frp->fr_child; frp2 != NULL; frp2 = frp2->fr_next) {
+ FOR_ALL_FRAMES(frp2, frp->fr_child) {
if (frp2 != prevfrp) {
minheight += frame_minheight(frp2, NOWIN);
}
@@ -1187,11 +1247,13 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
curfrp->fr_child = frp;
curfrp->fr_win = NULL;
curfrp = frp;
- if (frp->fr_win != NULL)
+ if (frp->fr_win != NULL) {
oldwin->w_frame = frp;
- else
- for (frp = frp->fr_child; frp != NULL; frp = frp->fr_next)
+ } else {
+ FOR_ALL_FRAMES(frp, frp->fr_child) {
frp->fr_parent = curfrp;
+ }
+ }
}
if (new_wp == NULL)
@@ -1637,11 +1699,9 @@ static void win_exchange(long Prenum)
redraw_win_later(wp, NOT_VALID);
}
-/*
- * rotate windows: if upwards TRUE the second window becomes the first one
- * if upwards FALSE the first window becomes the second one
- */
-static void win_rotate(int upwards, int count)
+// rotate windows: if upwards true the second window becomes the first one
+// if upwards false the first window becomes the second one
+static void win_rotate(bool upwards, int count)
{
win_T *wp1;
win_T *wp2;
@@ -1653,19 +1713,19 @@ static void win_rotate(int upwards, int count)
return;
}
- if (firstwin == curwin && lastwin_nofloating() == curwin) {
+ if (count <= 0 || (firstwin == curwin && lastwin_nofloating() == curwin)) {
// nothing to do
beep_flush();
return;
}
- /* Check if all frames in this row/col have one window. */
- for (frp = curwin->w_frame->fr_parent->fr_child; frp != NULL;
- frp = frp->fr_next)
+ // Check if all frames in this row/col have one window.
+ FOR_ALL_FRAMES(frp, curwin->w_frame->fr_parent->fr_child) {
if (frp->fr_win == NULL) {
EMSG(_("E443: Cannot rotate when another window is split"));
return;
}
+ }
while (count--) {
if (upwards) { /* first window becomes last window */
@@ -1832,8 +1892,8 @@ void win_equal(
if (dir == 0)
dir = *p_ead;
win_equal_rec(next_curwin == NULL ? curwin : next_curwin, current,
- topframe, dir, 0, tabline_height(),
- (int)Columns, topframe->fr_height);
+ topframe, dir, 0, tabline_height(),
+ Columns, topframe->fr_height);
}
/*
@@ -1903,10 +1963,10 @@ static void win_equal_rec(
room = 0;
} else {
next_curwin_size = -1;
- for (fr = topfr->fr_child; fr != NULL; fr = fr->fr_next) {
- /* If 'winfixwidth' set keep the window width if
- * possible.
- * Watch out for this window being the next_curwin. */
+ FOR_ALL_FRAMES(fr, topfr->fr_child) {
+ // If 'winfixwidth' set keep the window width if
+ // possible.
+ // Watch out for this window being the next_curwin.
if (!frame_fixed_width(fr)) {
continue;
}
@@ -1949,7 +2009,7 @@ static void win_equal_rec(
--totwincount; /* don't count curwin */
}
- for (fr = topfr->fr_child; fr != NULL; fr = fr->fr_next) {
+ FOR_ALL_FRAMES(fr, topfr->fr_child) {
wincount = 1;
if (fr->fr_next == NULL)
/* last frame gets all that remains (avoid roundoff error) */
@@ -2024,10 +2084,10 @@ static void win_equal_rec(
room = 0;
} else {
next_curwin_size = -1;
- for (fr = topfr->fr_child; fr != NULL; fr = fr->fr_next) {
- /* If 'winfixheight' set keep the window height if
- * possible.
- * Watch out for this window being the next_curwin. */
+ FOR_ALL_FRAMES(fr, topfr->fr_child) {
+ // If 'winfixheight' set keep the window height if
+ // possible.
+ // Watch out for this window being the next_curwin.
if (!frame_fixed_height(fr)) {
continue;
}
@@ -2070,7 +2130,7 @@ static void win_equal_rec(
--totwincount; /* don't count curwin */
}
- for (fr = topfr->fr_child; fr != NULL; fr = fr->fr_next) {
+ FOR_ALL_FRAMES(fr, topfr->fr_child) {
wincount = 1;
if (fr->fr_next == NULL)
/* last frame gets all that remains (avoid roundoff error) */
@@ -2757,8 +2817,9 @@ winframe_remove (
* and remove it. */
frp2->fr_parent->fr_layout = frp2->fr_layout;
frp2->fr_parent->fr_child = frp2->fr_child;
- for (frp = frp2->fr_child; frp != NULL; frp = frp->fr_next)
+ FOR_ALL_FRAMES(frp, frp2->fr_child) {
frp->fr_parent = frp2->fr_parent;
+ }
frp2->fr_parent->fr_win = frp2->fr_win;
if (frp2->fr_win != NULL)
frp2->fr_win->w_frame = frp2->fr_parent;
@@ -2879,13 +2940,14 @@ static win_T *frame2win(frame_T *frp)
///
/// @param frp frame
/// @param wp window
-static bool frame_has_win(frame_T *frp, win_T *wp)
+static bool frame_has_win(const frame_T *frp, const win_T *wp)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1)
{
if (frp->fr_layout == FR_LEAF) {
return frp->fr_win == wp;
}
- for (frame_T *p = frp->fr_child; p != NULL; p = p->fr_next) {
+ const frame_T *p;
+ FOR_ALL_FRAMES(p, frp->fr_child) {
if (frame_has_win(p, wp)) {
return true;
}
@@ -2916,8 +2978,8 @@ frame_new_height (
height - topfrp->fr_win->w_status_height);
} else if (topfrp->fr_layout == FR_ROW) {
do {
- /* All frames in this row get the same new height. */
- for (frp = topfrp->fr_child; frp != NULL; frp = frp->fr_next) {
+ // All frames in this row get the same new height.
+ FOR_ALL_FRAMES(frp, topfrp->fr_child) {
frame_new_height(frp, height, topfirst, wfh);
if (frp->fr_height > height) {
/* Could not fit the windows, make the whole row higher. */
@@ -2998,7 +3060,7 @@ static bool frame_fixed_height(frame_T *frp)
if (frp->fr_layout == FR_ROW) {
// The frame is fixed height if one of the frames in the row is fixed
// height.
- for (frp = frp->fr_child; frp != NULL; frp = frp->fr_next) {
+ FOR_ALL_FRAMES(frp, frp->fr_child) {
if (frame_fixed_height(frp)) {
return true;
}
@@ -3008,7 +3070,7 @@ static bool frame_fixed_height(frame_T *frp)
// frp->fr_layout == FR_COL: The frame is fixed height if all of the
// frames in the row are fixed height.
- for (frp = frp->fr_child; frp != NULL; frp = frp->fr_next) {
+ FOR_ALL_FRAMES(frp, frp->fr_child) {
if (!frame_fixed_height(frp)) {
return false;
}
@@ -3032,7 +3094,7 @@ static bool frame_fixed_width(frame_T *frp)
if (frp->fr_layout == FR_COL) {
// The frame is fixed width if one of the frames in the row is fixed
// width.
- for (frp = frp->fr_child; frp != NULL; frp = frp->fr_next) {
+ FOR_ALL_FRAMES(frp, frp->fr_child) {
if (frame_fixed_width(frp)) {
return true;
}
@@ -3042,7 +3104,7 @@ static bool frame_fixed_width(frame_T *frp)
// frp->fr_layout == FR_ROW: The frame is fixed width if all of the
// frames in the row are fixed width.
- for (frp = frp->fr_child; frp != NULL; frp = frp->fr_next) {
+ FOR_ALL_FRAMES(frp, frp->fr_child) {
if (!frame_fixed_width(frp)) {
return false;
}
@@ -3066,13 +3128,15 @@ static void frame_add_statusline(frame_T *frp)
wp->w_status_height = STATUS_HEIGHT;
}
} else if (frp->fr_layout == FR_ROW) {
- /* Handle all the frames in the row. */
- for (frp = frp->fr_child; frp != NULL; frp = frp->fr_next)
+ // Handle all the frames in the row.
+ FOR_ALL_FRAMES(frp, frp->fr_child) {
frame_add_statusline(frp);
- } else { /* frp->fr_layout == FR_COL */
- /* Only need to handle the last frame in the column. */
- for (frp = frp->fr_child; frp->fr_next != NULL; frp = frp->fr_next)
- ;
+ }
+ } else {
+ assert(frp->fr_layout == FR_COL);
+ // Only need to handle the last frame in the column.
+ for (frp = frp->fr_child; frp->fr_next != NULL; frp = frp->fr_next) {
+ }
frame_add_statusline(frp);
}
}
@@ -3107,8 +3171,8 @@ frame_new_width (
win_new_width(wp, width - wp->w_vsep_width);
} else if (topfrp->fr_layout == FR_COL) {
do {
- /* All frames in this column get the same new width. */
- for (frp = topfrp->fr_child; frp != NULL; frp = frp->fr_next) {
+ // All frames in this column get the same new width.
+ FOR_ALL_FRAMES(frp, topfrp->fr_child) {
frame_new_width(frp, width, leftfirst, wfw);
if (frp->fr_width > width) {
/* Could not fit the windows, make whole column wider. */
@@ -3177,7 +3241,8 @@ frame_new_width (
* Add the vertical separator to windows at the right side of "frp".
* Note: Does not check if there is room!
*/
-static void frame_add_vsep(frame_T *frp)
+static void frame_add_vsep(const frame_T *frp)
+ FUNC_ATTR_NONNULL_ARG(1)
{
win_T *wp;
@@ -3189,11 +3254,13 @@ static void frame_add_vsep(frame_T *frp)
wp->w_vsep_width = 1;
}
} else if (frp->fr_layout == FR_COL) {
- /* Handle all the frames in the column. */
- for (frp = frp->fr_child; frp != NULL; frp = frp->fr_next)
+ // Handle all the frames in the column.
+ FOR_ALL_FRAMES(frp, frp->fr_child) {
frame_add_vsep(frp);
- } else { /* frp->fr_layout == FR_ROW */
- /* Only need to handle the last frame in the row. */
+ }
+ } else {
+ assert(frp->fr_layout == FR_ROW);
+ // Only need to handle the last frame in the row.
frp = frp->fr_child;
while (frp->fr_next != NULL)
frp = frp->fr_next;
@@ -3243,7 +3310,7 @@ static int frame_minheight(frame_T *topfrp, win_T *next_curwin)
} else if (topfrp->fr_layout == FR_ROW) {
/* get the minimal height from each frame in this row */
m = 0;
- for (frp = topfrp->fr_child; frp != NULL; frp = frp->fr_next) {
+ FOR_ALL_FRAMES(frp, topfrp->fr_child) {
n = frame_minheight(frp, next_curwin);
if (n > m)
m = n;
@@ -3251,8 +3318,9 @@ static int frame_minheight(frame_T *topfrp, win_T *next_curwin)
} else {
/* Add up the minimal heights for all frames in this column. */
m = 0;
- for (frp = topfrp->fr_child; frp != NULL; frp = frp->fr_next)
+ FOR_ALL_FRAMES(frp, topfrp->fr_child) {
m += frame_minheight(frp, next_curwin);
+ }
}
return m;
@@ -3286,7 +3354,7 @@ frame_minwidth (
} else if (topfrp->fr_layout == FR_COL) {
/* get the minimal width from each frame in this column */
m = 0;
- for (frp = topfrp->fr_child; frp != NULL; frp = frp->fr_next) {
+ FOR_ALL_FRAMES(frp, topfrp->fr_child) {
n = frame_minwidth(frp, next_curwin);
if (n > m)
m = n;
@@ -3294,8 +3362,9 @@ frame_minwidth (
} else {
/* Add up the minimal widths for all frames in this row. */
m = 0;
- for (frp = topfrp->fr_child; frp != NULL; frp = frp->fr_next)
+ FOR_ALL_FRAMES(frp, topfrp->fr_child) {
m += frame_minwidth(frp, next_curwin);
+ }
}
return m;
@@ -4673,9 +4742,10 @@ void shell_new_columns(void)
/* First try setting the widths of windows with 'winfixwidth'. If that
* doesn't result in the right width, forget about that option. */
- frame_new_width(topframe, (int)Columns, FALSE, TRUE);
- if (!frame_check_width(topframe, Columns))
- frame_new_width(topframe, (int)Columns, FALSE, FALSE);
+ frame_new_width(topframe, Columns, false, true);
+ if (!frame_check_width(topframe, Columns)) {
+ frame_new_width(topframe, Columns, false, false);
+ }
(void)win_comp_pos(); /* recompute w_winrow and w_wincol */
}
@@ -4772,11 +4842,12 @@ static void frame_comp_pos(frame_T *topfrp, int *row, int *col)
} else {
startrow = *row;
startcol = *col;
- for (frp = topfrp->fr_child; frp != NULL; frp = frp->fr_next) {
- if (topfrp->fr_layout == FR_ROW)
- *row = startrow; /* all frames are at the same row */
- else
- *col = startcol; /* all frames are at the same col */
+ FOR_ALL_FRAMES(frp, topfrp->fr_child) {
+ if (topfrp->fr_layout == FR_ROW) {
+ *row = startrow; // all frames are at the same row
+ } else {
+ *col = startcol; // all frames are at the same col
+ }
frame_comp_pos(frp, row, col);
}
}
@@ -4808,13 +4879,9 @@ void win_setheight_win(int height, win_T *win)
}
if (win->w_floating) {
- if (win->w_float_config.external) {
- win->w_float_config.height = height;
- win_config_float(win, win->w_float_config);
- } else {
- beep_flush();
- return;
- }
+ win->w_float_config.height = height;
+ win_config_float(win, win->w_float_config);
+ redraw_win_later(win, NOT_VALID);
} else {
frame_setheight(win->w_frame, height + win->w_status_height);
@@ -4824,14 +4891,14 @@ void win_setheight_win(int height, win_T *win)
// If there is extra space created between the last window and the command
// line, clear it.
if (full_screen && msg_scrolled == 0 && row < cmdline_row) {
- grid_fill(&default_grid, row, cmdline_row, 0, (int)Columns, ' ', ' ', 0);
+ grid_fill(&default_grid, row, cmdline_row, 0, Columns, ' ', ' ', 0);
}
cmdline_row = row;
msg_row = row;
msg_col = 0;
+ redraw_all_later(NOT_VALID);
}
- redraw_all_later(NOT_VALID);
}
@@ -4889,15 +4956,16 @@ static void frame_setheight(frame_T *curfrp, int height)
for (run = 1; run <= 2; ++run) {
room = 0;
room_reserved = 0;
- for (frp = curfrp->fr_parent->fr_child; frp != NULL;
- frp = frp->fr_next) {
+ FOR_ALL_FRAMES(frp, curfrp->fr_parent->fr_child) {
if (frp != curfrp
&& frp->fr_win != NULL
- && frp->fr_win->w_p_wfh)
+ && frp->fr_win->w_p_wfh) {
room_reserved += frp->fr_height;
+ }
room += frp->fr_height;
- if (frp != curfrp)
+ if (frp != curfrp) {
room -= frame_minheight(frp, NULL);
+ }
}
if (curfrp->fr_width != Columns) {
room_cmdline = 0;
@@ -5014,21 +5082,17 @@ void win_setwidth_win(int width, win_T *wp)
width = 1;
}
if (wp->w_floating) {
- if (wp->w_float_config.external) {
- wp->w_float_config.width = width;
- win_config_float(wp, wp->w_float_config);
- } else {
- beep_flush();
- return;
- }
+ wp->w_float_config.width = width;
+ win_config_float(wp, wp->w_float_config);
+ redraw_win_later(wp, NOT_VALID);
} else {
frame_setwidth(wp->w_frame, width + wp->w_vsep_width);
// recompute the window positions
(void)win_comp_pos();
+ redraw_all_later(NOT_VALID);
}
- redraw_all_later(NOT_VALID);
}
/*
@@ -5074,15 +5138,16 @@ static void frame_setwidth(frame_T *curfrp, int width)
for (run = 1; run <= 2; ++run) {
room = 0;
room_reserved = 0;
- for (frp = curfrp->fr_parent->fr_child; frp != NULL;
- frp = frp->fr_next) {
+ FOR_ALL_FRAMES(frp, curfrp->fr_parent->fr_child) {
if (frp != curfrp
&& frp->fr_win != NULL
- && frp->fr_win->w_p_wfw)
+ && frp->fr_win->w_p_wfw) {
room_reserved += frp->fr_width;
+ }
room += frp->fr_width;
- if (frp != curfrp)
+ if (frp != curfrp) {
room -= frame_minwidth(frp, NULL);
+ }
}
if (width <= room)
@@ -5246,10 +5311,11 @@ void win_drag_status_line(win_T *dragwin, int offset)
room -= p_ch;
if (room < 0)
room = 0;
- /* sum up the room of frames below of the current one */
- for (fr = curfr->fr_next; fr != NULL; fr = fr->fr_next)
+ // sum up the room of frames below of the current one
+ FOR_ALL_FRAMES(fr, curfr->fr_next) {
room += fr->fr_height - frame_minheight(fr, NULL);
- fr = curfr; /* put fr at window that grows */
+ }
+ fr = curfr; // put fr at window that grows
}
if (room < offset) /* Not enough room */
@@ -5287,7 +5353,7 @@ void win_drag_status_line(win_T *dragwin, int offset)
fr = fr->fr_next;
}
row = win_comp_pos();
- grid_fill(&default_grid, row, cmdline_row, 0, (int)Columns, ' ', ' ', 0);
+ grid_fill(&default_grid, row, cmdline_row, 0, Columns, ' ', ' ', 0);
cmdline_row = row;
p_ch = Rows - cmdline_row;
if (p_ch < 1)
@@ -5349,9 +5415,10 @@ void win_drag_vsep_line(win_T *dragwin, int offset)
left = FALSE;
/* sum up the room of frames right of the current one */
room = 0;
- for (fr = curfr->fr_next; fr != NULL; fr = fr->fr_next)
+ FOR_ALL_FRAMES(fr, curfr->fr_next) {
room += fr->fr_width - frame_minwidth(fr, NULL);
- fr = curfr; /* put fr at window that grows */
+ }
+ fr = curfr; // put fr at window that grows
}
assert(fr);
@@ -5655,8 +5722,7 @@ void command_height(void)
// clear the lines added to cmdline
if (full_screen) {
- grid_fill(&default_grid, cmdline_row, (int)Rows, 0, (int)Columns, ' ',
- ' ', 0);
+ grid_fill(&default_grid, cmdline_row, Rows, 0, Columns, ' ', ' ', 0);
}
msg_row = cmdline_row;
redraw_cmdline = TRUE;
@@ -5883,9 +5949,10 @@ static void last_status_rec(frame_T *fr, int statusline)
redraw_all_later(SOME_VALID);
}
} else if (fr->fr_layout == FR_ROW) {
- /* vertically split windows, set status line for each one */
- for (fp = fr->fr_child; fp != NULL; fp = fp->fr_next)
+ // vertically split windows, set status line for each one
+ FOR_ALL_FRAMES(fp, fr->fr_child) {
last_status_rec(fp, statusline);
+ }
} else {
/* horizontally split window, set status line for last one */
for (fp = fr->fr_child; fp->fr_next != NULL; fp = fp->fr_next)
@@ -6501,14 +6568,15 @@ matchitem_T *get_match(win_T *wp, int id)
///
/// @param topfrp top frame pointer
/// @param height expected height
-static bool frame_check_height(frame_T *topfrp, int height)
+static bool frame_check_height(const frame_T *topfrp, int height)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
if (topfrp->fr_height != height) {
return false;
}
if (topfrp->fr_layout == FR_ROW) {
- for (frame_T *frp = topfrp->fr_child; frp != NULL; frp = frp->fr_next) {
+ const frame_T *frp;
+ FOR_ALL_FRAMES(frp, topfrp->fr_child) {
if (frp->fr_height != height) {
return false;
}
@@ -6521,14 +6589,15 @@ static bool frame_check_height(frame_T *topfrp, int height)
///
/// @param topfrp top frame pointer
/// @param width expected width
-static bool frame_check_width(frame_T *topfrp, int width)
+static bool frame_check_width(const frame_T *topfrp, int width)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
if (topfrp->fr_width != width) {
return false;
}
if (topfrp->fr_layout == FR_COL) {
- for (frame_T *frp = topfrp->fr_child; frp != NULL; frp = frp->fr_next) {
+ const frame_T *frp;
+ FOR_ALL_FRAMES(frp, topfrp->fr_child) {
if (frp->fr_width != width) {
return false;
}