aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/labeler.yml36
-rw-r--r--.github/workflows/reviews.yml99
-rw-r--r--ci/common/build.sh7
-rwxr-xr-xci/common/submit_coverage.sh2
-rw-r--r--ci/common/suite.sh21
-rw-r--r--ci/common/test.sh10
-rwxr-xr-xci/run_tests.sh24
-rw-r--r--runtime/autoload/health.vim9
-rw-r--r--runtime/doc/builtin.txt21
-rw-r--r--runtime/doc/change.txt7
-rw-r--r--runtime/doc/index.txt4
-rw-r--r--runtime/doc/quickfix.txt5
-rw-r--r--runtime/doc/various.txt2
-rw-r--r--runtime/doc/visual.txt1
-rw-r--r--runtime/lua/vim/lsp.lua2
-rw-r--r--runtime/lua/vim/treesitter/query.lua12
-rw-r--r--scripts/lintcommit.lua26
-rw-r--r--src/nvim/api/vim.c6
-rw-r--r--src/nvim/api/window.c2
-rw-r--r--src/nvim/autocmd.c7
-rw-r--r--src/nvim/buffer.c14
-rw-r--r--src/nvim/change.c6
-rw-r--r--src/nvim/channel.c1
-rw-r--r--src/nvim/eval.c2
-rw-r--r--src/nvim/eval.lua1
-rw-r--r--src/nvim/eval/funcs.c97
-rw-r--r--src/nvim/ex_cmds.c2
-rw-r--r--src/nvim/ex_cmds2.c9
-rw-r--r--src/nvim/ex_docmd.c12
-rw-r--r--src/nvim/ex_getln.c2
-rw-r--r--src/nvim/fileio.c2
-rw-r--r--src/nvim/main.c6
-rw-r--r--src/nvim/mbyte.c41
-rw-r--r--src/nvim/normal.c12
-rw-r--r--src/nvim/ops.c117
-rw-r--r--src/nvim/quickfix.c320
-rw-r--r--src/nvim/screen.c46
-rw-r--r--src/nvim/search.c1
-rw-r--r--src/nvim/tag.c2
-rw-r--r--src/nvim/terminal.c20
-rw-r--r--src/nvim/testdir/test_cursor_func.vim8
-rw-r--r--src/nvim/testdir/test_ex_mode.vim5
-rw-r--r--src/nvim/testdir/test_filechanged.vim2
-rw-r--r--src/nvim/testdir/test_filter_cmd.vim35
-rw-r--r--src/nvim/testdir/test_global.vim30
-rw-r--r--src/nvim/testdir/test_help.vim12
-rw-r--r--src/nvim/testdir/test_help_tagjump.vim43
-rw-r--r--src/nvim/testdir/test_listdict.vim43
-rw-r--r--src/nvim/testdir/test_options.vim13
-rw-r--r--src/nvim/testdir/test_quickfix.vim23
-rw-r--r--src/nvim/testdir/test_substitute.vim55
-rw-r--r--src/nvim/testdir/test_textformat.vim43
-rw-r--r--src/nvim/testdir/test_visual.vim58
-rw-r--r--src/nvim/testdir/test_writefile.vim152
-rw-r--r--src/nvim/window.c43
-rw-r--r--test/functional/api/vim_spec.lua25
-rw-r--r--test/functional/editor/put_spec.lua12
-rw-r--r--test/functional/fixtures/lua/test_plug/submodule_empty/health.lua7
-rw-r--r--test/functional/legacy/ex_mode_spec.lua36
-rw-r--r--test/functional/legacy/filechanged_spec.lua76
-rw-r--r--test/functional/plugin/health_spec.lua14
-rw-r--r--test/functional/treesitter/parser_spec.lua19
-rw-r--r--test/functional/ui/float_spec.lua22
-rw-r--r--third-party/CMakeLists.txt4
64 files changed, 1387 insertions, 409 deletions
diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml
index 5eb0185071..3de0c453a5 100644
--- a/.github/workflows/labeler.yml
+++ b/.github/workflows/labeler.yml
@@ -32,39 +32,3 @@ jobs:
- name: "Extract commit scope and add as label"
continue-on-error: true
run: gh pr edit "$PR_NUMBER" --add-label "$(echo "$PR_TITLE" | sed -E 's|[[:alpha:]]+\((.+)\)!?:.*|\1|')"
-
- add-reviewer:
- runs-on: ubuntu-latest
- needs: ["triage", "type-scope"]
- steps:
- - uses: actions/github-script@v5
- with:
- script: |
- const reviewers = []
-
- const { data: { labels } } = await github.rest.pulls.get({
- owner: context.repo.owner,
- repo: context.repo.repo,
- pull_number: context.issue.number
- })
- const label_names = labels.map(label => label.name)
-
- if (label_names.includes('ci')) {
- reviewers.push("jamessan")
- }
-
- if (label_names.includes('vim-patch')) {
- reviewers.push("seandewar")
- }
-
- const index = reviewers.indexOf(context.actor);
- if (index > -1) {
- reviewers.splice(index, 1);
- }
-
- github.rest.pulls.requestReviewers({
- owner: context.repo.owner,
- repo: context.repo.repo,
- pull_number: context.issue.number,
- reviewers: reviewers
- });
diff --git a/.github/workflows/reviews.yml b/.github/workflows/reviews.yml
new file mode 100644
index 0000000000..1491482b98
--- /dev/null
+++ b/.github/workflows/reviews.yml
@@ -0,0 +1,99 @@
+name: "Request reviews"
+on:
+ pull_request_target:
+ types: [labeled]
+ workflow_run:
+ workflows: [Pull Request Labeler]
+ types: [completed]
+jobs:
+ request-reviewer:
+ runs-on: ubuntu-latest
+ permissions:
+ pull-requests: write
+ steps:
+ - uses: actions/github-script@v5
+ with:
+ script: |
+ // The number of the pull request that triggered this run. If label
+ // was added manually by a person the number will be stored in current
+ // context, otherwise the number will be stored in the payload.
+ const pr_number = context.issue.number || context.payload.workflow_run.pull_requests[0].number
+
+ const pr_data = await github.rest.pulls.get({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ pull_number: pr_number
+ })
+ const labels = pr_data.data.labels.map(e => e.name)
+
+ const reviewers = new Set()
+ if (labels.includes('api')) {
+ reviewers.add("bfredl")
+ reviewers.add("gpanders")
+ reviewers.add("muniter")
+ }
+
+ if (labels.includes('ci')) {
+ reviewers.add("jamessan")
+ }
+
+ if (labels.includes('diagnostic')) {
+ reviewers.add("gpanders")
+ }
+
+ if (labels.includes('distribution')) {
+ reviewers.add("jamessan")
+ }
+
+ if (labels.includes('documentation')) {
+ reviewers.add("clason")
+ }
+
+ if (labels.includes('extmarks')) {
+ reviewers.add("bfredl")
+ }
+
+ if (labels.includes('filetype')) {
+ reviewers.add("clason")
+ reviewers.add("gpanders")
+ }
+
+ if (labels.includes('gui')) {
+ reviewers.add("glacambre")
+ reviewers.add("smolck")
+ }
+
+ if (labels.includes('lsp')) {
+ reviewers.add("mfussenegger")
+ reviewers.add("mjlbach")
+ }
+
+ if (labels.includes('treesitter')) {
+ reviewers.add("bfredl")
+ reviewers.add("vigoux")
+ }
+
+ if (labels.includes('typo')) {
+ reviewers.add("dundargoc")
+ }
+
+ if (labels.includes('ui')) {
+ reviewers.add("bfredl")
+ }
+
+ if (labels.includes('vim-patch')) {
+ reviewers.add("janlazo")
+ reviewers.add("seandewar")
+ reviewers.add("zeertzjq")
+ }
+
+ // Remove person that opened the PR since they can't review themselves
+ const pr_opener = pr_data.data.user.login
+ reviewers.delete(pr_opener)
+
+ github.rest.pulls.requestReviewers({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ pull_number: pr_number,
+ reviewers: Array.from(reviewers)
+ });
diff --git a/ci/common/build.sh b/ci/common/build.sh
index c44c234274..b8bbff0b16 100644
--- a/ci/common/build.sh
+++ b/ci/common/build.sh
@@ -44,7 +44,9 @@ build_deps() {
cd "${CI_BUILD_DIR}"
}
-prepare_build() {
+build_nvim() {
+ check_core_dumps --delete quiet
+
if test -n "${CLANG_SANITIZER}" ; then
CMAKE_FLAGS="${CMAKE_FLAGS} -DCLANG_${CLANG_SANITIZER}=ON"
fi
@@ -53,9 +55,8 @@ prepare_build() {
cd "${BUILD_DIR}"
echo "Configuring with '${CMAKE_FLAGS} $@'."
cmake -G Ninja ${CMAKE_FLAGS} "$@" "${CI_BUILD_DIR}"
-}
-build_nvim() {
+
echo "Building nvim."
if ! top_make nvim ; then
exit 1
diff --git a/ci/common/submit_coverage.sh b/ci/common/submit_coverage.sh
index cb6ab62b5b..f781ca8e5e 100755
--- a/ci/common/submit_coverage.sh
+++ b/ci/common/submit_coverage.sh
@@ -4,7 +4,7 @@
# Args:
# $1: Flag(s) for codecov, separated by comma.
-set -ex
+set -e
# Change to grandparent dir (POSIXly).
CDPATH='' cd -P -- "$(dirname -- "$0")/../.." || exit
diff --git a/ci/common/suite.sh b/ci/common/suite.sh
index f6a8c22d21..f33f8b89d1 100644
--- a/ci/common/suite.sh
+++ b/ci/common/suite.sh
@@ -29,17 +29,14 @@ ci_fold() {
}
enter_suite() {
- set +x
FAILED=0
rm -f "${END_MARKER}"
local suite_name="$1"
export NVIM_TEST_CURRENT_SUITE="${NVIM_TEST_CURRENT_SUITE}/$suite_name"
ci_fold "start" "$suite_name"
- set -x
}
exit_suite() {
- set +x
if test $FAILED -ne 0 ; then
echo "Suite ${NVIM_TEST_CURRENT_SUITE} failed, summary:"
echo "${FAIL_SUMMARY}"
@@ -47,13 +44,7 @@ exit_suite() {
ci_fold "end" ""
fi
export NVIM_TEST_CURRENT_SUITE="${NVIM_TEST_CURRENT_SUITE%/*}"
- if test "$1" != "--continue" ; then
- exit $FAILED
- else
- local saved_failed=$FAILED
- FAILED=0
- return $saved_failed
- fi
+ FAILED=0
}
fail() {
@@ -71,12 +62,6 @@ fail() {
FAILED=1
}
-run_test() {
- local cmd="$1"
- local test_name="$2"
- eval "$cmd" || fail "$test_name"
-}
-
ended_successfully() {
if test -f "${FAIL_SUMMARY_FILE}" ; then
echo 'Test failed, complete summary:'
@@ -100,7 +85,7 @@ run_suite() {
local suite_name="$2"
enter_suite "$suite_name"
- run_test "$command" "$suite_name"
- exit_suite --continue
+ eval "$command" || fail "$suite_name"
+ exit_suite
}
diff --git a/ci/common/test.sh b/ci/common/test.sh
index 798f2926c6..f211a2e7aa 100644
--- a/ci/common/test.sh
+++ b/ci/common/test.sh
@@ -87,18 +87,15 @@ check_sanitizer() {
}
run_unittests() {(
- enter_suite unittests
ulimit -c unlimited || true
if ! build_make unittest ; then
fail 'unittests' F 'Unit tests failed'
fi
submit_coverage unittest
check_core_dumps "$(command -v luajit)"
- exit_suite
)}
run_functionaltests() {(
- enter_suite functionaltests
ulimit -c unlimited || true
if ! build_make ${FUNCTIONALTEST}; then
fail 'functionaltests' F 'Functional tests failed'
@@ -107,11 +104,9 @@ run_functionaltests() {(
check_sanitizer "${LOG_DIR}"
valgrind_check "${LOG_DIR}"
check_core_dumps
- exit_suite
)}
run_oldtests() {(
- enter_suite oldtests
ulimit -c unlimited || true
if ! make oldtest; then
reset
@@ -121,11 +116,9 @@ run_oldtests() {(
check_sanitizer "${LOG_DIR}"
valgrind_check "${LOG_DIR}"
check_core_dumps
- exit_suite
)}
check_runtime_files() {(
- set +x
local test_name="$1" ; shift
local message="$1" ; shift
local tst="$1" ; shift
@@ -146,7 +139,6 @@ check_runtime_files() {(
)}
install_nvim() {(
- enter_suite 'install_nvim'
if ! build_make install ; then
fail 'install' E 'make install failed'
exit_suite
@@ -179,6 +171,4 @@ install_nvim() {(
if ! grep -q "$gpat" "${INSTALL_PREFIX}/share/nvim/runtime/$genvimsynf" ; then
fail 'funcnames' F "It appears that $genvimsynf does not contain $gpat."
fi
-
- exit_suite
)}
diff --git a/ci/run_tests.sh b/ci/run_tests.sh
index 6c310ccc76..1baeb090a8 100755
--- a/ci/run_tests.sh
+++ b/ci/run_tests.sh
@@ -8,29 +8,17 @@ source "${CI_DIR}/common/build.sh"
source "${CI_DIR}/common/test.sh"
source "${CI_DIR}/common/suite.sh"
-enter_suite build
+run_suite 'build_nvim' 'build'
-check_core_dumps --delete quiet
-
-prepare_build
-build_nvim
-
-exit_suite --continue
-
-enter_suite tests
-
-if test "$CLANG_SANITIZER" != "TSAN" ; then
+if test "$CLANG_SANITIZER" != "TSAN"; then
# Additional threads are only created when the builtin UI starts, which
# doesn't happen in the unit/functional tests
if test "${FUNCTIONALTEST}" != "functionaltest-lua"; then
- run_test run_unittests unittests
+ run_suite run_unittests unittests
fi
- run_test run_functionaltests functionaltests
+ run_suite run_functionaltests functionaltests
fi
-run_test run_oldtests oldtests
-
-run_test install_nvim install_nvim
-
-exit_suite --continue
+run_suite run_oldtests oldtests
+run_suite install_nvim install_nvim
end_tests
diff --git a/runtime/autoload/health.vim b/runtime/autoload/health.vim
index 1d462ad02c..ec030adf04 100644
--- a/runtime/autoload/health.vim
+++ b/runtime/autoload/health.vim
@@ -21,10 +21,17 @@ function! health#check(plugin_names) abort
throw 'healthcheck_not_found'
endif
eval type == 'v' ? call(func, []) : luaeval(func)
+ " in the event the healthcheck doesn't return anything
+ " (the plugin author should avoid this possibility)
+ if len(s:output) == 0
+ throw 'healthcheck_no_return_value'
+ endif
catch
let s:output = [] " Clear the output
if v:exception =~# 'healthcheck_not_found'
call health#report_error('No healthcheck found for "'.name.'" plugin.')
+ elseif v:exception =~# 'healthcheck_no_return_value'
+ call health#report_error('The healthcheck report for "'.name.'" plugin is empty.')
else
call health#report_error(printf(
\ "Failed to run healthcheck for \"%s\" plugin. Exception:\n%s\n%s",
@@ -127,7 +134,7 @@ endfunction " }}}
" From a path return a list [{name}, {func}, {type}] representing a healthcheck
function! s:filepath_to_healthcheck(path) abort
- if a:path =~# 'vim$'
+ if a:path =~# 'vim$'
let name = matchstr(a:path, '\zs[^\/]*\ze\.vim$')
let func = 'health#'.name.'#check'
let type = 'v'
diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt
index 0fcf01aadc..f371ad92cc 100644
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -340,6 +340,8 @@ range({expr} [, {max} [, {stride}]])
readdir({dir} [, {expr}]) List file names in {dir} selected by {expr}
readfile({fname} [, {type} [, {max}]])
List get list of lines from file {fname}
+reduce({object}, {func} [, {initial}])
+ any reduce {object} using {func}
reg_executing() String get the executing register name
reg_recorded() String get the last recorded register name
reg_recording() String get the recording register name
@@ -5772,6 +5774,25 @@ readfile({fname} [, {type} [, {max}]])
Can also be used as a |method|: >
GetFileName()->readfile()
+reduce({object}, {func} [, {initial}]) *reduce()* *E998*
+ {func} is called for every item in {object}, which can be a
+ |List| or a |Blob|. {func} is called with two arguments: the
+ result so far and current item. After processing all items
+ the result is returned.
+
+ {initial} is the initial result. When omitted, the first item
+ in {object} is used and {func} is first called for the second
+ item. If {initial} is not given and {object} is empty no
+ result can be computed, an E998 error is given.
+
+ Examples: >
+ echo reduce([1, 3, 5], { acc, val -> acc + val })
+ echo reduce(['x', 'y'], { acc, val -> acc .. val }, 'a')
+ echo reduce(0z1122, { acc, val -> 2 * acc + val })
+<
+ Can also be used as a |method|: >
+ echo mylist->reduce({ acc, val -> acc + val }, 0)
+
reg_executing() *reg_executing()*
Returns the single letter name of the register being executed.
Returns an empty string when no register is being executed.
diff --git a/runtime/doc/change.txt b/runtime/doc/change.txt
index e26a84f80f..b4d3304880 100644
--- a/runtime/doc/change.txt
+++ b/runtime/doc/change.txt
@@ -1118,8 +1118,11 @@ register. With blockwise selection it also depends on the size of the block
and whether the corners are on an existing character. (Implementation detail:
it actually works by first putting the register after the selection and then
deleting the selection.)
-The previously selected text is put in the unnamed register. If you want to
-put the same text into a Visual selection several times you need to use
+With 'p' the previously selected text is put in the unnamed register. This is
+useful if you want to put that text somewhere else. But you cannot repeat the
+same change.
+With 'P' the unnamed register is not changed, you can repeat the same change.
+But the deleted text cannot be used. If you do need it you can use 'p' with
another register. E.g., yank the text to copy, Visually select the text to
replace and use "0p . You can repeat this as many times as you like, and the
unnamed register will be changed each time.
diff --git a/runtime/doc/index.txt b/runtime/doc/index.txt
index f02f9f8032..e3bc3d5437 100644
--- a/runtime/doc/index.txt
+++ b/runtime/doc/index.txt
@@ -923,7 +923,9 @@ tag command note action in Visual mode ~
before the highlighted area
|v_J| J 2 join the highlighted lines
|v_K| K run 'keywordprg' on the highlighted area
-|v_O| O move horizontally to other corner of area.
+|v_O| O move horizontally to other corner of area
+|v_P| P replace highlighted area with register
+ contents; unnamed register is unchanged
Q does not start Ex mode
|v_R| R 2 delete the highlighted lines and start
insert
diff --git a/runtime/doc/quickfix.txt b/runtime/doc/quickfix.txt
index ed736ad4eb..5b68da8be9 100644
--- a/runtime/doc/quickfix.txt
+++ b/runtime/doc/quickfix.txt
@@ -1339,12 +1339,17 @@ Basic items
%f file name (finds a string)
%o module name (finds a string)
%l line number (finds a number)
+ %e end line number (finds a number)
%c column number (finds a number representing character
column of the error, byte index, a <tab> is 1
character column)
%v virtual column number (finds a number representing
screen column of the error (1 <tab> == 8 screen
columns))
+ %k end column number (finds a number representing
+ the character column of the error, byte index, or a
+ number representing screen end column of the error if
+ it's used with %v)
%t error type (finds a single character):
e - error message
w - warning message
diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt
index fc0230c62d..38869f8e94 100644
--- a/runtime/doc/various.txt
+++ b/runtime/doc/various.txt
@@ -388,6 +388,8 @@ g8 Print the hex values of the bytes used in the
|:marks| - filter by text in the current file,
or file name for other files
|:oldfiles| - filter by file name
+ |:registers| - filter by register contents
+ (does not work multi-line)
|:set| - filter by option name
Only normal messages are filtered, error messages are
diff --git a/runtime/doc/visual.txt b/runtime/doc/visual.txt
index 5563a56216..4d5366a41a 100644
--- a/runtime/doc/visual.txt
+++ b/runtime/doc/visual.txt
@@ -255,6 +255,7 @@ Additionally the following commands can be used:
X delete (2) |v_X|
Y yank (2) |v_Y|
p put |v_p|
+ P put without unnamed register overwrite |v_P|
J join (1) |v_J|
U make uppercase |v_U|
u make lowercase |v_u|
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index 37e222a5ce..8d11b4621c 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -1598,7 +1598,7 @@ end
local function adjust_start_col(lnum, line, items, encoding)
local min_start_char = nil
for _, item in pairs(items) do
- if item.textEdit and item.textEdit.range.start.line == lnum - 1 then
+ if item.filterText == nil and item.textEdit and item.textEdit.range.start.line == lnum - 1 then
if min_start_char and min_start_char ~= item.textEdit.range.start.character then
return nil
end
diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua
index b3036ea679..14940332d6 100644
--- a/runtime/lua/vim/treesitter/query.lua
+++ b/runtime/lua/vim/treesitter/query.lua
@@ -199,11 +199,13 @@ function M.get_node_text(node, source)
lines = a.nvim_buf_get_lines(source, start_row, end_row + 1, true)
end
- if #lines == 1 then
- lines[1] = string.sub(lines[1], start_col+1, end_col)
- else
- lines[1] = string.sub(lines[1], start_col+1)
- lines[#lines] = string.sub(lines[#lines], 1, end_col)
+ if #lines > 0 then
+ if #lines == 1 then
+ lines[1] = string.sub(lines[1], start_col+1, end_col)
+ else
+ lines[1] = string.sub(lines[1], start_col+1)
+ lines[#lines] = string.sub(lines[#lines], 1, end_col)
+ end
end
return table.concat(lines, "\n")
diff --git a/scripts/lintcommit.lua b/scripts/lintcommit.lua
index 0a7da4d4ef..6871858a0b 100644
--- a/scripts/lintcommit.lua
+++ b/scripts/lintcommit.lua
@@ -94,10 +94,24 @@ local function validate_commit(commit_message)
return [[Description ends with a period (".").]]
end
- -- Check that description has exactly one whitespace after colon, followed by
- -- a lowercase letter and then any number of letters.
- if not string.match(after_colon, '^ %l%a*') then
- return [[There should be one whitespace after the colon and the first letter should lowercase.]]
+ -- Check that description starts with a whitespace.
+ if after_colon:sub(1,1) ~= " " then
+ return [[There should be a whitespace after the colon.]]
+ end
+
+ -- Check that description doesn't start with multiple whitespaces.
+ if after_colon:sub(1,2) == " " then
+ return [[There should only be one whitespace after the colon.]]
+ end
+
+ -- Check that first character after space isn't uppercase.
+ if string.match(after_colon:sub(2,2), '%u') then
+ return [[First character should not be uppercase.]]
+ end
+
+ -- Check that description isn't just whitespaces
+ if vim.trim(after_colon) == "" then
+ return [[Description shouldn't be empty.]]
end
return nil
@@ -168,12 +182,14 @@ function M._test()
['ci(tui)!: message with scope and breaking change'] = true,
['vim-patch:8.2.3374: Pyret files are not recognized (#15642)'] = true,
['vim-patch:8.1.1195,8.2.{3417,3419}'] = true,
+ ['revert: "ci: use continue-on-error instead of "|| true""'] = true,
[':no type before colon 1'] = false,
[' :no type before colon 2'] = false,
[' :no type before colon 3'] = false,
['ci(empty description):'] = false,
- ['ci(whitespace as description): '] = false,
+ ['ci(only whitespace as description): '] = false,
['docs(multiple whitespaces as description): '] = false,
+ ['revert(multiple whitespaces and then characters as description): description'] = false,
['ci no colon after type'] = false,
['test: extra space after colon'] = false,
['ci: tab after colon'] = false,
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index ada041bab2..f7c55344f5 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -31,6 +31,7 @@
#include "nvim/file_search.h"
#include "nvim/fileio.h"
#include "nvim/getchar.h"
+#include "nvim/globals.h"
#include "nvim/highlight.h"
#include "nvim/highlight_defs.h"
#include "nvim/lua/executor.h"
@@ -545,20 +546,19 @@ void nvim_set_current_dir(String dir, Error *err)
return;
}
- char string[MAXPATHL];
+ char_u string[MAXPATHL];
memcpy(string, dir.data, dir.size);
string[dir.size] = NUL;
try_start();
- if (vim_chdir((char_u *)string)) {
+ if (!changedir_func(string, kCdScopeGlobal)) {
if (!try_end(err)) {
api_set_error(err, kErrorTypeException, "Failed to change directory");
}
return;
}
- post_chdir(kCdScopeGlobal, true);
try_end(err);
}
diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c
index 9fd4de4bb2..fc7823a070 100644
--- a/src/nvim/api/window.c
+++ b/src/nvim/api/window.c
@@ -395,7 +395,7 @@ void nvim_win_hide(Window window, Error *err)
TryState tstate;
try_enter(&tstate);
if (tabpage == curtab) {
- win_close(win, false);
+ win_close(win, false, false);
} else {
win_close_othertab(win, false, tabpage);
}
diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c
index 2e7f9c5136..dfba18b11d 100644
--- a/src/nvim/autocmd.c
+++ b/src/nvim/autocmd.c
@@ -285,7 +285,7 @@ void aubuflocal_remove(buf_T *buf)
}
// Add an autocmd group name.
-// Return its ID. Returns AUGROUP_ERROR (< 0) for error.
+// Return its ID.
static int au_new_group(char_u *name)
{
int i = au_find_group(name);
@@ -379,10 +379,7 @@ void do_augroup(char_u *arg, int del_group)
} else if (STRICMP(arg, "end") == 0) { // ":aug end": back to group 0
current_augroup = AUGROUP_DEFAULT;
} else if (*arg) { // ":aug xxx": switch to group xxx
- int i = au_new_group(arg);
- if (i != AUGROUP_ERROR) {
- current_augroup = i;
- }
+ current_augroup = au_new_group(arg);
} else { // ":aug": list the group names
msg_start();
for (int i = 0; i < augroups.ga_len; i++) {
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index bb8483f644..a9addc0e68 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -846,7 +846,7 @@ void goto_buffer(exarg_T *eap, int start, int dir, int count)
enter_cleanup(&cs);
// Quitting means closing the split window, nothing else.
- win_close(curwin, true);
+ win_close(curwin, true, false);
swap_exists_action = SEA_NONE;
swap_exists_did_quit = true;
@@ -1237,7 +1237,7 @@ int do_buffer(int action, int start, int dir, int count, int forceit)
while (buf == curbuf
&& !(curwin->w_closing || curwin->w_buffer->b_locked > 0)
&& (!ONE_WINDOW || first_tabpage->tp_next != NULL)) {
- if (win_close(curwin, false) == FAIL) {
+ if (win_close(curwin, false, false) == FAIL) {
break;
}
}
@@ -3327,7 +3327,7 @@ void maketitle(void)
len = (int)STRLEN(buf_p);
if (len > 100) {
len -= 100;
- len += (*mb_tail_off)(buf_p, buf_p + len) + 1;
+ len += mb_tail_off(buf_p, buf_p + len) + 1;
buf_p += len;
}
STRCPY(icon_str, buf_p);
@@ -4822,7 +4822,7 @@ void do_arg_all(int count, int forceit, int keep_tabs)
&& (first_tabpage->tp_next == NULL || !had_tab)) {
use_firstwin = true;
} else {
- win_close(wp, !buf_hide(buf) && !bufIsChanged(buf));
+ win_close(wp, !buf_hide(buf) && !bufIsChanged(buf), false);
// check if autocommands removed the next window
if (!win_valid(wpnext)) {
// start all over...
@@ -5013,7 +5013,7 @@ void ex_buffer_all(exarg_T *eap)
&& !ONE_WINDOW
&& !(wp->w_closing
|| wp->w_buffer->b_locked > 0)) {
- win_close(wp, false);
+ win_close(wp, false, false);
wpnext = firstwin; // just in case an autocommand does
// something strange with windows
tpnext = first_tabpage; // start all over...
@@ -5094,7 +5094,7 @@ void ex_buffer_all(exarg_T *eap)
enter_cleanup(&cs);
// User selected Quit at ATTENTION prompt; close this window.
- win_close(curwin, true);
+ win_close(curwin, true, false);
open_wins--;
swap_exists_action = SEA_NONE;
swap_exists_did_quit = true;
@@ -5136,7 +5136,7 @@ void ex_buffer_all(exarg_T *eap)
// BufWrite Autocommands made the window invalid, start over
wp = lastwin;
} else if (r) {
- win_close(wp, !buf_hide(wp->w_buffer));
+ win_close(wp, !buf_hide(wp->w_buffer), false);
open_wins--;
wp = lastwin;
} else {
diff --git a/src/nvim/change.c b/src/nvim/change.c
index 54c4ba5319..6ac759d5e0 100644
--- a/src/nvim/change.c
+++ b/src/nvim/change.c
@@ -1201,10 +1201,10 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment)
// 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 && dir == FORWARD) {
- // Check for a line comment after code.
+ if (flags & OPENLINE_DO_COM) {
lead_len = get_leader_len(saved_line, &lead_flags, dir == BACKWARD, true);
- if (lead_len == 0 && do_cindent) {
+ if (lead_len == 0 && do_cindent && dir == FORWARD) {
+ // Check for a line comment after code.
comment_start = check_linecomment(saved_line);
if (comment_start != MAXCOL) {
lead_len = get_leader_len(saved_line + comment_start,
diff --git a/src/nvim/channel.c b/src/nvim/channel.c
index cd5134fe5f..d79c0acc4a 100644
--- a/src/nvim/channel.c
+++ b/src/nvim/channel.c
@@ -613,7 +613,6 @@ static void on_channel_output(Stream *stream, Channel *chan, RBuffer *buf, size_
} else {
if (chan->term) {
terminal_receive(chan->term, ptr, count);
- terminal_flush_output(chan->term);
}
rbuffer_consumed(buf, count);
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 926c385892..c197754685 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -10700,7 +10700,7 @@ repeat:
pbuf = NULL;
// Need full path first (use expand_env() to remove a "~/")
if (!has_fullname && !has_homerelative) {
- if ((c == '.' || c == '~') && **fnamep == '~') {
+ if (**fnamep == '~') {
p = pbuf = expand_env_save(*fnamep);
} else {
p = pbuf = (char_u *)FullName_save((char *)*fnamep, FALSE);
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index 6dedd0f745..1e39854c86 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -282,6 +282,7 @@ return {
range={args={1, 3}, base=1},
readdir={args={1, 2}, base=1},
readfile={args={1, 3}, base=1},
+ reduce={args={2, 3}, base=1},
reg_executing={},
reg_recording={},
reg_recorded={},
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 111fae0928..db4fb06a73 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -100,6 +100,7 @@ PRAGMA_DIAG_POP
static char *e_listblobarg = N_("E899: Argument of %s must be a List or Blob");
static char *e_invalwindow = N_("E957: Invalid window number");
+static char *e_reduceempty = N_("E998: Reduce of an empty %s with no initial value");
/// Dummy va_list for passing to vim_snprintf
///
@@ -8054,6 +8055,102 @@ static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
+/// "reduce(list, { accumlator, element -> value } [, initial])" function
+static void f_reduce(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ if (argvars[0].v_type != VAR_LIST && argvars[0].v_type != VAR_BLOB) {
+ emsg(_(e_listblobreq));
+ return;
+ }
+
+ const char_u *func_name;
+ partial_T *partial = NULL;
+ if (argvars[1].v_type == VAR_FUNC) {
+ func_name = argvars[1].vval.v_string;
+ } else if (argvars[1].v_type == VAR_PARTIAL) {
+ partial = argvars[1].vval.v_partial;
+ func_name = partial_name(partial);
+ } else {
+ func_name = (const char_u *)tv_get_string(&argvars[1]);
+ }
+ if (*func_name == NUL) {
+ return; // type error or empty name
+ }
+
+ funcexe_T funcexe = FUNCEXE_INIT;
+ funcexe.evaluate = true;
+ funcexe.partial = partial;
+
+ typval_T initial;
+ typval_T argv[3];
+ if (argvars[0].v_type == VAR_LIST) {
+ list_T *const l = argvars[0].vval.v_list;
+ const listitem_T *li;
+
+ if (argvars[2].v_type == VAR_UNKNOWN) {
+ if (tv_list_len(l) == 0) {
+ semsg(_(e_reduceempty), "List");
+ return;
+ }
+ const listitem_T *const first = tv_list_first(l);
+ initial = *TV_LIST_ITEM_TV(first);
+ li = TV_LIST_ITEM_NEXT(l, first);
+ } else {
+ initial = argvars[2];
+ li = tv_list_first(l);
+ }
+
+ tv_copy(&initial, rettv);
+
+ if (l != NULL) {
+ const VarLockStatus prev_locked = tv_list_locked(l);
+ const int called_emsg_start = called_emsg;
+
+ tv_list_set_lock(l, VAR_FIXED); // disallow the list changing here
+ for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) {
+ argv[0] = *rettv;
+ argv[1] = *TV_LIST_ITEM_TV(li);
+ rettv->v_type = VAR_UNKNOWN;
+ const int r = call_func(func_name, -1, rettv, 2, argv, &funcexe);
+ tv_clear(&argv[0]);
+ if (r == FAIL || called_emsg != called_emsg_start) {
+ break;
+ }
+ }
+ tv_list_set_lock(l, prev_locked);
+ }
+ } else {
+ const blob_T *const b = argvars[0].vval.v_blob;
+ int i;
+
+ if (argvars[2].v_type == VAR_UNKNOWN) {
+ if (tv_blob_len(b) == 0) {
+ semsg(_(e_reduceempty), "Blob");
+ return;
+ }
+ initial.v_type = VAR_NUMBER;
+ initial.vval.v_number = tv_blob_get(b, 0);
+ i = 1;
+ } else if (argvars[2].v_type != VAR_NUMBER) {
+ emsg(_(e_number_exp));
+ return;
+ } else {
+ initial = argvars[2];
+ i = 0;
+ }
+
+ tv_copy(&initial, rettv);
+ for (; i < tv_blob_len(b); i++) {
+ argv[0] = *rettv;
+ argv[1].v_type = VAR_NUMBER;
+ argv[1].vval.v_number = tv_blob_get(b, i);
+ if (call_func(func_name, -1, rettv, 2, argv, &funcexe) == FAIL) {
+ return;
+ }
+ }
+ }
+}
+
#define SP_NOMOVE 0x01 ///< don't move cursor
#define SP_REPEAT 0x02 ///< repeat to find outer pair
#define SP_RETCOUNT 0x04 ///< return matchcount
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 81fce3565a..fd1e34803f 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -5856,7 +5856,7 @@ void ex_helpclose(exarg_T *eap)
{
FOR_ALL_WINDOWS_IN_TAB(win, curtab) {
if (bt_help(win->w_buffer)) {
- win_close(win, false);
+ win_close(win, false, eap->forceit);
return;
}
}
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c
index e5cec0e060..090422088a 100644
--- a/src/nvim/ex_cmds2.c
+++ b/src/nvim/ex_cmds2.c
@@ -12,6 +12,7 @@
#include <string.h>
#include "nvim/ascii.h"
+#include "nvim/globals.h"
#include "nvim/vim.h"
#ifdef HAVE_LOCALE_H
# include <locale.h>
@@ -2190,9 +2191,11 @@ void ex_scriptnames(exarg_T *eap)
if (SCRIPT_ITEM(i).sn_name != NULL) {
home_replace(NULL, SCRIPT_ITEM(i).sn_name, NameBuff, MAXPATHL, true);
vim_snprintf((char *)IObuff, IOSIZE, "%3d: %s", i, NameBuff);
- msg_putchar('\n');
- msg_outtrans(IObuff);
- line_breakcheck();
+ if (!message_filtered(IObuff)) {
+ msg_putchar('\n');
+ msg_outtrans(IObuff);
+ line_breakcheck();
+ }
}
}
}
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index d884838136..4dba0b97ed 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -6619,7 +6619,7 @@ static void ex_quit(exarg_T *eap)
}
not_exiting();
// close window; may free buffer
- win_close(wp, !buf_hide(wp->w_buffer) || eap->forceit);
+ win_close(wp, !buf_hide(wp->w_buffer) || eap->forceit, eap->forceit);
}
}
@@ -6734,7 +6734,7 @@ void ex_win_close(int forceit, win_T *win, tabpage_T *tp)
// free buffer when not hiding it or when it's a scratch buffer
if (tp == NULL) {
- win_close(win, !need_hide && !buf_hide(buf));
+ win_close(win, !need_hide && !buf_hide(buf), forceit);
} else {
win_close_othertab(win, !need_hide && !buf_hide(buf), tp);
}
@@ -6898,7 +6898,7 @@ static void ex_hide(exarg_T *eap)
// ":hide" or ":hide | cmd": hide current window
if (!eap->skip) {
if (eap->addr_count == 0) {
- win_close(curwin, false); // don't free buffer
+ win_close(curwin, false, eap->forceit); // don't free buffer
} else {
int winnr = 0;
win_T *win = NULL;
@@ -6913,7 +6913,7 @@ static void ex_hide(exarg_T *eap)
if (win == NULL) {
win = lastwin;
}
- win_close(win, false);
+ win_close(win, false, eap->forceit);
}
}
}
@@ -6971,7 +6971,7 @@ static void ex_exit(exarg_T *eap)
}
not_exiting();
// Quit current window, may free the buffer.
- win_close(curwin, !buf_hide(curwin->w_buffer));
+ win_close(curwin, !buf_hide(curwin->w_buffer), eap->forceit);
}
}
@@ -7572,7 +7572,7 @@ void do_exedit(exarg_T *eap, win_T *old_curwin)
// Reset the error/interrupt/exception state here so that
// aborting() returns FALSE when closing a window.
enter_cleanup(&cs);
- win_close(curwin, !need_hide && !buf_hide(curbuf));
+ win_close(curwin, !need_hide && !buf_hide(curbuf), false);
// Restore the error/interrupt/exception state if not
// discarded by a new aborting error, interrupt, or
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index f81f49a174..ed4475eb1a 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -6563,7 +6563,7 @@ static int open_cmdwin(void)
set_bufref(&bufref, curbuf);
win_goto(old_curwin);
if (win_valid(wp) && wp != curwin) {
- win_close(wp, true);
+ win_close(wp, true, false);
}
// win_close() may have already wiped the buffer when 'bh' is
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index f28ee1bfcb..8e1be3bbf7 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -3318,7 +3318,7 @@ restore_backup:
if (end == 0
|| (lnum == end
&& (write_bin || !buf->b_p_fixeol)
- && (lnum == buf->b_no_eol_lnum
+ && ((write_bin && lnum == buf->b_no_eol_lnum)
|| (lnum == buf->b_ml.ml_line_count && !buf->b_p_eol)))) {
lnum++; // written the line, count it
no_eol = true;
diff --git a/src/nvim/main.c b/src/nvim/main.c
index 748f5098fd..8991ae7f00 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -1560,7 +1560,7 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd)
// When w_arg_idx is -1 remove the window (see create_windows()).
if (curwin->w_arg_idx == -1) {
- win_close(curwin, true);
+ win_close(curwin, true, false);
advance = false;
}
@@ -1572,7 +1572,7 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd)
// When w_arg_idx is -1 remove the window (see create_windows()).
if (curwin->w_arg_idx == -1) {
arg_idx++;
- win_close(curwin, true);
+ win_close(curwin, true, false);
advance = false;
continue;
}
@@ -1619,7 +1619,7 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd)
did_emsg = FALSE; // avoid hit-enter prompt
getout(1);
}
- win_close(curwin, true);
+ win_close(curwin, true, false);
advance = false;
}
if (arg_idx == GARGCOUNT - 1) {
diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c
index 7fa2562be0..e5fa80a242 100644
--- a/src/nvim/mbyte.c
+++ b/src/nvim/mbyte.c
@@ -1821,12 +1821,10 @@ void mb_copy_char(const char_u **const fp, char_u **const tp)
*fp += l;
}
-/*
- * Return the offset from "p" to the first byte of a character. When "p" is
- * at the start of a character 0 is returned, otherwise the offset to the next
- * character. Can start anywhere in a stream of bytes.
- */
-int mb_off_next(char_u *base, char_u *p)
+/// Return the offset from "p" to the first byte of a character. When "p" is
+/// at the start of a character 0 is returned, otherwise the offset to the next
+/// character. Can start anywhere in a stream of bytes.
+int mb_off_next(const char_u *base, const char_u *p)
{
int i;
int j;
@@ -1854,7 +1852,7 @@ int mb_off_next(char_u *base, char_u *p)
/// Return the offset from "p" to the last byte of the character it points
/// into. Can start anywhere in a stream of bytes.
/// Composing characters are not included.
-int mb_tail_off(char_u *base, char_u *p)
+int mb_tail_off(const char_u *base, const char_u *p)
{
int i;
int j;
@@ -1882,12 +1880,13 @@ int mb_tail_off(char_u *base, char_u *p)
/// Return the offset from "p" to the first byte of the character it points
/// into. Can start anywhere in a stream of bytes.
+/// Unlike utf_head_off() this doesn't include composing characters and returns a negative value.
///
/// @param[in] base Pointer to start of string
/// @param[in] p Pointer to byte for which to return the offset to the previous codepoint
//
/// @return 0 if invalid sequence, else offset to previous codepoint
-int mb_head_off(char_u *base, char_u *p)
+int mb_head_off(const char_u *base, const char_u *p)
{
int i;
int j;
@@ -2037,13 +2036,11 @@ char_u *mb_prevptr(char_u *line, char_u *p)
return p;
}
-/*
- * Return the character length of "str". Each multi-byte character (with
- * following composing characters) counts as one.
- */
-int mb_charlen(char_u *str)
+/// Return the character length of "str". Each multi-byte character (with
+/// following composing characters) counts as one.
+int mb_charlen(const char_u *str)
{
- char_u *p = str;
+ const char_u *p = str;
int count;
if (p == NULL) {
@@ -2057,12 +2054,10 @@ int mb_charlen(char_u *str)
return count;
}
-/*
- * Like mb_charlen() but for a string with specified length.
- */
-int mb_charlen_len(char_u *str, int len)
+/// Like mb_charlen() but for a string with specified length.
+int mb_charlen_len(const char_u *str, int len)
{
- char_u *p = str;
+ const char_u *p = str;
int count;
for (count = 0; *p != NUL && p < str + len; count++) {
@@ -2201,11 +2196,9 @@ char_u *enc_canonize(char_u *enc) FUNC_ATTR_NONNULL_RET
return r;
}
-/*
- * Search for an encoding alias of "name".
- * Returns -1 when not found.
- */
-static int enc_alias_search(char_u *name)
+/// Search for an encoding alias of "name".
+/// Returns -1 when not found.
+static int enc_alias_search(const char_u *name)
{
int i;
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 225c66aae1..21c465434a 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -7509,9 +7509,9 @@ static void nv_put_opt(cmdarg_T *cap, bool fix_indent)
// overwrites if the old contents is being put.
was_visual = true;
regname = cap->oap->regname;
+ bool save_unnamed = cap->cmdchar == 'P';
// '+' and '*' could be the same selection
- bool clipoverwrite = (regname == '+' || regname == '*')
- && (cb_flags & CB_UNNAMEDMASK);
+ bool clipoverwrite = (regname == '+' || regname == '*') && (cb_flags & CB_UNNAMEDMASK);
if (regname == 0 || regname == '"' || clipoverwrite
|| ascii_isdigit(regname) || regname == '-') {
// The delete might overwrite the register we want to put, save it first
@@ -7524,6 +7524,10 @@ static void nv_put_opt(cmdarg_T *cap, bool fix_indent)
// do_put(), which requires the visual selection to still be active.
if (!VIsual_active || VIsual_mode == 'V' || regname != '.') {
// Now delete the selected text. Avoid messages here.
+ yankreg_T *old_y_previous;
+ if (save_unnamed) {
+ old_y_previous = get_y_previous();
+ }
cap->cmdchar = 'd';
cap->nchar = NUL;
cap->oap->regname = NUL;
@@ -7533,6 +7537,10 @@ static void nv_put_opt(cmdarg_T *cap, bool fix_indent)
empty = (curbuf->b_ml.ml_flags & ML_EMPTY);
msg_silent--;
+ if (save_unnamed) {
+ set_y_previous(old_y_previous);
+ }
+
// delete PUT_LINE_BACKWARD;
cap->oap->regname = regname;
}
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index b8b639265c..f999b68236 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -135,10 +135,18 @@ static char opchars[][3] =
{ Ctrl_X, NUL, OPF_CHANGE }, // OP_NR_SUB
};
-/*
- * Translate a command name into an operator type.
- * Must only be called with a valid operator name!
- */
+yankreg_T *get_y_previous(void)
+{
+ return y_previous;
+}
+
+void set_y_previous(yankreg_T *yreg)
+{
+ y_previous = yreg;
+}
+
+/// Translate a command name into an operator type.
+/// Must only be called with a valid operator name!
int get_op_type(int char1, int char2)
{
int i;
@@ -566,7 +574,7 @@ static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def
if (b_insert) {
off = utf_head_off(oldp, oldp + offset + spaces);
} else {
- off = (*mb_off_next)(oldp, oldp + offset);
+ off = mb_off_next(oldp, oldp + offset);
offset += off;
}
spaces -= off;
@@ -3724,7 +3732,7 @@ void ex_display(exarg_T *eap)
int name;
char_u *arg = eap->arg;
int clen;
- char_u type[2];
+ int type;
if (arg != NULL && *arg == NUL) {
arg = NULL;
@@ -3737,11 +3745,11 @@ void ex_display(exarg_T *eap)
name = get_register_name(i);
switch (get_reg_type(name, NULL)) {
case kMTLineWise:
- type[0] = 'l'; break;
+ type = 'l'; break;
case kMTCharWise:
- type[0] = 'c'; break;
+ type = 'c'; break;
default:
- type[0] = 'b'; break;
+ type = 'b'; break;
}
if (arg != NULL && vim_strchr(arg, name) == NULL) {
@@ -3768,88 +3776,87 @@ void ex_display(exarg_T *eap)
}
if (yb->y_array != NULL) {
- msg_putchar('\n');
- msg_puts(" ");
- msg_putchar(type[0]);
- msg_puts(" ");
- msg_putchar('"');
- msg_putchar(name);
- msg_puts(" ");
-
- int n = Columns - 11;
- for (size_t j = 0; j < yb->y_size && n > 1; j++) {
- if (j) {
- msg_puts_attr("^J", attr);
- n -= 2;
+ bool do_show = false;
+
+ for (size_t j = 0; !do_show && j < yb->y_size; j++) {
+ do_show = !message_filtered(yb->y_array[j]);
+ }
+
+ if (do_show || yb->y_size == 0) {
+ msg_putchar('\n');
+ msg_puts(" ");
+ msg_putchar(type);
+ msg_puts(" ");
+ msg_putchar('"');
+ msg_putchar(name);
+ msg_puts(" ");
+
+ int n = Columns - 11;
+ 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++) {
+ clen = utfc_ptr2len(p);
+ msg_outtrans_len(p, clen);
+ p += clen - 1;
+ }
}
- for (p = yb->y_array[j]; *p && (n -= ptr2cells(p)) >= 0; p++) { // -V1019 NOLINT(whitespace/line_length)
- clen = utfc_ptr2len(p);
- msg_outtrans_len(p, clen);
- p += clen - 1;
+ if (n > 1 && yb->y_type == kMTLineWise) {
+ msg_puts_attr("^J", attr);
}
+ ui_flush(); // show one line at a time
}
- if (n > 1 && yb->y_type == kMTLineWise) {
- msg_puts_attr("^J", attr);
- }
- ui_flush(); // show one line at a time
+ os_breakcheck();
}
- os_breakcheck();
}
- /*
- * display last inserted text
- */
+ // display last inserted text
if ((p = get_last_insert()) != NULL
- && (arg == NULL || vim_strchr(arg, '.') != NULL) && !got_int) {
+ && (arg == NULL || vim_strchr(arg, '.') != NULL) && !got_int
+ && !message_filtered(p)) {
msg_puts("\n c \". ");
dis_msg(p, true);
}
- /*
- * display last command line
- */
+ // display last command line
if (last_cmdline != NULL && (arg == NULL || vim_strchr(arg, ':') != NULL)
- && !got_int) {
+ && !got_int && !message_filtered(last_cmdline)) {
msg_puts("\n c \": ");
dis_msg(last_cmdline, false);
}
- /*
- * display current file name
- */
+ // display current file name
if (curbuf->b_fname != NULL
- && (arg == NULL || vim_strchr(arg, '%') != NULL) && !got_int) {
+ && (arg == NULL || vim_strchr(arg, '%') != NULL) && !got_int
+ && !message_filtered(curbuf->b_fname)) {
msg_puts("\n c \"% ");
dis_msg(curbuf->b_fname, false);
}
- /*
- * display alternate file name
- */
+ // display alternate file name
if ((arg == NULL || vim_strchr(arg, '%') != NULL) && !got_int) {
char_u *fname;
linenr_T dummy;
- if (buflist_name_nr(0, &fname, &dummy) != FAIL) {
+ if (buflist_name_nr(0, &fname, &dummy) != FAIL && !message_filtered(fname)) {
msg_puts("\n c \"# ");
dis_msg(fname, false);
}
}
- /*
- * display last search pattern
- */
+ // display last search pattern
if (last_search_pat() != NULL
- && (arg == NULL || vim_strchr(arg, '/') != NULL) && !got_int) {
+ && (arg == NULL || vim_strchr(arg, '/') != NULL) && !got_int
+ && !message_filtered(last_search_pat())) {
msg_puts("\n c \"/ ");
dis_msg(last_search_pat(), false);
}
- /*
- * display last used expression
- */
+ // display last used expression
if (expr_line != NULL && (arg == NULL || vim_strchr(arg, '=') != NULL)
- && !got_int) {
+ && !got_int && !message_filtered(expr_line)) {
msg_puts("\n c \"= ");
dis_msg(expr_line, false);
}
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index c609daa55c..7e29aed51b 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -131,7 +131,7 @@ struct qf_info_S {
static qf_info_T ql_info; // global quickfix list
static unsigned last_qf_id = 0; // Last Used quickfix list id
-#define FMT_PATTERNS 11 // maximum number of % recognized
+#define FMT_PATTERNS 13 // maximum number of % recognized
// Structure used to hold the info of one part of 'errorformat'
typedef struct efm_S efm_T;
@@ -209,6 +209,17 @@ typedef struct {
bool valid;
} qffields_T;
+/// :vimgrep command arguments
+typedef struct vgr_args_S {
+ long tomatch; ///< maximum number of matches to find
+ char_u *spat; ///< search pattern
+ int flags; ///< search modifier
+ char_u **fnames; ///< list of files to search
+ int fcount; ///< number of files
+ regmmatch_T regmatch; ///< compiled search pattern
+ char_u *qf_title; ///< quickfix list title
+} vgr_args_T;
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "quickfix.c.generated.h"
#endif
@@ -321,22 +332,27 @@ int qf_init(win_T *wp, const char_u *restrict efile, char_u *restrict errorforma
// Maximum number of bytes allowed per line while reading an errorfile.
static const size_t LINE_MAXLEN = 4096;
+/// Patterns used. Keep in sync with qf_parse_fmt[].
static struct fmtpattern {
char_u convchar;
char *pattern;
} fmt_pat[FMT_PATTERNS] =
{
- { 'f', ".\\+" }, // only used when at end
- { 'n', "\\d\\+" },
- { 'l', "\\d\\+" },
- { 'c', "\\d\\+" },
- { 't', "." },
- { 'm', ".\\+" },
- { 'r', ".*" },
- { 'p', "[- .]*"}, // NOLINT(whitespace/tab)
- { 'v', "\\d\\+" },
- { 's', ".\\+" },
- { 'o', ".\\+" }
+ { 'f', ".\\+" }, // only used when at end
+ { 'n', "\\d\\+" }, // 1
+ { 'l', "\\d\\+" }, // 2
+ { 'e', "\\d\\+" }, // 3
+ { 'c', "\\d\\+" }, // 4
+ { 'k', "\\d\\+" }, // 5
+ { 't', "." }, // 6
+#define FMT_PATTERN_M 7
+ { 'm', ".\\+" }, // 7
+#define FMT_PATTERN_R 8
+ { 'r', ".*" }, // 8
+ { 'p', "[- \t.]*" }, // 9
+ { 'v', "\\d\\+" }, // 10
+ { 's', ".\\+" }, // 11
+ { 'o', ".\\+" } // 12
};
/// Convert an errorformat pattern to a regular expression pattern.
@@ -352,9 +368,9 @@ static char_u *efmpat_to_regpat(const char_u *efmpat, char_u *regpat, efm_T *efm
semsg(_("E372: Too many %%%c in format string"), *efmpat);
return NULL;
}
- if ((idx && idx < 6
+ if ((idx && idx < FMT_PATTERN_R
&& vim_strchr((char_u *)"DXOPQ", efminfo->prefix) != NULL)
- || (idx == 6
+ || (idx == FMT_PATTERN_R
&& vim_strchr((char_u *)"OPQ", efminfo->prefix) == NULL)) {
semsg(_("E373: Unexpected %%%c in format string"), *efmpat);
return NULL;
@@ -1277,7 +1293,7 @@ static int qf_parse_fmt_n(regmatch_T *rmp, int midx, qffields_T *fields)
return QF_OK;
}
-/// Parse the match for line number (%l') pattern in regmatch.
+/// Parse the match for line number ('%l') pattern in regmatch.
/// Return the matched value in "fields->lnum".
static int qf_parse_fmt_l(regmatch_T *rmp, int midx, qffields_T *fields)
{
@@ -1288,6 +1304,17 @@ static int qf_parse_fmt_l(regmatch_T *rmp, int midx, qffields_T *fields)
return QF_OK;
}
+/// Parse the match for end line number ('%e') pattern in regmatch.
+/// Return the matched value in "fields->end_lnum".
+static int qf_parse_fmt_e(regmatch_T *rmp, int midx, qffields_T *fields)
+{
+ if (rmp->startp[midx] == NULL) {
+ return QF_FAIL;
+ }
+ fields->end_lnum = atol((char *)rmp->startp[midx]);
+ return QF_OK;
+}
+
/// Parse the match for column number ('%c') pattern in regmatch.
/// Return the matched value in "fields->col".
static int qf_parse_fmt_c(regmatch_T *rmp, int midx, qffields_T *fields)
@@ -1299,6 +1326,17 @@ static int qf_parse_fmt_c(regmatch_T *rmp, int midx, qffields_T *fields)
return QF_OK;
}
+/// Parse the match for end line number ('%e') pattern in regmatch.
+/// Return the matched value in "fields->end_lnum".
+static int qf_parse_fmt_k(regmatch_T *rmp, int midx, qffields_T *fields)
+{
+ if (rmp->startp[midx] == NULL) {
+ return QF_FAIL;
+ }
+ fields->end_col = (int)atol((char *)rmp->startp[midx]);
+ return QF_OK;
+}
+
/// Parse the match for error type ('%t') pattern in regmatch.
/// Return the matched value in "fields->type".
static int qf_parse_fmt_t(regmatch_T *rmp, int midx, qffields_T *fields)
@@ -1431,14 +1469,17 @@ static int qf_parse_fmt_o(regmatch_T *rmp, int midx, qffields_T *fields)
/// 'errorformat' format pattern parser functions.
/// The '%f' and '%r' formats are parsed differently from other formats.
/// See qf_parse_match() for details.
+/// Keep in sync with fmt_pat[].
static int (*qf_parse_fmt[FMT_PATTERNS])(regmatch_T *, int, qffields_T *) = {
- NULL,
+ NULL, // %f
qf_parse_fmt_n,
qf_parse_fmt_l,
+ qf_parse_fmt_e,
qf_parse_fmt_c,
+ qf_parse_fmt_k,
qf_parse_fmt_t,
qf_parse_fmt_m,
- NULL,
+ NULL, // %r
qf_parse_fmt_p,
qf_parse_fmt_v,
qf_parse_fmt_s,
@@ -1474,13 +1515,13 @@ static int qf_parse_match(char_u *linebuf, size_t linelen, efm_T *fmt_ptr, regma
midx = (int)fmt_ptr->addr[i];
if (i == 0 && midx > 0) { // %f
status = qf_parse_fmt_f(regmatch, midx, fields, idx);
- } else if (i == 5) {
+ } else if (i == FMT_PATTERN_M) {
if (fmt_ptr->flags == '+' && !qf_multiscan) { // %+
qf_parse_fmt_plus(linebuf, linelen, fields);
} else if (midx > 0) { // %m
status = qf_parse_fmt_m(regmatch, midx, fields);
}
- } else if (i == 6 && midx > 0) { // %r
+ } else if (i == FMT_PATTERN_R && midx > 0) { // %r
status = qf_parse_fmt_r(regmatch, midx, tail);
} else if (midx > 0) { // others
status = (qf_parse_fmt[i])(regmatch, midx, fields);
@@ -1625,10 +1666,16 @@ static int qf_parse_multiline_pfx(int idx, qf_list_T *qfl, qffields_T *fields)
if (!qfprev->qf_lnum) {
qfprev->qf_lnum = fields->lnum;
}
+ if (!qfprev->qf_end_lnum) {
+ qfprev->qf_end_lnum = fields->end_lnum;
+ }
if (!qfprev->qf_col) {
qfprev->qf_col = fields->col;
qfprev->qf_viscol = fields->use_viscol;
}
+ if (!qfprev->qf_end_col) {
+ qfprev->qf_end_col = fields->end_col;
+ }
if (!qfprev->qf_fnum) {
qfprev->qf_fnum = qf_get_fnum(qfl, qfl->qf_directory,
*fields->namebuf || qfl->qf_directory
@@ -2975,7 +3022,7 @@ static void qf_jump_newwin(qf_info_T *qi, int dir, int errornr, int forceit, boo
if (retval != OK) {
if (opened_window) {
- win_close(curwin, true); // Close opened window
+ win_close(curwin, true, false); // Close opened window
}
if (qf_ptr != NULL && qf_ptr->qf_fnum != 0) {
// Couldn't open file, so put index back where it was. This could
@@ -3530,7 +3577,7 @@ void ex_cclose(exarg_T *eap)
// Find existing quickfix window and close it.
win = qf_find_win(qi);
if (win != NULL) {
- win_close(win, false);
+ win_close(win, false, false);
}
}
@@ -3604,7 +3651,7 @@ static int qf_open_new_cwindow(qf_info_T *qi, int height)
// win_split, so add a check to ensure that the win is still here
if (IS_LL_STACK(qi) && !win_valid(win)) {
// close the window that was supposed to be for the loclist
- win_close(curwin, false);
+ win_close(curwin, false, false);
return FAIL;
}
@@ -4849,11 +4896,12 @@ static qfline_T *qf_find_closest_entry(qf_list_T *qfl, int bnr, const pos_T *pos
/// Get the nth quickfix entry below the specified entry. Searches forward in
/// the list. If linewise is true, then treat multiple entries on a single line
/// as one.
-static void qf_get_nth_below_entry(qfline_T *entry, linenr_T n, bool linewise, int *errornr)
+static void qf_get_nth_below_entry(qfline_T *entry_arg, linenr_T n, bool linewise, int *errornr)
FUNC_ATTR_NONNULL_ALL
{
+ qfline_T *entry = entry_arg;
+
while (n-- > 0 && !got_int) {
- // qfline_T *first_entry = entry;
int first_errornr = *errornr;
if (linewise) {
@@ -4864,9 +4912,6 @@ static void qf_get_nth_below_entry(qfline_T *entry, linenr_T n, bool linewise, i
if (entry->qf_next == NULL
|| entry->qf_next->qf_fnum != entry->qf_fnum) {
if (linewise) {
- // If multiple entries are on the same line, then use the first
- // entry
- // entry = first_entry;
*errornr = first_errornr;
}
break;
@@ -5293,7 +5338,7 @@ static bool vgr_match_buflines(qf_list_T *qfl, char_u *fname, buf_T *buf, char_u
}
/// Jump to the first match and update the directory.
-static void vgr_jump_to_match(qf_info_T *qi, int forceit, int *redraw_for_dummy,
+static void vgr_jump_to_match(qf_info_T *qi, int forceit, bool *redraw_for_dummy,
buf_T *first_match_buf, char_u *target_dir)
{
buf_T *buf = curbuf;
@@ -5328,104 +5373,72 @@ static bool existing_swapfile(const buf_T *buf)
return false;
}
-// ":vimgrep {pattern} file(s)"
-// ":vimgrepadd {pattern} file(s)"
-// ":lvimgrep {pattern} file(s)"
-// ":lvimgrepadd {pattern} file(s)"
-void ex_vimgrep(exarg_T *eap)
+/// Process :vimgrep command arguments. The command syntax is:
+///
+/// :{count}vimgrep /{pattern}/[g][j] {file} ...
+static int vgr_process_args(exarg_T *eap, vgr_args_T *args)
{
- regmmatch_T regmatch;
- int fcount;
- char_u **fnames;
- char_u *fname;
- char_u *s;
- char_u *p;
- int fi;
- qf_list_T *qfl;
- win_T *wp = NULL;
- buf_T *buf;
- int duplicate_name = FALSE;
- int using_dummy;
- int redraw_for_dummy = FALSE;
- int found_match;
- buf_T *first_match_buf = NULL;
- time_t seconds = 0;
- aco_save_T aco;
- int flags = 0;
- long tomatch;
- char_u *dirname_start = NULL;
- char_u *dirname_now = NULL;
- char_u *target_dir = NULL;
- char_u *au_name = NULL;
+ memset(args, 0, sizeof(*args));
- au_name = vgr_get_auname(eap->cmdidx);
- if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name,
- curbuf->b_fname, true, curbuf)) {
- if (aborting()) {
- return;
- }
- }
-
- qf_info_T *qi = qf_cmd_get_or_alloc_stack(eap, &wp);
+ args->regmatch.regprog = NULL;
+ args->qf_title = vim_strsave(qf_cmdtitle(*eap->cmdlinep));
if (eap->addr_count > 0) {
- tomatch = eap->line2;
+ args->tomatch = eap->line2;
} else {
- tomatch = MAXLNUM;
+ args->tomatch = MAXLNUM;
}
// Get the search pattern: either white-separated or enclosed in //
- regmatch.regprog = NULL;
- char_u *title = vim_strsave(qf_cmdtitle(*eap->cmdlinep));
- p = skip_vimgrep_pat(eap->arg, &s, &flags);
+ char_u *p = skip_vimgrep_pat(eap->arg, &args->spat, &args->flags);
if (p == NULL) {
emsg(_(e_invalpat));
- goto theend;
+ return FAIL;
}
- vgr_init_regmatch(&regmatch, s);
- if (regmatch.regprog == NULL) {
- goto theend;
+ vgr_init_regmatch(&args->regmatch, args->spat);
+ if (args->regmatch.regprog == NULL) {
+ return FAIL;
}
p = skipwhite(p);
if (*p == NUL) {
emsg(_("E683: File name missing or invalid pattern"));
- goto theend;
- }
-
- if ((eap->cmdidx != CMD_grepadd && eap->cmdidx != CMD_lgrepadd
- && eap->cmdidx != CMD_vimgrepadd && eap->cmdidx != CMD_lvimgrepadd)
- || qf_stack_empty(qi)) {
- // make place for a new list
- qf_new_list(qi, title);
+ return FAIL;
}
// Parse the list of arguments, wildcards have already been expanded.
- if (get_arglist_exp(p, &fcount, &fnames, true) == FAIL) {
- goto theend;
+ if (get_arglist_exp(p, &args->fcount, &args->fnames, true) == FAIL) {
+ return FAIL;
}
- if (fcount == 0) {
+ if (args->fcount == 0) {
emsg(_(e_nomatch));
- goto theend;
+ return FAIL;
}
- dirname_start = xmalloc(MAXPATHL);
- dirname_now = xmalloc(MAXPATHL);
+ return OK;
+}
+
+/// Search for a pattern in a list of files and populate the quickfix list with
+/// the matches.
+static int vgr_process_files(win_T *wp, qf_info_T *qi, vgr_args_T *cmd_args,
+ bool *redraw_for_dummy, buf_T **first_match_buf,
+ char_u **target_dir)
+{
+ int status = FAIL;
+ unsigned save_qfid = qf_get_curlist(qi)->qf_id;
+ bool duplicate_name = false;
+
+ char_u *dirname_start = xmalloc(MAXPATHL);
+ char_u *dirname_now = xmalloc(MAXPATHL);
// Remember the current directory, because a BufRead autocommand that does
// ":lcd %:p:h" changes the meaning of short path names.
os_dirname(dirname_start, MAXPATHL);
- incr_quickfix_busy();
-
- // Remember the current quickfix list identifier, so that we can check for
- // autocommands changing the current quickfix list.
- unsigned save_qfid = qf_get_curlist(qi)->qf_id;
-
- seconds = (time_t)0;
- for (fi = 0; fi < fcount && !got_int && tomatch > 0; fi++) {
- fname = path_try_shorten_fname(fnames[fi]);
+ time_t seconds = (time_t)0;
+ for (int fi = 0; fi < cmd_args->fcount && !got_int && cmd_args->tomatch > 0; fi++) {
+ char_u *fname = path_try_shorten_fname(cmd_args->fnames[fi]);
if (time(NULL) > seconds) {
// Display the file name every second or so, show the user we are
// working on it.
@@ -5433,13 +5446,13 @@ void ex_vimgrep(exarg_T *eap)
vgr_display_fname(fname);
}
- buf = buflist_findname_exp(fnames[fi]);
+ buf_T *buf = buflist_findname_exp(cmd_args->fnames[fi]);
+ bool using_dummy;
if (buf == NULL || buf->b_ml.ml_mfp == NULL) {
// Remember that a buffer with this name already exists.
duplicate_name = (buf != NULL);
- using_dummy = TRUE;
- redraw_for_dummy = TRUE;
-
+ using_dummy = true;
+ *redraw_for_dummy = true;
buf = vgr_load_dummy_buf(fname, dirname_start, dirname_now);
} else {
// Use existing, loaded buffer.
@@ -5448,11 +5461,10 @@ void ex_vimgrep(exarg_T *eap)
// Check whether the quickfix list is still valid. When loading a
// buffer above, autocommands might have changed the quickfix list.
- if (!vgr_qflist_valid(wp, qi, save_qfid, *eap->cmdlinep)) {
- FreeWild(fcount, fnames);
- decr_quickfix_busy();
+ if (!vgr_qflist_valid(wp, qi, save_qfid, cmd_args->qf_title)) {
goto theend;
}
+
save_qfid = qf_get_curlist(qi)->qf_id;
if (buf == NULL) {
@@ -5462,12 +5474,18 @@ void ex_vimgrep(exarg_T *eap)
} else {
// Try for a match in all lines of the buffer.
// For ":1vimgrep" look for first match only.
- found_match = vgr_match_buflines(qf_get_curlist(qi), fname, buf, s, &regmatch, &tomatch,
- duplicate_name, flags);
+ bool found_match = vgr_match_buflines(qf_get_curlist(qi),
+ fname,
+ buf,
+ cmd_args->spat,
+ &cmd_args->regmatch,
+ &cmd_args->tomatch,
+ duplicate_name,
+ cmd_args->flags);
if (using_dummy) {
- if (found_match && first_match_buf == NULL) {
- first_match_buf = buf;
+ if (found_match && *first_match_buf == NULL) {
+ *first_match_buf = buf;
}
if (duplicate_name) {
// Never keep a dummy buffer if there is another buffer
@@ -5487,8 +5505,8 @@ void ex_vimgrep(exarg_T *eap)
if (!found_match) {
wipe_dummy_buffer(buf, dirname_start);
buf = NULL;
- } else if (buf != first_match_buf
- || (flags & VGR_NOJUMP)
+ } else if (buf != *first_match_buf
+ || (cmd_args->flags & VGR_NOJUMP)
|| existing_swapfile(buf)) {
unload_dummy_buffer(buf, dirname_start);
// Keeping the buffer, remove the dummy flag.
@@ -5503,16 +5521,17 @@ void ex_vimgrep(exarg_T *eap)
// If the buffer is still loaded we need to use the
// directory we jumped to below.
- if (buf == first_match_buf
- && target_dir == NULL
+ if (buf == *first_match_buf
+ && *target_dir == NULL
&& STRCMP(dirname_start, dirname_now) != 0) {
- target_dir = vim_strsave(dirname_now);
+ *target_dir = vim_strsave(dirname_now);
}
// The buffer is still loaded, the Filetype autocommands
// need to be done now, in that buffer. And the modelines
// need to be done (again). But not the window-local
// options!
+ aco_save_T aco;
aucmd_prepbuf(&aco, buf);
apply_autocmds(EVENT_FILETYPE, buf->b_p_ft, buf->b_fname, true, buf);
do_modelines(OPT_NOWIN);
@@ -5522,9 +5541,58 @@ void ex_vimgrep(exarg_T *eap)
}
}
- FreeWild(fcount, fnames);
+ status = OK;
- qfl = qf_get_curlist(qi);
+theend:
+ xfree(dirname_now);
+ xfree(dirname_start);
+ return status;
+}
+
+/// ":vimgrep {pattern} file(s)"
+/// ":vimgrepadd {pattern} file(s)"
+/// ":lvimgrep {pattern} file(s)"
+/// ":lvimgrepadd {pattern} file(s)"
+void ex_vimgrep(exarg_T *eap)
+{
+ char_u *au_name = vgr_get_auname(eap->cmdidx);
+ if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name,
+ curbuf->b_fname, true, curbuf)) {
+ if (aborting()) {
+ return;
+ }
+ }
+
+ win_T *wp = NULL;
+ qf_info_T *qi = qf_cmd_get_or_alloc_stack(eap, &wp);
+ char_u *target_dir = NULL;
+ vgr_args_T args;
+ if (vgr_process_args(eap, &args) == FAIL) {
+ goto theend;
+ }
+
+ if ((eap->cmdidx != CMD_grepadd && eap->cmdidx != CMD_lgrepadd
+ && eap->cmdidx != CMD_vimgrepadd && eap->cmdidx != CMD_lvimgrepadd)
+ || qf_stack_empty(qi)) {
+ // make place for a new list
+ qf_new_list(qi, args.qf_title);
+ }
+
+ incr_quickfix_busy();
+
+ bool redraw_for_dummy = false;
+ buf_T *first_match_buf = NULL;
+ int status = vgr_process_files(wp, qi, &args, &redraw_for_dummy, &first_match_buf, &target_dir);
+
+ if (status != OK) {
+ FreeWild(args.fcount, args.fnames);
+ decr_quickfix_busy();
+ goto theend;
+ }
+
+ FreeWild(args.fcount, args.fnames);
+
+ qf_list_T *qfl = qf_get_curlist(qi);
qfl->qf_nonevalid = false;
qfl->qf_ptr = qfl->qf_start;
qfl->qf_index = 1;
@@ -5532,26 +5600,28 @@ void ex_vimgrep(exarg_T *eap)
qf_update_buffer(qi, NULL);
+ // Remember the current quickfix list identifier, so that we can check for
+ // autocommands changing the current quickfix list.
+ unsigned save_qfid = qf_get_curlist(qi)->qf_id;
+
if (au_name != NULL) {
apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, curbuf->b_fname, true, curbuf);
}
// The QuickFixCmdPost autocmd may free the quickfix list. Check the list
// is still valid.
- if (!qflist_valid(wp, save_qfid)
- || qf_restore_list(qi, save_qfid) == FAIL) {
+ if (!qflist_valid(wp, save_qfid) || qf_restore_list(qi, save_qfid) == FAIL) {
decr_quickfix_busy();
goto theend;
}
// Jump to first match.
if (!qf_list_empty(qf_get_curlist(qi))) {
- if ((flags & VGR_NOJUMP) == 0) {
- vgr_jump_to_match(qi, eap->forceit, &redraw_for_dummy, first_match_buf,
- target_dir);
+ if ((args.flags & VGR_NOJUMP) == 0) {
+ vgr_jump_to_match(qi, eap->forceit, &redraw_for_dummy, first_match_buf, target_dir);
}
} else {
- semsg(_(e_nomatch2), s);
+ semsg(_(e_nomatch2), args.spat);
}
decr_quickfix_busy();
@@ -5563,11 +5633,9 @@ void ex_vimgrep(exarg_T *eap)
}
theend:
- xfree(title);
- xfree(dirname_now);
- xfree(dirname_start);
+ xfree(args.qf_title);
xfree(target_dir);
- vim_regfree(regmatch.regprog);
+ vim_regfree(args.regmatch.regprog);
}
// Restore current working directory to "dirname_start" if they differ, taking
@@ -5699,7 +5767,7 @@ static void wipe_dummy_buffer(buf_T *buf, char_u *dirname_start)
if (firstwin->w_next != NULL) {
for (win_T *wp = firstwin; wp != NULL; wp = wp->w_next) {
if (wp->w_buffer == buf) {
- if (win_close(wp, false) == OK) {
+ if (win_close(wp, false, false) == OK) {
did_one = true;
}
break;
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index 6b2a2afa41..0644a08210 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -3753,34 +3753,30 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc
}
c = wp->w_p_lcs_chars.tab1;
p = xmalloc(len + 1);
- if (p == NULL) {
- n_extra = 0;
- } else {
- memset(p, ' ', len);
- p[len] = NUL;
- xfree(p_extra_free);
- p_extra_free = p;
- for (i = 0; i < tab_len; i++) {
- if (*p == NUL) {
- tab_len = i;
- break;
- }
- int lcs = wp->w_p_lcs_chars.tab2;
-
- // if tab3 is given, use it for the last char
- if (wp->w_p_lcs_chars.tab3 && i == tab_len - 1) {
- lcs = wp->w_p_lcs_chars.tab3;
- }
- p += utf_char2bytes(lcs, p);
- n_extra += utf_char2len(lcs) - (saved_nextra > 0 ? 1 : 0);
+ memset(p, ' ', len);
+ p[len] = NUL;
+ xfree(p_extra_free);
+ p_extra_free = p;
+ for (i = 0; i < tab_len; i++) {
+ if (*p == NUL) {
+ tab_len = i;
+ break;
}
- p_extra = p_extra_free;
+ int lcs = wp->w_p_lcs_chars.tab2;
- // n_extra will be increased by FIX_FOX_BOGUSCOLS
- // macro below, so need to adjust for that here
- if (vcol_off > 0) {
- n_extra -= vcol_off;
+ // if tab3 is given, use it for the last char
+ if (wp->w_p_lcs_chars.tab3 && i == tab_len - 1) {
+ lcs = wp->w_p_lcs_chars.tab3;
}
+ p += utf_char2bytes(lcs, p);
+ n_extra += utf_char2len(lcs) - (saved_nextra > 0 ? 1 : 0);
+ }
+ p_extra = p_extra_free;
+
+ // n_extra will be increased by FIX_FOX_BOGUSCOLS
+ // macro below, so need to adjust for that here
+ if (vcol_off > 0) {
+ n_extra -= vcol_off;
}
}
diff --git a/src/nvim/search.c b/src/nvim/search.c
index 3a2435e07a..682fa417a9 100644
--- a/src/nvim/search.c
+++ b/src/nvim/search.c
@@ -4848,6 +4848,7 @@ static int fuzzy_match_compute_score(const char_u *const str, const int strSz,
const uint32_t *const matches, const int numMatches)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
{
+ assert(numMatches > 0); // suppress clang "result of operation is garbage"
// Initialize score
int score = 100;
diff --git a/src/nvim/tag.c b/src/nvim/tag.c
index 54d7e54fb4..32d72218c8 100644
--- a/src/nvim/tag.c
+++ b/src/nvim/tag.c
@@ -2960,7 +2960,7 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help)
} else {
RedrawingDisabled--;
if (postponed_split) { // close the window
- win_close(curwin, false);
+ win_close(curwin, false, false);
postponed_split = 0;
}
}
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
index a2d855244c..1c26e46a21 100644
--- a/src/nvim/terminal.c
+++ b/src/nvim/terminal.c
@@ -172,6 +172,11 @@ void terminal_teardown(void)
pmap_init(ptr_t, &invalidated_terminals);
}
+static void term_output_callback(const char *s, size_t len, void *user_data)
+{
+ terminal_send((Terminal *)user_data, (char *)s, len);
+}
+
// public API {{{
Terminal *terminal_open(buf_T *buf, TerminalOptions opts)
@@ -195,6 +200,7 @@ Terminal *terminal_open(buf_T *buf, TerminalOptions opts)
vterm_screen_set_callbacks(rv->vts, &vterm_screen_callbacks, rv);
vterm_screen_set_damage_merge(rv->vts, VTERM_DAMAGE_SCROLL);
vterm_screen_reset(rv->vts, 1);
+ vterm_output_set_callback(rv->vt, term_output_callback, rv);
// force a initial refresh of the screen to ensure the buffer will always
// have as many lines as screen rows when refresh_scrollback is called
rv->invalid_start = 0;
@@ -636,7 +642,6 @@ void terminal_paste(long count, char_u **y_array, size_t y_size)
return;
}
vterm_keyboard_start_paste(curbuf->terminal->vt);
- terminal_flush_output(curbuf->terminal);
size_t buff_len = STRLEN(y_array[0]);
char_u *buff = xmalloc(buff_len);
for (int i = 0; i < count; i++) { // -V756
@@ -667,14 +672,6 @@ void terminal_paste(long count, char_u **y_array, size_t y_size)
}
xfree(buff);
vterm_keyboard_end_paste(curbuf->terminal->vt);
- terminal_flush_output(curbuf->terminal);
-}
-
-void terminal_flush_output(Terminal *term)
-{
- size_t len = vterm_output_read(term->vt, term->textbuf,
- sizeof(term->textbuf));
- terminal_send(term, term->textbuf, len);
}
void terminal_send_key(Terminal *term, int c)
@@ -693,8 +690,6 @@ void terminal_send_key(Terminal *term, int c)
} else {
vterm_keyboard_unichar(term->vt, (uint32_t)c, mod);
}
-
- terminal_flush_output(term);
}
void terminal_receive(Terminal *term, char *data, size_t len)
@@ -1265,9 +1260,6 @@ static bool send_mouse_event(Terminal *term, int c)
}
mouse_action(term, button, row, col - offset, pressed, 0);
- size_t len = vterm_output_read(term->vt, term->textbuf,
- sizeof(term->textbuf));
- terminal_send(term, term->textbuf, len);
return false;
}
diff --git a/src/nvim/testdir/test_cursor_func.vim b/src/nvim/testdir/test_cursor_func.vim
index f2ffd50726..47e74a24d6 100644
--- a/src/nvim/testdir/test_cursor_func.vim
+++ b/src/nvim/testdir/test_cursor_func.vim
@@ -75,7 +75,6 @@ func Test_curswant_with_cursorline()
endfunc
func Test_screenpos()
- throw 'skipped: TODO: '
rightbelow new
rightbelow 20vsplit
call setline(1, ["\tsome text", "long wrapping line here", "next line"])
@@ -103,9 +102,10 @@ func Test_screenpos()
bwipe!
call assert_equal({'col': 1, 'row': 1, 'endcol': 1, 'curscol': 1}, screenpos(win_getid(), 1, 1))
- nmenu WinBar.TEST :
- call assert_equal({'col': 1, 'row': 2, 'endcol': 1, 'curscol': 1}, screenpos(win_getid(), 1, 1))
- nunmenu WinBar.TEST
+ " Needs WinBar
+ " nmenu WinBar.TEST :
+ " call assert_equal({'col': 1, 'row': 2, 'endcol': 1, 'curscol': 1}, screenpos(win_getid(), 1, 1))
+ " nunmenu WinBar.TEST
endfunc
func Test_screenpos_number()
diff --git a/src/nvim/testdir/test_ex_mode.vim b/src/nvim/testdir/test_ex_mode.vim
index 78663f7deb..dcec5f7cc6 100644
--- a/src/nvim/testdir/test_ex_mode.vim
+++ b/src/nvim/testdir/test_ex_mode.vim
@@ -29,12 +29,11 @@ endfunc
" Test editing line in Ex mode (both Q and gQ)
func Test_ex_mode()
- throw 'skipped: TODO: '
+ throw 'Skipped: Nvim only supports Vim Ex mode'
let encoding_save = &encoding
set sw=2
- " for e in ['utf8', 'latin1']
- for e in ['utf8']
+ for e in ['utf8', 'latin1']
exe 'set encoding=' . e
call assert_equal(['bar', 'bar'], Ex("foo bar\<C-u>bar"), e)
diff --git a/src/nvim/testdir/test_filechanged.vim b/src/nvim/testdir/test_filechanged.vim
index b95cd5faf8..b4c32fcc16 100644
--- a/src/nvim/testdir/test_filechanged.vim
+++ b/src/nvim/testdir/test_filechanged.vim
@@ -91,7 +91,7 @@ func Test_FileChangedShell_reload()
endfunc
func Test_file_changed_dialog()
- throw 'skipped: TODO: '
+ throw 'Skipped: requires a UI to a active'
if !has('unix') || has('gui_running')
return
endif
diff --git a/src/nvim/testdir/test_filter_cmd.vim b/src/nvim/testdir/test_filter_cmd.vim
index 0c45db049b..d465e48c7b 100644
--- a/src/nvim/testdir/test_filter_cmd.vim
+++ b/src/nvim/testdir/test_filter_cmd.vim
@@ -145,3 +145,38 @@ func Test_filter_commands()
bwipe! file.h
bwipe! file.hs
endfunc
+
+func Test_filter_display()
+ edit Xdoesnotmatch
+ let @a = '!!willmatch'
+ let @b = '!!doesnotmatch'
+ let @c = "oneline\ntwoline\nwillmatch\n"
+ let @/ = '!!doesnotmatch'
+ call feedkeys(":echo '!!doesnotmatch:'\<CR>", 'ntx')
+ let lines = map(split(execute('filter /willmatch/ display'), "\n"), 'v:val[5:6]')
+
+ call assert_true(index(lines, '"a') >= 0)
+ call assert_false(index(lines, '"b') >= 0)
+ call assert_true(index(lines, '"c') >= 0)
+ call assert_false(index(lines, '"/') >= 0)
+ call assert_false(index(lines, '":') >= 0)
+ call assert_false(index(lines, '"%') >= 0)
+
+ let lines = map(split(execute('filter /doesnotmatch/ display'), "\n"), 'v:val[5:6]')
+ call assert_true(index(lines, '"a') < 0)
+ call assert_false(index(lines, '"b') < 0)
+ call assert_true(index(lines, '"c') < 0)
+ call assert_false(index(lines, '"/') < 0)
+ call assert_false(index(lines, '":') < 0)
+ call assert_false(index(lines, '"%') < 0)
+
+ bwipe!
+endfunc
+
+func Test_filter_scriptnames()
+ let lines = split(execute('filter /test_filter_cmd/ scriptnames'), "\n")
+ call assert_equal(1, len(lines))
+ call assert_match('filter_cmd', lines[0])
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_global.vim b/src/nvim/testdir/test_global.vim
index 8edc9c2608..ad561baf4a 100644
--- a/src/nvim/testdir/test_global.vim
+++ b/src/nvim/testdir/test_global.vim
@@ -36,6 +36,36 @@ func Test_global_error()
call assert_fails('g/\(/y', 'E476:')
endfunc
+" Test for printing lines using :g with different search patterns
+func Test_global_print()
+ new
+ call setline(1, ['foo', 'bar', 'foo', 'foo'])
+ let @/ = 'foo'
+ let t = execute("g/")->trim()->split("\n")
+ call assert_equal(['foo', 'foo', 'foo'], t)
+
+ " Test for Vi compatible patterns
+ let @/ = 'bar'
+ let t = execute('g\/')->trim()->split("\n")
+ call assert_equal(['bar'], t)
+
+ normal gg
+ s/foo/foo/
+ let t = execute('g\&')->trim()->split("\n")
+ call assert_equal(['foo', 'foo', 'foo'], t)
+
+ let @/ = 'bar'
+ let t = execute('g?')->trim()->split("\n")
+ call assert_equal(['bar'], t)
+
+ " Test for the 'Pattern found in every line' message
+ let v:statusmsg = ''
+ v/foo\|bar/p
+ call assert_notequal('', v:statusmsg)
+
+ close!
+endfunc
+
func Test_wrong_delimiter()
call assert_fails('g x^bxd', 'E146:')
endfunc
diff --git a/src/nvim/testdir/test_help.vim b/src/nvim/testdir/test_help.vim
index e91dea1040..b2d943be00 100644
--- a/src/nvim/testdir/test_help.vim
+++ b/src/nvim/testdir/test_help.vim
@@ -12,6 +12,18 @@ endfunc
func Test_help_errors()
call assert_fails('help doesnotexist', 'E149:')
call assert_fails('help!', 'E478:')
+ if has('multi_lang')
+ call assert_fails('help help@xy', 'E661:')
+ endif
+
+ let save_hf = &helpfile
+ set helpfile=help_missing
+ help
+ call assert_equal(1, winnr('$'))
+ call assert_notequal('help', &buftype)
+ let &helpfile = save_hf
+
+ call assert_fails('help ' . repeat('a', 1048), 'E149:')
new
set keywordprg=:help
diff --git a/src/nvim/testdir/test_help_tagjump.vim b/src/nvim/testdir/test_help_tagjump.vim
index a6494c531c..a43889b57e 100644
--- a/src/nvim/testdir/test_help_tagjump.vim
+++ b/src/nvim/testdir/test_help_tagjump.vim
@@ -23,6 +23,11 @@ func Test_help_tagjump()
call assert_true(getline('.') =~ '\*bar\*')
helpclose
+ help "
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*quote\*')
+ helpclose
+
help "*
call assert_equal("help", &filetype)
call assert_true(getline('.') =~ '\*quotestar\*')
@@ -86,11 +91,40 @@ func Test_help_tagjump()
call assert_true(getline('.') =~ '\*i_^_CTRL-D\*')
helpclose
+ help i^x^y
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*i_CTRL-X_CTRL-Y\*')
+ helpclose
+
+ exe "help i\<C-\>\<C-G>"
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*i_CTRL-\\_CTRL-G\*')
+ helpclose
+
exec "help \<C-V>"
call assert_equal("help", &filetype)
call assert_true(getline('.') =~ '\*CTRL-V\*')
helpclose
+ help /\|
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*/\\bar\*')
+ helpclose
+
+ help CTRL-\_CTRL-N
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*CTRL-\\_CTRL-N\*')
+ helpclose
+
+ help `:pwd`,
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*:pwd\*')
+ helpclose
+
+ help `:ls`.
+ call assert_equal("help", &filetype)
+ call assert_true(getline('.') =~ '\*:ls\*')
+ helpclose
exec "help! ('textwidth'"
call assert_equal("help", &filetype)
@@ -122,6 +156,15 @@ func Test_help_tagjump()
call assert_true(getline('.') =~ '\*{address}\*')
helpclose
+ " Use special patterns in the help tag
+ for h in ['/\w', '/\%^', '/\%(', '/\zs', '/\@<=', '/\_$', '[++opt]', '/\{']
+ exec "help! " . h
+ call assert_equal("help", &filetype)
+ let pat = '\*' . escape(h, '\$[') . '\*'
+ call assert_true(getline('.') =~ pat, pat)
+ helpclose
+ endfor
+
exusage
call assert_equal("help", &filetype)
call assert_true(getline('.') =~ '\*:index\*')
diff --git a/src/nvim/testdir/test_listdict.vim b/src/nvim/testdir/test_listdict.vim
index 10c6164c7c..aa66d86af1 100644
--- a/src/nvim/testdir/test_listdict.vim
+++ b/src/nvim/testdir/test_listdict.vim
@@ -620,6 +620,49 @@ func Test_reverse_sort_uniq()
call assert_fails('call reverse("")', 'E899:')
endfunc
+" reduce a list or a blob
+func Test_reduce()
+ call assert_equal(1, reduce([], { acc, val -> acc + val }, 1))
+ call assert_equal(10, reduce([1, 3, 5], { acc, val -> acc + val }, 1))
+ call assert_equal(2 * (2 * ((2 * 1) + 2) + 3) + 4, reduce([2, 3, 4], { acc, val -> 2 * acc + val }, 1))
+ call assert_equal('a x y z', ['x', 'y', 'z']->reduce({ acc, val -> acc .. ' ' .. val}, 'a'))
+ call assert_equal(#{ x: 1, y: 1, z: 1 }, ['x', 'y', 'z']->reduce({ acc, val -> extend(acc, { val: 1 }) }, {}))
+ call assert_equal([0, 1, 2, 3], reduce([1, 2, 3], function('add'), [0]))
+
+ let l = ['x', 'y', 'z']
+ call assert_equal(42, reduce(l, function('get'), #{ x: #{ y: #{ z: 42 } } }))
+ call assert_equal(['x', 'y', 'z'], l)
+
+ call assert_equal(1, reduce([1], { acc, val -> acc + val }))
+ call assert_equal('x y z', reduce(['x', 'y', 'z'], { acc, val -> acc .. ' ' .. val }))
+ call assert_equal(120, range(1, 5)->reduce({ acc, val -> acc * val }))
+ call assert_fails("call reduce([], { acc, val -> acc + val })", 'E998: Reduce of an empty List with no initial value')
+
+ call assert_equal(1, reduce(0z, { acc, val -> acc + val }, 1))
+ call assert_equal(1 + 0xaf + 0xbf + 0xcf, reduce(0zAFBFCF, { acc, val -> acc + val }, 1))
+ call assert_equal(2 * (2 * 1 + 0xaf) + 0xbf, 0zAFBF->reduce({ acc, val -> 2 * acc + val }, 1))
+
+ call assert_equal(0xff, reduce(0zff, { acc, val -> acc + val }))
+ call assert_equal(2 * (2 * 0xaf + 0xbf) + 0xcf, reduce(0zAFBFCF, { acc, val -> 2 * acc + val }))
+ call assert_fails("call reduce(0z, { acc, val -> acc + val })", 'E998: Reduce of an empty Blob with no initial value')
+
+ call assert_fails("call reduce({}, { acc, val -> acc + val }, 1)", 'E897:')
+ call assert_fails("call reduce(0, { acc, val -> acc + val }, 1)", 'E897:')
+ call assert_fails("call reduce('', { acc, val -> acc + val }, 1)", 'E897:')
+
+ let g:lut = [1, 2, 3, 4]
+ func EvilRemove()
+ call remove(g:lut, 1)
+ return 1
+ endfunc
+ call assert_fails("call reduce(g:lut, { acc, val -> EvilRemove() }, 1)", 'E742:')
+ unlet g:lut
+ delfunc EvilRemove
+
+ call assert_equal(42, reduce(v:_null_list, function('add'), 42))
+ call assert_equal(42, reduce(v:_null_blob, function('add'), 42))
+endfunc
+
" splitting a string to a List
func Test_str_split()
call assert_equal(['aa', 'bb'], split(' aa bb '))
diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim
index a5adb5ff16..7d1fed3b94 100644
--- a/src/nvim/testdir/test_options.vim
+++ b/src/nvim/testdir/test_options.vim
@@ -656,6 +656,19 @@ func Test_buftype()
close!
endfunc
+" Test for the 'shellquote' option
+func Test_shellquote()
+ CheckUnix
+ set shellquote=#
+ set verbose=20
+ redir => v
+ silent! !echo Hello
+ redir END
+ set verbose&
+ set shellquote&
+ call assert_match(': "#echo Hello#"', v)
+endfunc
+
" Test for setting option values using v:false and v:true
func Test_opt_boolean()
set number&
diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim
index 00679e1958..c4d70fb1de 100644
--- a/src/nvim/testdir/test_quickfix.vim
+++ b/src/nvim/testdir/test_quickfix.vim
@@ -1384,6 +1384,29 @@ func Test_efm_error_type()
let &efm = save_efm
endfunc
+" Test for end_lnum ('%e') and end_col ('%k') fields in 'efm'
+func Test_efm_end_lnum_col()
+ let save_efm = &efm
+
+ " single line
+ set efm=%f:%l-%e:%c-%k:%t:%m
+ cexpr ["Xfile1:10-20:1-2:E:msg1", "Xfile1:20-30:2-3:W:msg2",]
+ let output = split(execute('clist'), "\n")
+ call assert_equal([
+ \ ' 1 Xfile1:10-20 col 1-2 error: msg1',
+ \ ' 2 Xfile1:20-30 col 2-3 warning: msg2'], output)
+
+ " multiple lines
+ set efm=%A%n)%m,%Z%f:%l-%e:%c-%k
+ cexpr ["1)msg1", "Xfile1:14-24:1-2",
+ \ "2)msg2", "Xfile1:24-34:3-4"]
+ let output = split(execute('clist'), "\n")
+ call assert_equal([
+ \ ' 1 Xfile1:14-24 col 1-2 error 1: msg1',
+ \ ' 2 Xfile1:24-34 col 3-4 error 2: msg2'], output)
+ let &efm = save_efm
+endfunc
+
func XquickfixChangedByAutocmd(cchar)
call s:setup_commands(a:cchar)
if a:cchar == 'c'
diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim
index ecd980472a..dbb792d2b0 100644
--- a/src/nvim/testdir/test_substitute.vim
+++ b/src/nvim/testdir/test_substitute.vim
@@ -51,10 +51,12 @@ func Test_substitute_variants()
\ { 'cmd': ':s/t/r/cg', 'exp': 'Tesring srring', 'prompt': 'a' },
\ { 'cmd': ':s/t/r/ci', 'exp': 'resting string', 'prompt': 'y' },
\ { 'cmd': ':s/t/r/cI', 'exp': 'Tesring string', 'prompt': 'y' },
+ \ { 'cmd': ':s/t/r/c', 'exp': 'Testing string', 'prompt': 'n' },
\ { 'cmd': ':s/t/r/cn', 'exp': ln },
\ { 'cmd': ':s/t/r/cp', 'exp': 'Tesring string', 'prompt': 'y' },
\ { 'cmd': ':s/t/r/cl', 'exp': 'Tesring string', 'prompt': 'y' },
\ { 'cmd': ':s/t/r/gc', 'exp': 'Tesring srring', 'prompt': 'a' },
+ \ { 'cmd': ':s/i/I/gc', 'exp': 'TestIng string', 'prompt': 'l' },
\ { 'cmd': ':s/foo/bar/ge', 'exp': ln },
\ { 'cmd': ':s/t/r/g', 'exp': 'Tesring srring' },
\ { 'cmd': ':s/t/r/gi', 'exp': 'resring srring' },
@@ -86,6 +88,7 @@ func Test_substitute_variants()
\ { 'cmd': ':s//r/rp', 'exp': 'Testr string' },
\ { 'cmd': ':s//r/rl', 'exp': 'Testr string' },
\ { 'cmd': ':s//r/r', 'exp': 'Testr string' },
+ \ { 'cmd': ':s/i/I/gc', 'exp': 'Testing string', 'prompt': 'q' },
\]
for var in variants
@@ -384,6 +387,10 @@ func Test_substitute_join()
call assert_equal(["foo\tbarbar\<C-H>foo"], getline(1, '$'))
call assert_equal('\n', histget("search", -1))
+ call setline(1, ['foo', 'bar', 'baz', 'qux'])
+ call execute('1,2s/\n//')
+ call assert_equal(['foobarbaz', 'qux'], getline(1, '$'))
+
bwipe!
endfunc
@@ -398,6 +405,11 @@ func Test_substitute_count()
call assert_fails('s/foo/bar/0', 'E939:')
+ call setline(1, ['foo foo', 'foo foo', 'foo foo', 'foo foo', 'foo foo'])
+ 2,4s/foo/bar/ 10
+ call assert_equal(['foo foo', 'foo foo', 'foo foo', 'bar foo', 'bar foo'],
+ \ getline(1, '$'))
+
bwipe!
endfunc
@@ -416,6 +428,10 @@ func Test_substitute_flag_n()
" No substitution should have been done.
call assert_equal(lines, getline(1, '$'))
+ %delete _
+ call setline(1, ['A', 'Bar', 'Baz'])
+ call assert_equal("\n1 match on 1 line", execute('s/\nB\@=//gn'))
+
bwipe!
endfunc
@@ -749,6 +765,45 @@ func Test_sub_beyond_end()
bwipe!
endfunc
+" Test for repeating last substitution using :~ and :&r
+func Test_repeat_last_sub()
+ new
+ call setline(1, ['blue green yellow orange white'])
+ s/blue/red/
+ let @/ = 'yellow'
+ ~
+ let @/ = 'white'
+ :&r
+ let @/ = 'green'
+ s//gray
+ call assert_equal('red gray red orange red', getline(1))
+ close!
+endfunc
+
+" Test for Vi compatible substitution:
+" \/{string}/, \?{string}? and \&{string}&
+func Test_sub_vi_compatibility()
+ new
+ call setline(1, ['blue green yellow orange blue'])
+ let @/ = 'orange'
+ s\/white/
+ let @/ = 'blue'
+ s\?amber?
+ let @/ = 'white'
+ s\&green&
+ call assert_equal('amber green yellow white green', getline(1))
+ close!
+endfunc
+
+" Test for substitute with the new text longer than the original text
+func Test_sub_expand_text()
+ new
+ call setline(1, 'abcabcabcabcabcabcabcabc')
+ s/b/\=repeat('B', 10)/g
+ call assert_equal(repeat('aBBBBBBBBBBc', 8), getline(1))
+ close!
+endfunc
+
func Test_submatch_list_concatenate()
let pat = 'A\(.\)'
let Rep = {-> string([submatch(0, 1)] + [[submatch(1)]])}
diff --git a/src/nvim/testdir/test_textformat.vim b/src/nvim/testdir/test_textformat.vim
index 052c32214d..e9f846af7b 100644
--- a/src/nvim/testdir/test_textformat.vim
+++ b/src/nvim/testdir/test_textformat.vim
@@ -238,7 +238,33 @@ func Test_format_c_comment()
END
call assert_equal(expected, getline(1, '$'))
- " Using "o" repeats the line comment, "O" does not.
+ " Using either "o" or "O" repeats a line comment occupying a whole line.
+ %del
+ let text =<< trim END
+ nop;
+ // This is a comment
+ val = val;
+ END
+ call setline(1, text)
+ normal 2Go
+ let expected =<< trim END
+ nop;
+ // This is a comment
+ //
+ val = val;
+ END
+ call assert_equal(expected, getline(1, '$'))
+ normal 2GO
+ let expected =<< trim END
+ nop;
+ //
+ // This is a comment
+ //
+ val = val;
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ " Using "o" repeats a line comment after a statement, "O" does not.
%del
let text =<< trim END
nop;
@@ -531,6 +557,21 @@ func Test_format_align()
call assert_equal("\t\t Vim", getline(1))
q!
+ " align text with 'rightleft'
+ if has('rightleft')
+ new
+ call setline(1, 'Vim')
+ setlocal rightleft
+ left 20
+ setlocal norightleft
+ call assert_equal("\t\t Vim", getline(1))
+ setlocal rightleft
+ right
+ setlocal norightleft
+ call assert_equal("Vim", getline(1))
+ close!
+ endif
+
set tw&
endfunc
diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim
index 76274fb038..099a90643f 100644
--- a/src/nvim/testdir/test_visual.vim
+++ b/src/nvim/testdir/test_visual.vim
@@ -1184,8 +1184,66 @@ func Test_visual_undo_deletes_last_line()
exe "normal ggvjfxO"
undo
normal gNU
+
bwipe!
endfunc
+func Test_visual_paste()
+ new
+
+ " v_p overwrites unnamed register.
+ call setline(1, ['xxxx'])
+ call setreg('"', 'foo')
+ call setreg('-', 'bar')
+ normal gg0vp
+ call assert_equal('x', @")
+ call assert_equal('x', @-)
+ call assert_equal('fooxxx', getline(1))
+ normal $vp
+ call assert_equal('x', @")
+ call assert_equal('x', @-)
+ call assert_equal('fooxxx', getline(1))
+ " Test with a different register as unnamed register.
+ call setline(2, ['baz'])
+ normal 2gg0"rD
+ call assert_equal('baz', @")
+ normal gg0vp
+ call assert_equal('f', @")
+ call assert_equal('f', @-)
+ call assert_equal('bazooxxx', getline(1))
+ normal $vp
+ call assert_equal('x', @")
+ call assert_equal('x', @-)
+ call assert_equal('bazooxxf', getline(1))
+
+ if has('clipboard')
+ " v_P does not overwrite unnamed register.
+ call setline(1, ['xxxx'])
+ call setreg('"', 'foo')
+ call setreg('-', 'bar')
+ normal gg0vP
+ call assert_equal('foo', @")
+ call assert_equal('x', @-)
+ call assert_equal('fooxxx', getline(1))
+ normal $vP
+ call assert_equal('foo', @")
+ call assert_equal('x', @-)
+ call assert_equal('fooxxfoo', getline(1))
+ " Test with a different register as unnamed register.
+ call setline(2, ['baz'])
+ normal 2gg0"rD
+ call assert_equal('baz', @")
+ normal gg0vP
+ call assert_equal('baz', @")
+ call assert_equal('f', @-)
+ call assert_equal('bazooxxfoo', getline(1))
+ normal $vP
+ call assert_equal('baz', @")
+ call assert_equal('o', @-)
+ call assert_equal('bazooxxfobaz', getline(1))
+ endif
+
+ bwipe!
+endfunc
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_writefile.vim b/src/nvim/testdir/test_writefile.vim
index 5ffbe82082..b42665c9b5 100644
--- a/src/nvim/testdir/test_writefile.vim
+++ b/src/nvim/testdir/test_writefile.vim
@@ -169,9 +169,7 @@ endfunc
" Test for ':w !<cmd>' to pipe lines from the current buffer to an external
" command.
func Test_write_pipe_to_cmd()
- if !has('unix')
- return
- endif
+ CheckUnix
new
call setline(1, ['L1', 'L2', 'L3', 'L4'])
2,3w !cat > Xfile
@@ -371,6 +369,154 @@ func Test_write_file_encoding()
%bw!
endfunc
+" Test for writing and reading a file starting with a BOM.
+" Byte Order Mark (BOM) character for various encodings is below:
+" UTF-8 : EF BB BF
+" UTF-16 (BE): FE FF
+" UTF-16 (LE): FF FE
+" UTF-32 (BE): 00 00 FE FF
+" UTF-32 (LE): FF FE 00 00
+func Test_readwrite_file_with_bom()
+ let utf8_bom = "\xEF\xBB\xBF"
+ let utf16be_bom = "\xFE\xFF"
+ let utf16le_bom = "\xFF\xFE"
+ let utf32be_bom = "\n\n\xFE\xFF"
+ let utf32le_bom = "\xFF\xFE\n\n"
+ let save_fileencoding = &fileencoding
+ set cpoptions+=S
+
+ " Check that editing a latin1 file doesn't see a BOM
+ call writefile(["\xFE\xFElatin-1"], 'Xtest1')
+ edit Xtest1
+ call assert_equal('latin1', &fileencoding)
+ call assert_equal(0, &bomb)
+ set fenc=latin1
+ write Xfile2
+ call assert_equal(["\xFE\xFElatin-1", ''], readfile('Xfile2', 'b'))
+ set bomb fenc=latin1
+ write Xtest3
+ call assert_equal(["\xFE\xFElatin-1", ''], readfile('Xtest3', 'b'))
+ set bomb&
+
+ " Check utf-8 BOM
+ %bw!
+ call writefile([utf8_bom .. "utf-8"], 'Xtest1')
+ edit! Xtest1
+ call assert_equal('utf-8', &fileencoding)
+ call assert_equal(1, &bomb)
+ call assert_equal('utf-8', getline(1))
+ set fenc=latin1
+ write! Xfile2
+ call assert_equal(['utf-8', ''], readfile('Xfile2', 'b'))
+ set fenc=utf-8
+ w! Xtest3
+ call assert_equal([utf8_bom .. "utf-8", ''], readfile('Xtest3', 'b'))
+
+ " Check utf-8 with an error (will fall back to latin-1)
+ %bw!
+ call writefile([utf8_bom .. "utf-8\x80err"], 'Xtest1')
+ edit! Xtest1
+ call assert_equal('latin1', &fileencoding)
+ call assert_equal(0, &bomb)
+ call assert_equal("\xC3\xAF\xC2\xBB\xC2\xBFutf-8\xC2\x80err", getline(1))
+ set fenc=latin1
+ write! Xfile2
+ call assert_equal([utf8_bom .. "utf-8\x80err", ''], readfile('Xfile2', 'b'))
+ set fenc=utf-8
+ w! Xtest3
+ call assert_equal(["\xC3\xAF\xC2\xBB\xC2\xBFutf-8\xC2\x80err", ''],
+ \ readfile('Xtest3', 'b'))
+
+ " Check ucs-2 BOM
+ %bw!
+ call writefile([utf16be_bom .. "\nu\nc\ns\n-\n2\n"], 'Xtest1')
+ edit! Xtest1
+ call assert_equal('utf-16', &fileencoding)
+ call assert_equal(1, &bomb)
+ call assert_equal('ucs-2', getline(1))
+ set fenc=latin1
+ write! Xfile2
+ call assert_equal(["ucs-2", ''], readfile('Xfile2', 'b'))
+ set fenc=ucs-2
+ w! Xtest3
+ call assert_equal([utf16be_bom .. "\nu\nc\ns\n-\n2\n", ''],
+ \ readfile('Xtest3', 'b'))
+
+ " Check ucs-2le BOM
+ %bw!
+ call writefile([utf16le_bom .. "u\nc\ns\n-\n2\nl\ne\n"], 'Xtest1')
+ " Need to add a NUL byte after the NL byte
+ call writefile(0z00, 'Xtest1', 'a')
+ edit! Xtest1
+ call assert_equal('utf-16le', &fileencoding)
+ call assert_equal(1, &bomb)
+ call assert_equal('ucs-2le', getline(1))
+ set fenc=latin1
+ write! Xfile2
+ call assert_equal(["ucs-2le", ''], readfile('Xfile2', 'b'))
+ set fenc=ucs-2le
+ w! Xtest3
+ call assert_equal([utf16le_bom .. "u\nc\ns\n-\n2\nl\ne\n", "\n"],
+ \ readfile('Xtest3', 'b'))
+
+ " Check ucs-4 BOM
+ %bw!
+ call writefile([utf32be_bom .. "\n\n\nu\n\n\nc\n\n\ns\n\n\n-\n\n\n4\n\n\n"], 'Xtest1')
+ edit! Xtest1
+ call assert_equal('ucs-4', &fileencoding)
+ call assert_equal(1, &bomb)
+ call assert_equal('ucs-4', getline(1))
+ set fenc=latin1
+ write! Xfile2
+ call assert_equal(["ucs-4", ''], readfile('Xfile2', 'b'))
+ set fenc=ucs-4
+ w! Xtest3
+ call assert_equal([utf32be_bom .. "\n\n\nu\n\n\nc\n\n\ns\n\n\n-\n\n\n4\n\n\n", ''], readfile('Xtest3', 'b'))
+
+ " Check ucs-4le BOM
+ %bw!
+ call writefile([utf32le_bom .. "u\n\n\nc\n\n\ns\n\n\n-\n\n\n4\n\n\nl\n\n\ne\n\n\n"], 'Xtest1')
+ " Need to add three NUL bytes after the NL byte
+ call writefile(0z000000, 'Xtest1', 'a')
+ edit! Xtest1
+ call assert_equal('ucs-4le', &fileencoding)
+ call assert_equal(1, &bomb)
+ call assert_equal('ucs-4le', getline(1))
+ set fenc=latin1
+ write! Xfile2
+ call assert_equal(["ucs-4le", ''], readfile('Xfile2', 'b'))
+ set fenc=ucs-4le
+ w! Xtest3
+ call assert_equal([utf32le_bom .. "u\n\n\nc\n\n\ns\n\n\n-\n\n\n4\n\n\nl\n\n\ne\n\n\n", "\n\n\n"], readfile('Xtest3', 'b'))
+
+ set cpoptions-=S
+ let &fileencoding = save_fileencoding
+ call delete('Xtest1')
+ call delete('Xfile2')
+ call delete('Xtest3')
+ %bw!
+endfunc
+
+func Test_read_write_bin()
+ " write file missing EOL
+ call writefile(['noeol'], "XNoEolSetEol", 'bS')
+ call assert_equal(0z6E6F656F6C, readfile('XNoEolSetEol', 'B'))
+
+ " when file is read 'eol' is off
+ set nofixeol
+ e! ++ff=unix XNoEolSetEol
+ call assert_equal(0, &eol)
+
+ " writing with 'eol' set adds the newline
+ setlocal eol
+ w
+ call assert_equal(0z6E6F656F6C0A, readfile('XNoEolSetEol', 'B'))
+
+ call delete('XNoEolSetEol')
+ set ff&
+ bwipe! XNoEolSetEol
+endfunc
+
" Check that buffer is written before triggering QuitPre
func Test_wq_quitpre_autocommand()
edit Xsomefile
diff --git a/src/nvim/window.c b/src/nvim/window.c
index 6996a93928..83d5910400 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -304,7 +304,7 @@ newwindow:
newtab = curtab;
goto_tabpage_tp(oldtab, true, true);
if (curwin == wp) {
- win_close(curwin, false);
+ win_close(curwin, false, false);
}
if (valid_tabpage(newtab)) {
goto_tabpage_tp(newtab, true, true);
@@ -449,7 +449,7 @@ wingotofile:
RESET_BINDING(curwin);
if (do_ecmd(0, ptr, NULL, NULL, ECMD_LASTL, ECMD_HIDE, NULL) == FAIL) {
// Failed to open the file, close the window opened for it.
- win_close(curwin, false);
+ win_close(curwin, false, false);
goto_tabpage_win(oldtab, oldwin);
} else if (nchar == 'F' && lnum >= 0) {
curwin->w_cursor.lnum = lnum;
@@ -2290,7 +2290,7 @@ void close_windows(buf_T *buf, int keep_curwin)
for (win_T *wp = firstwin; wp != NULL && !ONE_WINDOW;) {
if (wp->w_buffer == buf && (!keep_curwin || wp != curwin)
&& !(wp->w_closing || wp->w_buffer->b_locked > 0)) {
- if (win_close(wp, false) == FAIL) {
+ if (win_close(wp, false, false) == FAIL) {
// If closing the window fails give up, to avoid looping forever.
break;
}
@@ -2368,6 +2368,22 @@ bool last_nonfloat(win_T *wp) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
return wp != NULL && firstwin == wp && !(wp->w_next && !wp->w_floating);
}
+/// Check if floating windows can be closed.
+///
+/// @return true if all floating windows can be closed
+static bool can_close_floating_windows(tabpage_T *tab)
+{
+ FOR_ALL_WINDOWS_IN_TAB(wp, tab) {
+ buf_T *buf = wp->w_buffer;
+ int need_hide = (bufIsChanged(buf) && buf->b_nwindows <= 1);
+
+ if (need_hide && !buf_hide(buf)) {
+ return false;
+ }
+ }
+ return true;
+}
+
/// Close the possibly last window in a tab page.
///
/// @param win window to close
@@ -2432,7 +2448,7 @@ static bool close_last_window_tabpage(win_T *win, bool free_buf, tabpage_T *prev
//
// Called by :quit, :close, :xit, :wq and findtag().
// Returns FAIL when the window was not closed.
-int win_close(win_T *win, bool free_buf)
+int win_close(win_T *win, bool free_buf, bool force)
{
win_T *wp;
bool other_buffer = false;
@@ -2462,9 +2478,18 @@ int win_close(win_T *win, bool free_buf)
}
if ((firstwin == win && lastwin_nofloating() == win)
&& lastwin->w_floating) {
- // TODO(bfredl): we might close the float also instead
- emsg(e_floatonly);
- return FAIL;
+ if (force || can_close_floating_windows(curtab)) {
+ win_T *nextwp;
+ for (win_T *wpp = firstwin; wpp != NULL; wpp = nextwp) {
+ nextwp = wpp->w_next;
+ if (wpp->w_floating) {
+ win_close(wpp, free_buf, force);
+ }
+ }
+ } else {
+ emsg(e_floatonly);
+ return FAIL;
+ }
}
// When closing the last window in a tab page first go to another tab page
@@ -3611,7 +3636,9 @@ void close_others(int message, int forceit)
continue;
}
}
- win_close(wp, !buf_hide(wp->w_buffer) && !bufIsChanged(wp->w_buffer));
+ win_close(wp,
+ !buf_hide(wp->w_buffer) && !bufIsChanged(wp->w_buffer),
+ false);
}
if (message && !ONE_WINDOW) {
diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua
index 937b6559de..ccf3e81b22 100644
--- a/test/functional/api/vim_spec.lua
+++ b/test/functional/api/vim_spec.lua
@@ -536,6 +536,31 @@ describe('API', function()
end)
end)
+ describe('nvim_set_current_dir', function()
+ local start_dir
+
+ before_each(function()
+ clear()
+ funcs.mkdir("Xtestdir")
+ start_dir = funcs.getcwd()
+ end)
+
+ after_each(function()
+ helpers.rmdir("Xtestdir")
+ end)
+
+ it('works', function()
+ meths.set_current_dir("Xtestdir")
+ eq(funcs.getcwd(), start_dir .. helpers.get_pathsep() .. "Xtestdir")
+ end)
+
+ it('sets previous directory', function()
+ meths.set_current_dir("Xtestdir")
+ meths.exec('cd -', false)
+ eq(funcs.getcwd(), start_dir)
+ end)
+ end)
+
describe('nvim_exec_lua', function()
it('works', function()
meths.exec_lua('vim.api.nvim_set_var("test", 3)', {})
diff --git a/test/functional/editor/put_spec.lua b/test/functional/editor/put_spec.lua
index fdda2be131..c367f8fdd0 100644
--- a/test/functional/editor/put_spec.lua
+++ b/test/functional/editor/put_spec.lua
@@ -507,7 +507,9 @@ describe('put command', function()
return function(exception_table, after_redo)
test_expect(exception_table, after_redo)
if selection_string then
- eq(selection_string, getreg('"'))
+ if not conversion_table.put_backwards then
+ eq(selection_string, getreg('"'))
+ end
else
eq('test_string"', getreg('"'))
end
@@ -714,7 +716,9 @@ describe('put command', function()
expect_base, conversion_table)
return function(exception_table, after_redo)
test_expect(exception_table, after_redo)
- eq('Line of words 1\n', getreg('"'))
+ if not conversion_table.put_backwards then
+ eq('Line of words 1\n', getreg('"'))
+ end
end
end
local base_expect_string = [[
@@ -748,7 +752,9 @@ describe('put command', function()
end, expect_base, conversion_table)
return function(e,c)
test_expect(e,c)
- eq('Lin\nLin', getreg('"'))
+ if not conversion_table.put_backwards then
+ eq('Lin\nLin', getreg('"'))
+ end
end
end
diff --git a/test/functional/fixtures/lua/test_plug/submodule_empty/health.lua b/test/functional/fixtures/lua/test_plug/submodule_empty/health.lua
new file mode 100644
index 0000000000..d2cf86e4f0
--- /dev/null
+++ b/test/functional/fixtures/lua/test_plug/submodule_empty/health.lua
@@ -0,0 +1,7 @@
+local M = {}
+
+M.check = function()
+ return {}
+end
+
+return M
diff --git a/test/functional/legacy/ex_mode_spec.lua b/test/functional/legacy/ex_mode_spec.lua
new file mode 100644
index 0000000000..44719027a6
--- /dev/null
+++ b/test/functional/legacy/ex_mode_spec.lua
@@ -0,0 +1,36 @@
+local helpers = require('test.functional.helpers')(after_each)
+local clear = helpers.clear
+local command = helpers.command
+local eq = helpers.eq
+local eval = helpers.eval
+local feed = helpers.feed
+local meths = helpers.meths
+
+before_each(clear)
+
+describe('Ex mode', function()
+ it('supports command line editing', function()
+ local function test_ex_edit(expected, cmd)
+ feed('gQ' .. cmd .. '<C-b>"<CR>')
+ local ret = eval('@:[1:]') -- Remove leading quote.
+ feed('visual<CR>')
+ eq(meths.replace_termcodes(expected, true, true, true), ret)
+ end
+ command('set sw=2')
+ test_ex_edit('bar', 'foo bar<C-u>bar')
+ test_ex_edit('1<C-u>2', '1<C-v><C-u>2')
+ test_ex_edit('213', '1<C-b>2<C-e>3')
+ test_ex_edit('2013', '01<Home>2<End>3')
+ test_ex_edit('0213', '01<Left>2<Right>3')
+ test_ex_edit('0342', '012<Left><Left><Insert>3<Insert>4')
+ test_ex_edit('foo ', 'foo bar<C-w>')
+ test_ex_edit('foo', 'fooba<Del><Del>')
+ test_ex_edit('foobar', 'foo<Tab>bar')
+ test_ex_edit('abbreviate', 'abbrev<Tab>')
+ test_ex_edit('1<C-t><C-t>', '1<C-t><C-t>')
+ test_ex_edit('1<C-t><C-t>', '1<C-t><C-t><C-d>')
+ test_ex_edit(' foo', ' foo<C-d>')
+ test_ex_edit(' foo0', ' foo0<C-d>')
+ test_ex_edit(' foo^', ' foo^<C-d>')
+ end)
+end)
diff --git a/test/functional/legacy/filechanged_spec.lua b/test/functional/legacy/filechanged_spec.lua
new file mode 100644
index 0000000000..fed7b27b0c
--- /dev/null
+++ b/test/functional/legacy/filechanged_spec.lua
@@ -0,0 +1,76 @@
+local helpers = require('test.functional.helpers')(after_each)
+local clear, source = helpers.clear, helpers.source
+local call, eq, meths = helpers.call, helpers.eq, helpers.meths
+
+local function expected_empty()
+ eq({}, meths.get_vvar('errors'))
+end
+
+describe('file changed dialog', function()
+ before_each(function()
+ clear()
+ meths.ui_attach(80, 24, {})
+ meths.set_option('autoread', false)
+ source([[
+ func Test_file_changed_dialog()
+ au! FileChangedShell
+
+ new Xchanged_d
+ call setline(1, 'reload this')
+ write
+ " Need to wait until the timestamp would change by at least a second.
+ sleep 2
+ silent !echo 'extra line' >>Xchanged_d
+ call nvim_input('L')
+ checktime
+ call assert_match('W11:', v:warningmsg)
+ call assert_equal(2, line('$'))
+ call assert_equal('reload this', getline(1))
+ call assert_equal('extra line', getline(2))
+
+ " delete buffer, only shows an error, no prompt
+ silent !rm Xchanged_d
+ checktime
+ call assert_match('E211:', v:warningmsg)
+ call assert_equal(2, line('$'))
+ call assert_equal('extra line', getline(2))
+ let v:warningmsg = 'empty'
+
+ " change buffer, recreate the file and reload
+ call setline(1, 'buffer is changed')
+ silent !echo 'new line' >Xchanged_d
+ call nvim_input('L')
+ checktime
+ call assert_match('W12:', v:warningmsg)
+ call assert_equal(1, line('$'))
+ call assert_equal('new line', getline(1))
+
+ " Only mode changed, reload
+ silent !chmod +x Xchanged_d
+ call nvim_input('L')
+ checktime
+ call assert_match('W16:', v:warningmsg)
+ call assert_equal(1, line('$'))
+ call assert_equal('new line', getline(1))
+
+ " Only time changed, no prompt
+ sleep 2
+ silent !touch Xchanged_d
+ let v:warningmsg = ''
+ checktime
+ call assert_equal('', v:warningmsg)
+ call assert_equal(1, line('$'))
+ call assert_equal('new line', getline(1))
+
+ bwipe!
+ call delete('Xchanged_d')
+ endfunc
+ ]])
+ end)
+
+ it('works', function()
+ if helpers.pending_win32(pending) then return end
+ call('Test_file_changed_dialog')
+ expected_empty()
+ end)
+end)
diff --git a/test/functional/plugin/health_spec.lua b/test/functional/plugin/health_spec.lua
index f7c2dbdb43..a9bd76ce24 100644
--- a/test/functional/plugin/health_spec.lua
+++ b/test/functional/plugin/health_spec.lua
@@ -153,6 +153,10 @@ describe('health.vim', function()
## report 2
- OK: nothing to see here
+ test_plug.submodule_empty: require("test_plug.submodule_empty.health").check()
+ ========================================================================
+ - ERROR: The healthcheck report for "test_plug.submodule_empty" plugin is empty.
+
test_plug.submodule_failed: require("test_plug.submodule_failed.health").check()
========================================================================
- ERROR: Failed to run healthcheck for "test_plug.submodule_failed" plugin. Exception:
@@ -172,6 +176,16 @@ describe('health.vim', function()
]])
end)
+ it("... including empty reports", function()
+ command("checkhealth test_plug.submodule_empty")
+ helpers.expect([[
+
+ test_plug.submodule_empty: require("test_plug.submodule_empty.health").check()
+ ========================================================================
+ - ERROR: The healthcheck report for "test_plug.submodule_empty" plugin is empty.
+ ]])
+ end)
+
it("gracefully handles broken lua healthcheck", function()
command("checkhealth test_plug.submodule_failed")
local buf_lines = helpers.curbuf('get_lines', 0, -1, true)
diff --git a/test/functional/treesitter/parser_spec.lua b/test/functional/treesitter/parser_spec.lua
index 396fe5feab..599c74102c 100644
--- a/test/functional/treesitter/parser_spec.lua
+++ b/test/functional/treesitter/parser_spec.lua
@@ -285,6 +285,25 @@ end]]
eq(true, result)
end)
+ it('support getting empty text if node range is zero width', function()
+ local text = [[
+```lua
+{}
+```]]
+ insert(text)
+ local result = exec_lua([[
+ local fake_node = {}
+ function fake_node:start()
+ return 1, 0, 7
+ end
+ function fake_node:end_()
+ return 1, 0, 7
+ end
+ return vim.treesitter.get_node_text(fake_node, 0) == ''
+ ]])
+ eq(true, result)
+ end)
+
it('can match special regex characters like \\ * + ( with `vim-match?`', function()
insert('char* astring = "\\n"; (1 + 1) * 2 != 2;')
diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua
index 5f29261b17..dc26c52f1a 100644
--- a/test/functional/ui/float_spec.lua
+++ b/test/functional/ui/float_spec.lua
@@ -417,6 +417,28 @@ describe('float window', function()
eq(winids, eval('winids'))
end)
+ it('closed when the last non-float window is closed', function()
+ local tabpage = exec_lua([[
+ vim.cmd('edit ./src/nvim/main.c')
+ vim.cmd('tabedit %')
+
+ local buf = vim.api.nvim_create_buf(false, true)
+ local win = vim.api.nvim_open_win(buf, false, {
+ relative = 'win',
+ row = 1,
+ col = 1,
+ width = 10,
+ height = 2
+ })
+
+ vim.cmd('quit')
+
+ return vim.api.nvim_get_current_tabpage()
+ ]])
+
+ eq(1, tabpage)
+ end)
+
local function with_ext_multigrid(multigrid)
local screen
before_each(function()
diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt
index cdca7c7b9c..174f1cbaba 100644
--- a/third-party/CMakeLists.txt
+++ b/third-party/CMakeLists.txt
@@ -163,10 +163,10 @@ set(LUAROCKS_SHA256 ab6612ca9ab87c6984871d2712d05525775e8b50172701a0a1cabddf76de
set(UNIBILIUM_URL https://github.com/neovim/unibilium/archive/92d929f.tar.gz)
set(UNIBILIUM_SHA256 29815283c654277ef77a3adcc8840db79ddbb20a0f0b0c8f648bd8cd49a02e4b)
-set(LIBTERMKEY_URL http://www.leonerd.org.uk/code/libtermkey/libtermkey-0.22.tar.gz)
+set(LIBTERMKEY_URL https://www.leonerd.org.uk/code/libtermkey/libtermkey-0.22.tar.gz)
set(LIBTERMKEY_SHA256 6945bd3c4aaa83da83d80a045c5563da4edd7d0374c62c0d35aec09eb3014600)
-set(LIBVTERM_URL http://www.leonerd.org.uk/code/libvterm/libvterm-0.1.4.tar.gz)
+set(LIBVTERM_URL https://www.leonerd.org.uk/code/libvterm/libvterm-0.1.4.tar.gz)
set(LIBVTERM_SHA256 bc70349e95559c667672fc8c55b9527d9db9ada0fb80a3beda533418d782d3dd)
set(LUV_VERSION 1.42.0-1)