diff options
169 files changed, 5644 insertions, 6562 deletions
diff --git a/.github/workflows/commitlint.yml b/.github/workflows/commitlint.yml index 5044b7dcab..f190981322 100644 --- a/.github/workflows/commitlint.yml +++ b/.github/workflows/commitlint.yml @@ -13,7 +13,9 @@ jobs: with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} + path: pr_nvim - uses: rhysd/action-setup-vim@v1 with: neovim: true - - run: nvim --clean -es +"lua require('scripts.lintcommit').main({trace=true})" + - run: wget https://raw.githubusercontent.com/neovim/neovim/master/scripts/lintcommit.lua + - run: nvim --clean -es +"cd pr_nvim" +"lua dofile('../lintcommit.lua').main({trace=true})" diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 3de0c453a5..5eb0185071 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -32,3 +32,39 @@ 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/ci/common/build.sh b/ci/common/build.sh index 0ee4b7493f..c44c234274 100644 --- a/ci/common/build.sh +++ b/ci/common/build.sh @@ -8,8 +8,6 @@ _stat() { top_make() { printf '%78s\n' | tr ' ' '=' - # Travis has 1.5 virtual cores according to: - # http://docs.travis-ci.com/user/speeding-up-the-build/#Paralellizing-your-build-on-one-VM ninja "$@" } diff --git a/ci/common/suite.sh b/ci/common/suite.sh index 561849ce2d..f6a8c22d21 100644 --- a/ci/common/suite.sh +++ b/ci/common/suite.sh @@ -73,13 +73,8 @@ fail() { run_test() { local cmd="$1" - test $# -gt 0 && shift - local test_name="$1" - : ${test_name:=$cmd} - test $# -gt 0 && shift - if ! eval "$cmd" ; then - fail "${test_name}" "$@" - fi + local test_name="$2" + eval "$cmd" || fail "$test_name" } ended_successfully() { @@ -99,3 +94,13 @@ end_tests() { touch "${END_MARKER}" ended_successfully } + +run_suite() { + local command="$1" + local suite_name="$2" + + enter_suite "$suite_name" + run_test "$command" "$suite_name" + exit_suite --continue +} + diff --git a/ci/common/test.sh b/ci/common/test.sh index 92c15c8ba1..798f2926c6 100644 --- a/ci/common/test.sh +++ b/ci/common/test.sh @@ -182,8 +182,3 @@ install_nvim() {( exit_suite )} - -csi_clean() { - find "${BUILD_DIR}/bin" -name 'test-includes-*' -delete - find "${BUILD_DIR}" -name '*test-include*.o' -delete -} diff --git a/ci/run_lint.sh b/ci/run_lint.sh index 314976edc2..607ffa233a 100755 --- a/ci/run_lint.sh +++ b/ci/run_lint.sh @@ -8,24 +8,10 @@ CI_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "${CI_DIR}/common/build.sh" source "${CI_DIR}/common/suite.sh" -enter_suite 'clint' -run_test 'make clint-full' clint -exit_suite --continue - -enter_suite 'lualint' -run_test 'make lualint' lualint -exit_suite --continue - -enter_suite 'pylint' -run_test 'make pylint' pylint -exit_suite --continue - -enter_suite 'shlint' -run_test 'make shlint' shlint -exit_suite --continue - -enter_suite single-includes -run_test 'make check-single-includes' single-includes -exit_suite --continue +run_suite 'make clint-full' 'clint' +run_suite 'make lualint' 'lualint' +run_suite 'make pylint' 'pylint' +run_suite 'make shlint' 'shlint' +run_suite 'make check-single-includes' 'single-includes' end_tests diff --git a/ci/run_tests.sh b/ci/run_tests.sh index c175910da5..6c310ccc76 100755 --- a/ci/run_tests.sh +++ b/ci/run_tests.sh @@ -23,13 +23,13 @@ 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 + run_test run_unittests unittests fi - run_test run_functionaltests + run_test run_functionaltests functionaltests fi -run_test run_oldtests +run_test run_oldtests oldtests -run_test install_nvim +run_test install_nvim install_nvim exit_suite --continue diff --git a/runtime/autoload/dist/ft.vim b/runtime/autoload/dist/ft.vim index 86c71fa52d..aacecc521e 100644 --- a/runtime/autoload/dist/ft.vim +++ b/runtime/autoload/dist/ft.vim @@ -1,7 +1,7 @@ " Vim functions for file type detection " " Maintainer: Bram Moolenaar <Bram@vim.org> -" Last Change: 2022 Jan 11 +" Last Change: 2022 Jan 31 " These functions are moved here from runtime/filetype.vim to make startup " faster. @@ -67,6 +67,9 @@ func dist#ft#FTasmsyntax() endif endfunc +let s:ft_visual_basic_content = '\cVB_Name\|Begin VB\.\(Form\|MDIForm\|UserControl\)' + +" See FTfrm() for Visual Basic form file detection func dist#ft#FTbas() if exists("g:filetype_bas") exe "setf " . g:filetype_bas @@ -86,7 +89,7 @@ func dist#ft#FTbas() setf freebasic elseif match(lines, qb64_preproc) > -1 setf qb64 - elseif match(lines, '\cVB_Name\|Begin VB\.\(Form\|MDIForm\|UserControl\)') > -1 + elseif match(lines, s:ft_visual_basic_content) > -1 setf vb else setf basic @@ -235,6 +238,21 @@ func dist#ft#FTe() endif endfunc +func dist#ft#FTfrm() + if exists("g:filetype_frm") + exe "setf " . g:filetype_frm + return + endif + + let lines = getline(1, min([line("$"), 5])) + + if match(lines, s:ft_visual_basic_content) > -1 + setf vb + else + setf form + endif +endfunc + " Distinguish between Forth and F#. " Provided by Doug Kearns. func dist#ft#FTfs() @@ -708,7 +726,7 @@ func dist#ft#FTperl() endif let save_cursor = getpos('.') call cursor(1,1) - let has_use = search('^use\s\s*\k', 'c', 30) + let has_use = search('^use\s\s*\k', 'c', 30) > 0 call setpos('.', save_cursor) if has_use setf perl @@ -740,7 +758,8 @@ func dist#ft#FTtex() let save_cursor = getpos('.') call cursor(1,1) let firstNC = search('^\s*[^[:space:]%]', 'c', 1000) - if firstNC " Check the next thousand lines for a LaTeX or ConTeXt keyword. + if firstNC > 0 + " Check the next thousand lines for a LaTeX or ConTeXt keyword. let lpat = 'documentclass\>\|usepackage\>\|begin{\|newcommand\>\|renewcommand\>' let cpat = 'start\a\+\|setup\a\+\|usemodule\|enablemode\|enableregime\|setvariables\|useencoding\|usesymbols\|stelle\a\+\|verwende\a\+\|stel\a\+\|gebruik\a\+\|usa\a\+\|imposta\a\+\|regle\a\+\|utilisemodule\>' let kwline = search('^\s*\\\%(' . lpat . '\)\|^\s*\\\(' . cpat . '\)', diff --git a/runtime/autoload/freebasic.vim b/runtime/autoload/freebasic.vim new file mode 100644 index 0000000000..fe6d2745be --- /dev/null +++ b/runtime/autoload/freebasic.vim @@ -0,0 +1,42 @@ +" Vim filetype plugin file +" Language: FreeBASIC +" Maintainer: Doug Kearns <dougkearns@gmail.com> +" Last Change: 2021 Mar 16 + +" Dialects can be one of fb, qb, fblite, or deprecated +" Precedence is forcelang > #lang > lang +function! freebasic#GetDialect() abort + if exists("g:freebasic_forcelang") + return g:freebasic_forcelang + endif + + if exists("g:freebasic_lang") + let dialect = g:freebasic_lang + else + let dialect = "fb" + endif + + " override with #lang directive or metacommand + + let skip = "has('syntax_items') && synIDattr(synID(line('.'), col('.'), 1), 'name') =~ 'Comment$'" + let pat = '\c^\s*\%(#\s*lang\s\+\|''\s*$lang\s*:\s*\)"\([^"]*\)"' + + let save_cursor = getcurpos() + call cursor(1, 1) + " let lnum = search(pat, 'n', '', '', skip) " 'skip' needs 8.2.0915 + let lnum = search(pat, 'n', '', '') + call setpos('.', save_cursor) + + if lnum + let word = matchlist(getline(lnum), pat)[1] + if word =~? '\%(fb\|deprecated\|fblite\|qb\)' + let dialect = word + else + echomsg "freebasic#GetDialect: Invalid lang, found '" .. word .. "' at line " .. lnum .. " " .. getline(lnum) + endif + endif + + return dialect +endfunction + +" vim: nowrap sw=2 sts=2 ts=8 noet fdm=marker: diff --git a/runtime/autoload/health/nvim.vim b/runtime/autoload/health/nvim.vim index ef680097d5..961f83d926 100644 --- a/runtime/autoload/health/nvim.vim +++ b/runtime/autoload/health/nvim.vim @@ -148,14 +148,14 @@ endfunction function! s:get_tmux_option(option) abort let cmd = 'tmux show-option -qvg '.a:option " try global scope - let out = system(cmd) + let out = system(split(cmd)) let val = substitute(out, '\v(\s|\r|\n)', '', 'g') if v:shell_error call health#report_error('command failed: '.cmd."\n".out) return 'error' elseif empty(val) let cmd = 'tmux show-option -qvgs '.a:option " try session scope - let out = system(cmd) + let out = system(split(cmd)) let val = substitute(out, '\v(\s|\r|\n)', '', 'g') if v:shell_error call health#report_error('command failed: '.cmd."\n".out) @@ -202,11 +202,11 @@ function! s:check_tmux() abort " check default-terminal and $TERM call health#report_info('$TERM: '.$TERM) let cmd = 'tmux show-option -qvg default-terminal' - let out = system(cmd) + let out = system(split(cmd)) let tmux_default_term = substitute(out, '\v(\s|\r|\n)', '', 'g') if empty(tmux_default_term) let cmd = 'tmux show-option -qvgs default-terminal' - let out = system(cmd) + let out = system(split(cmd)) let tmux_default_term = substitute(out, '\v(\s|\r|\n)', '', 'g') endif @@ -225,7 +225,7 @@ function! s:check_tmux() abort endif " check for RGB capabilities - let info = system('tmux server-info') + let info = system(['tmux', 'server-info']) let has_tc = stridx(info, " Tc: (flag) true") != -1 let has_rgb = stridx(info, " RGB: (flag) true") != -1 if !has_tc && !has_rgb @@ -242,7 +242,7 @@ function! s:check_terminal() abort endif call health#report_start('terminal') let cmd = 'infocmp -L' - let out = system(cmd) + let out = system(split(cmd)) let kbs_entry = matchstr(out, 'key_backspace=[^,[:space:]]*') let kdch1_entry = matchstr(out, 'key_dc=[^,[:space:]]*') diff --git a/runtime/autoload/health/provider.vim b/runtime/autoload/health/provider.vim index 8f0dbbab39..2f35179338 100644 --- a/runtime/autoload/health/provider.vim +++ b/runtime/autoload/health/provider.vim @@ -565,7 +565,7 @@ function! s:check_ruby() abort \ ['Install Ruby and verify that `ruby` and `gem` commands work.']) return endif - call health#report_info('Ruby: '. s:system('ruby -v')) + call health#report_info('Ruby: '. s:system(['ruby', '-v'])) let [host, err] = provider#ruby#Detect() if empty(host) @@ -588,11 +588,11 @@ function! s:check_ruby() abort endif let latest_gem = get(split(latest_gem, 'neovim (\|, \|)$' ), 0, 'not found') - let current_gem_cmd = host .' --version' + let current_gem_cmd = [host, '--version'] let current_gem = s:system(current_gem_cmd) if s:shell_error - call health#report_error('Failed to run: '. current_gem_cmd, - \ ['Report this issue with the output of: ', current_gem_cmd]) + call health#report_error('Failed to run: '. join(current_gem_cmd), + \ ['Report this issue with the output of: ', join(current_gem_cmd)]) return endif @@ -619,7 +619,7 @@ function! s:check_node() abort \ ['Install Node.js and verify that `node` and `npm` (or `yarn`) commands work.']) return endif - let node_v = get(split(s:system('node -v'), "\n"), 0, '') + let node_v = get(split(s:system(['node', '-v']), "\n"), 0, '') call health#report_info('Node.js: '. node_v) if s:shell_error || s:version_cmp(node_v[1:], '6.0.0') < 0 call health#report_warn('Nvim node.js host does not support '.node_v) @@ -660,8 +660,8 @@ function! s:check_node() abort let current_npm_cmd = ['node', host, '--version'] let current_npm = s:system(current_npm_cmd) if s:shell_error - call health#report_error('Failed to run: '. string(current_npm_cmd), - \ ['Report this issue with the output of: ', string(current_npm_cmd)]) + call health#report_error('Failed to run: '. join(current_npm_cmd), + \ ['Report this issue with the output of: ', join(current_npm_cmd)]) return endif @@ -734,8 +734,8 @@ function! s:check_perl() abort let current_cpan_cmd = [perl_exec, '-W', '-MNeovim::Ext', '-e', 'print $Neovim::Ext::VERSION'] let current_cpan = s:system(current_cpan_cmd) if s:shell_error - call health#report_error('Failed to run: '. string(current_cpan_cmd), - \ ['Report this issue with the output of: ', string(current_cpan_cmd)]) + call health#report_error('Failed to run: '. join(current_cpan_cmd), + \ ['Report this issue with the output of: ', join(current_cpan_cmd)]) return endif diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 2da1f5e40d..7bae5bb94b 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -186,7 +186,7 @@ About the `functions` map: a type name, e.g. `nvim_buf_get_lines` is the `get_lines` method of a Buffer instance. |dev-api| - Global functions have the "method=false" flag and are prefixed with just - `nvim_`, e.g. `nvim_get_buffers`. + `nvim_`, e.g. `nvim_list_bufs`. *api-mapping* External programs (clients) can use the metadata to discover the API, using diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index 5e50f9c1f8..ed75acf36e 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -526,7 +526,7 @@ DirChanged After the |current-directory| was changed. "auto" to trigger on 'autochdir'. Sets these |v:event| keys: cwd: current working directory - scope: "global", "tab", "window" + scope: "global", "tabpage", "window" changed_window: v:true if we fired the event switching window (or tab) <afile> is set to the new directory name. diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index 92f33bd933..6195dd4a0b 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -22,8 +22,10 @@ acos({expr}) Float arc cosine of {expr} add({object}, {item}) List/Blob append {item} to {object} and({expr}, {expr}) Number bitwise AND api_info() Dict api metadata -append({lnum}, {string}) Number append {string} below line {lnum} -append({lnum}, {list}) Number append lines {list} below line {lnum} +append({lnum}, {text}) Number append {text} below line {lnum} +appendbufline({expr}, {lnum}, {text}) + Number append {text} below line {lnum} + in buffer {expr} argc([{winid}]) Number number of files in the argument list argidx() Number current index in the argument list arglistid([{winnr} [, {tabnr}]]) Number argument list id @@ -52,7 +54,7 @@ assert_notmatch({pat}, {text} [, {msg}]) assert_report({msg}) Number report a test failure assert_true({actual} [, {msg}]) Number assert {actual} is true atan({expr}) Float arc tangent of {expr} -atan2({expr}, {expr}) Float arc tangent of {expr1} / {expr2} +atan2({expr1}, {expr2}) Float arc tangent of {expr1} / {expr2} browse({save}, {title}, {initdir}, {default}) String put up a file requester browsedir({title}, {initdir}) String put up a directory requester @@ -72,15 +74,16 @@ call({func}, {arglist} [, {dict}]) any call {func} with arguments {arglist} ceil({expr}) Float round {expr} up changenr() Number current change number -chanclose({id}[, {stream}]) Number Closes a channel or one of its streams +chanclose({id} [, {stream}]) Number Closes a channel or one of its streams chansend({id}, {data}) Number Writes {data} to channel -char2nr({expr}[, {utf8}]) Number ASCII/UTF-8 value of first char in {expr} +char2nr({expr} [, {utf8}]) Number ASCII/UTF-8 value of first char in {expr} +charcol({expr}) Number column number of cursor or mark charidx({string}, {idx} [, {countcc}]) Number char index of byte {idx} in {string} chdir({dir}) String change current working directory cindent({lnum}) Number C indent for line {lnum} clearmatches([{win}]) none clear all matches -col({expr}) Number column nr of cursor or mark +col({expr}) Number column byte index of cursor or mark complete({startcol}, {matches}) none set Insert mode completion complete_add({expr}) Number add completion match complete_check() Number check for key typed during completion @@ -90,8 +93,8 @@ confirm({msg} [, {choices} [, {default} [, {type}]]]) copy({expr}) any make a shallow copy of {expr} cos({expr}) Float cosine of {expr} cosh({expr}) Float hyperbolic cosine of {expr} -count({list}, {expr} [, {ic} [, {start}]]) - Number count how many {expr} are in {list} +count({comp}, {expr} [, {ic} [, {start}]]) + Number count how many {expr} are in {comp} cscope_connection([{num}, {dbpath} [, {prepend}]]) Number checks existence of cscope connection ctxget([{index}]) Dict return the |context| dict at {index} @@ -99,7 +102,7 @@ ctxpop() none pop and restore |context| from the |context-stack| ctxpush([{types}]) none push the current |context| to the |context-stack| -ctxset({context}[, {index}]) none set |context| at {index} +ctxset({context} [, {index}]) none set |context| at {index} ctxsize() Number return |context-stack| size cursor({lnum}, {col} [, {off}]) Number move cursor to {lnum}, {col}, {off} @@ -107,7 +110,7 @@ cursor({list}) Number move cursor to position in {list} debugbreak({pid}) Number interrupt process being debugged deepcopy({expr} [, {noref}]) any make a full copy of {expr} delete({fname} [, {flags}]) Number delete the file or directory {fname} -deletebufline({buf}, {first}[, {last}]) +deletebufline({buf}, {first} [, {last}]) Number delete lines from buffer {buf} dictwatcheradd({dict}, {pattern}, {callback}) Start watching a dictionary @@ -170,6 +173,7 @@ getchangelist([{buf}]) List list of change list items getchar([expr]) Number or String get one character from the user getcharmod() Number modifiers for the last typed character +getcharpos({expr}) List position of cursor, mark, etc. getcharsearch() Dict last character search getcharstr([expr]) String get one character from the user getcmdline() String return the current command-line @@ -178,7 +182,8 @@ getcmdtype() String return current command-line type getcmdwintype() String return current command-line window type getcompletion({pat}, {type} [, {filtered}]) List list of cmdline completion matches -getcurpos() List position of the cursor +getcurpos([{winnr}]) List position of the cursor +getcursorcharpos([{winnr}]) List character position of the cursor getcwd([{winnr} [, {tabnr}]]) String get the current working directory getenv({name}) String return environment variable getfontname([{name}]) String name of font being used @@ -209,7 +214,7 @@ gettabvar({nr}, {varname} [, {def}]) gettabwinvar({tabnr}, {winnr}, {name} [, {def}]) any {name} in {winnr} in tab page {tabnr} gettagstack([{nr}]) Dict get the tag stack of window {nr} -getwininfo([{winid}]) List list of windows +getwininfo([{winid}]) List list of info about each window getwinpos([{timeout}]) List X and Y coord in pixels of the Vim window getwinposx() Number X coord in pixels of Vim window getwinposy() Number Y coord in pixels of Vim window @@ -259,9 +264,9 @@ items({dict}) List key-value pairs in {dict} jobpid({id}) Number Returns pid of a job. jobresize({id}, {width}, {height}) Number Resize pseudo terminal window of a job -jobstart({cmd}[, {opts}]) Number Spawns {cmd} as a job +jobstart({cmd} [, {opts}]) Number Spawns {cmd} as a job jobstop({id}) Number Stops a job -jobwait({ids}[, {timeout}]) Number Wait for a set of jobs +jobwait({ids} [, {timeout}]) Number Wait for a set of jobs join({list} [, {sep}]) String join {list} items into one String json_decode({expr}) any Convert {expr} from JSON json_encode({expr}) String Convert {expr} to JSON @@ -276,28 +281,32 @@ list2str({list} [, {utf8}]) String turn numbers in {list} into a String localtime() Number current time log({expr}) Float natural logarithm (base e) of {expr} log10({expr}) Float logarithm of Float {expr} to base 10 -luaeval({expr}[, {expr}]) any evaluate Lua expression +luaeval({expr} [, {expr}]) any evaluate |Lua| expression map({expr1}, {expr2}) List/Dict change each item in {expr1} to {expr} -maparg({name}[, {mode} [, {abbr} [, {dict}]]]) +maparg({name} [, {mode} [, {abbr} [, {dict}]]]) String or Dict rhs of mapping {name} in mode {mode} -mapcheck({name}[, {mode} [, {abbr}]]) +mapcheck({name} [, {mode} [, {abbr}]]) String check for mappings matching {name} -match({expr}, {pat}[, {start}[, {count}]]) +match({expr}, {pat} [, {start} [, {count}]]) Number position where {pat} matches in {expr} -matchadd({group}, {pattern}[, {priority}[, {id}]]) +matchadd({group}, {pattern} [, {priority} [, {id} [, {dict}]]]) Number highlight {pattern} with {group} -matchaddpos({group}, {list}[, {priority}[, {id}]]) +matchaddpos({group}, {pos} [, {priority} [, {id} [, {dict}]]]) Number highlight positions with {group} matcharg({nr}) List arguments of |:match| matchdelete({id} [, {win}]) Number delete match identified by {id} -matchend({expr}, {pat}[, {start}[, {count}]]) +matchend({expr}, {pat} [, {start} [, {count}]]) Number position where {pat} ends in {expr} -matchlist({expr}, {pat}[, {start}[, {count}]]) +matchfuzzy({list}, {str} [, {dict}]) + List fuzzy match {str} in {list} +matchfuzzypos({list}, {str} [, {dict}]) + List fuzzy match {str} in {list} +matchlist({expr}, {pat} [, {start} [, {count}]]) List match and submatches of {pat} in {expr} -matchstr({expr}, {pat}[, {start}[, {count}]]) +matchstr({expr}, {pat} [, {start} [, {count}]]) String {count}'th match of {pat} in {expr} -matchstrpos({expr}, {pat}[, {start}[, {count}]]) +matchstrpos({expr}, {pat} [, {start} [, {count}]]) List {count}'th match of {pat} in {expr} max({expr}) Number maximum value of items in {expr} menu_get({path} [, {modes}]) List description of |menus| matched by {path} @@ -308,10 +317,10 @@ mode([expr]) String current editing mode msgpackdump({list} [, {type}]) List/Blob dump objects to msgpack msgpackparse({data}) List parse msgpack to a list of objects nextnonblank({lnum}) Number line nr of non-blank line >= {lnum} -nr2char({expr}[, {utf8}]) String single char with ASCII/UTF-8 value {expr} +nr2char({expr} [, {utf8}]) String single char with ASCII/UTF-8 value {expr} nvim_...({args}...) any call nvim |api| functions or({expr}, {expr}) Number bitwise OR -pathshorten({expr}) String shorten directory names in a path +pathshorten({expr} [, {len}]) String shorten directory names in a path perleval({expr}) any evaluate |perl| expression pow({x}, {y}) Float {x} to the power of {y} prevnonblank({lnum}) Number line nr of non-blank line <= {lnum} @@ -325,6 +334,7 @@ pumvisible() Number whether popup menu is visible pyeval({expr}) any evaluate |Python| expression py3eval({expr}) any evaluate |python3| expression pyxeval({expr}) any evaluate |python_x| expression +rand([{expr}]) Number get pseudo-random number range({expr} [, {max} [, {stride}]]) List items from {expr} to {max} readdir({dir} [, {expr}]) List file names in {dir} selected by {expr} @@ -359,9 +369,9 @@ resolve({filename}) String get filename a shortcut points to reverse({list}) List reverse {list} in-place round({expr}) Float round off {expr} rubyeval({expr}) any evaluate |Ruby| expression -rpcnotify({channel}, {event}[, {args}...]) +rpcnotify({channel}, {event} [, {args}...]) Sends an |RPC| notification to {channel} -rpcrequest({channel}, {method}[, {args}...]) +rpcrequest({channel}, {method} [, {args}...]) Sends an |RPC| request to {channel} screenattr({row}, {col}) Number attribute at screen position screenchar({row}, {col}) Number character at screen position @@ -384,12 +394,14 @@ searchpos({pattern} [, {flags} [, {stopline} [, {timeout}]]]) server2client({clientid}, {string}) Number send reply string serverlist() String get a list of available servers -setbufline( {expr}, {lnum}, {line}) - Number set line {lnum} to {line} in buffer +setbufline({expr}, {lnum}, {text}) + Number set line {lnum} to {text} in buffer {expr} setbufvar({buf}, {varname}, {val}) set {varname} in buffer {buf} to {val} +setcharpos({expr}, {list}) Number set the {expr} position to {list} setcharsearch({dict}) Dict set character search from {dict} setcmdpos({pos}) Number set cursor position in command-line +setcursorcharpos({list}) Number move cursor to position in {list} setenv({name}, {val}) none set environment variable setfperm({fname}, {mode} Number set {fname} file permissions to {mode} setline({lnum}, {line}) Number set line {lnum} to {line} @@ -402,7 +414,7 @@ setpos({expr}, {list}) Number set the {expr} position to {list} setqflist({list} [, {action}]) Number modify quickfix list using {list} setqflist({list}, {action}, {what}) Number modify specific quickfix list props -setreg({n}, {v}[, {opt}]) Number set register to value and type +setreg({n}, {v} [, {opt}]) Number set register to value and type settabvar({nr}, {varname}, {val}) set {varname} in tab page {nr} to {val} settabwinvar({tabnr}, {winnr}, {varname}, {val}) set {varname} in window {winnr} in tab page {tabnr} to {val} @@ -443,6 +455,7 @@ spellsuggest({word} [, {max} [, {capital}]]) split({expr} [, {pat} [, {keepempty}]]) List make |List| from {pat} separated {expr} sqrt({expr}) Float square root of {expr} +srand([{expr}]) List get seed for |rand()| stdioopen({dict}) Number open stdio in a headless instance. stdpath({what}) String/List returns the standard path(s) for {what} str2float({expr} [, {quoted}]) Float convert String to Float @@ -486,9 +499,9 @@ system({cmd} [, {input}]) String output of shell command/filter {cmd} systemlist({cmd} [, {input}]) List output of shell command/filter {cmd} tabpagebuflist([{arg}]) List list of buffer numbers in tab page tabpagenr([{arg}]) Number number of current or last tab page -tabpagewinnr({tabarg}[, {arg}]) +tabpagewinnr({tabarg} [, {arg}]) Number number of current window in tab page -taglist({expr}[, {filename}]) List list of tags matching {expr} +taglist({expr} [, {filename}]) List list of tags matching {expr} tagfiles() List tags files used tan({expr}) Float tangent of {expr} tanh({expr}) Float hyperbolic tangent of {expr} @@ -515,7 +528,7 @@ uniq({list} [, {func} [, {dict}]]) values({dict}) List values in {dict} virtcol({expr}) Number screen column of cursor or mark visualmode([expr]) String last visual mode used -wait({timeout}, {condition}[, {interval}]) +wait({timeout}, {condition} [, {interval}]) Number Wait until {condition} is satisfied wildmenumode() Number whether 'wildmenu' mode is active win_execute({id}, {command} [, {silent}]) @@ -949,8 +962,8 @@ byteidxcomp({expr}, {nr}) *byteidxcomp()* < The first and third echo result in 3 ('e' plus composing character is 3 bytes), the second echo results in 1 ('e' is one byte). - Only works differently from byteidx() when 'encoding' is set to - a Unicode encoding. + Only works differently from byteidx() when 'encoding' is set + to a Unicode encoding. Can also be used as a |method|: > GetName()->byteidxcomp(idx) @@ -990,7 +1003,7 @@ changenr() *changenr()* redo it is the number of the redone change. After undo it is one less than the number of the undone change. -chanclose({id}[, {stream}]) *chanclose()* +chanclose({id} [, {stream}]) *chanclose()* Close a channel or a specific stream associated with it. For a job, {stream} can be one of "stdin", "stdout", "stderr" or "rpc" (closes stdin/stdout for a job started @@ -1035,6 +1048,18 @@ char2nr({string} [, {utf8}]) *char2nr()* Can also be used as a |method|: > GetChar()->char2nr() < + *charcol()* +charcol({expr}) Same as |col()| but returns the character index of the column + position given with {expr} instead of the byte position. + + Example: + With the cursor on '세' in line 5 with text "여보세요": > + charcol('.') returns 3 + col('.') returns 7 + +< Can also be used as a |method|: > + GetPos()->col() +< *charidx()* charidx({string}, {idx} [, {countcc}]) Return the character index of the byte at {idx} in {string}. @@ -1120,7 +1145,8 @@ col({expr}) The result is a Number, which is the byte index of the column out of range then col() returns zero. To get the line number use |line()|. To get both use |getpos()|. - For the screen column position use |virtcol()|. + For the screen column position use |virtcol()|. For the + character position use |charcol()|. Note that only marks in the current file can be used. Examples: > col(".") column of cursor @@ -1421,7 +1447,7 @@ ctxpush([{types}]) *ctxpush()* which |context-types| to include in the pushed context. Otherwise, all context types are included. -ctxset({context}[, {index}]) *ctxset()* +ctxset({context} [, {index}]) *ctxset()* Sets the |context| at {index} from the top of the |context-stack| to that represented by {context}. {context} is a Dictionary with context data (|context-dict|). @@ -1443,6 +1469,9 @@ cursor({list}) This is like the return value of |getpos()| or |getcurpos()|, but without the first item. + To position the cursor using the character count, use + |setcursorcharpos()|. + Does not change the jumplist. If {lnum} is greater than the number of lines in the buffer, the cursor will be positioned at the last line in the buffer. @@ -1462,7 +1491,7 @@ cursor({list}) Can also be used as a |method|: > GetCursorPos()->cursor() -deepcopy({expr}[, {noref}]) *deepcopy()* *E698* +deepcopy({expr} [, {noref}]) *deepcopy()* *E698* Make a copy of {expr}. For Numbers and Strings this isn't different from using {expr} directly. When {expr} is a |List| a full copy is created. This means @@ -1505,7 +1534,7 @@ delete({fname} [, {flags}]) *delete()* Can also be used as a |method|: > GetName()->delete() -deletebufline({buf}, {first}[, {last}]) *deletebufline()* +deletebufline({buf}, {first} [, {last}]) *deletebufline()* Delete lines {first} to {last} (inclusive) from buffer {buf}. If {last} is omitted then delete line {first} only. On success 0 is returned, on failure 1 is returned. @@ -1515,7 +1544,7 @@ deletebufline({buf}, {first}[, {last}]) *deletebufline()* For the use of {buf}, see |bufname()| above. - {first} and {last} are used like with |setline()|. Note that + {first} and {last} are used like with |getline()|. Note that when using |line()| this refers to the current buffer. Use "$" to refer to the last line in buffer {buf}. @@ -1725,6 +1754,19 @@ exists({expr}) The result is a Number, which is |TRUE| if {expr} is For checking if a file exists use |filereadable()|. The {expr} argument is a string, which contains one of these: + varname internal variable (see + dict.key |internal-variables|). Also works + list[i] for |curly-braces-names|, |Dictionary| + entries, |List| items, etc. + Beware that evaluating an index may + cause an error message for an invalid + expression. E.g.: > + :let l = [1, 2, 3] + :echo exists("l[5]") +< 0 > + :echo exists("l[xx]") +< E121: Undefined variable: xx + 0 &option-name Vim option (only checks if it exists, not if it really works) +option-name Vim option that works. @@ -1735,19 +1777,6 @@ exists({expr}) The result is a Number, which is |TRUE| if {expr} is or user defined function (see |user-function|). Also works for a variable that is a Funcref. - varname internal variable (see - |internal-variables|). Also works - for |curly-braces-names|, |Dictionary| - entries, |List| items, etc. Beware - that evaluating an index may cause an - error message for an invalid - expression. E.g.: > - :let l = [1, 2, 3] - :echo exists("l[5]") -< 0 > - :echo exists("l[xx]") -< E121: Undefined variable: xx - 0 :cmdname Ex command: built-in command, user command or command modifier |:command|. Returns: @@ -2652,6 +2681,20 @@ getcharmod() *getcharmod()* character itself are obtained. Thus Shift-a results in "A" without a modifier. + *getcharpos()* +getcharpos({expr}) + Get the position for String {expr}. Same as |getpos()| but the + column number in the returned List is a character index + instead of a byte index. + + Example: + With the cursor on '세' in line 5 with text "여보세요": > + getcharpos('.') returns [0, 5, 3, 0] + getpos('.') returns [0, 5, 7, 0] +< + Can also be used as a |method|: > + GetMark()->getcharpos() + getcharsearch() *getcharsearch()* Return the current character search information as a {dict} with the following entries: @@ -2786,12 +2829,22 @@ getcompletion({pat}, {type} [, {filtered}]) *getcompletion()* GetPattern()->getcompletion('color') < *getcurpos()* -getcurpos() Get the position of the cursor. This is like getpos('.'), but +getcurpos([{winid}]) + Get the position of the cursor. This is like getpos('.'), but includes an extra "curswant" in the list: [0, lnum, col, off, curswant] ~ The "curswant" number is the preferred column when moving the - cursor vertically. Also see |getpos()|. - The first "bufnum" item is always zero. + cursor vertically. Also see |getcursorcharpos()| and + |getpos()|. + The first "bufnum" item is always zero. The byte position of + the cursor is returned in 'col'. To get the character + position, use |getcursorcharpos()|. + + The optional {winid} argument can specify the window. It can + be the window number or the |window-ID|. The last known + cursor position is returned, this may be invalid for the + current value of the buffer if it is not the current window. + If {winid} is invalid a list with zeroes is returned. This can be used to save and restore the cursor position: > let save_cursor = getcurpos() @@ -2800,7 +2853,23 @@ getcurpos() Get the position of the cursor. This is like getpos('.'), but < Note that this only works within the window. See |winrestview()| for restoring more state. -getcwd([{winnr}[, {tabnr}]]) *getcwd()* + Can also be used as a |method|: > + GetWinid()->getcurpos() +< + *getcursorcharpos()* +getcursorcharpos([{winid}]) + Same as |getcurpos()| but the column number in the returned + List is a character index instead of a byte index. + + Example: + With the cursor on '보' in line 3 with text "여보세요": > + getcursorcharpos() returns [0, 3, 2, 0, 3] + getcurpos() returns [0, 3, 4, 0, 3] +< + Can also be used as a |method|: > + GetWinid()->getcursorcharpos() + +getcwd([{winnr} [, {tabnr}]]) *getcwd()* With no arguments, returns the name of the effective |current-directory|. With {winnr} or {tabnr} the working directory of that scope is returned, and 'autochdir' is @@ -2957,7 +3026,7 @@ getline({lnum} [, {end}]) < To get lines from another buffer see |getbufline()| -getloclist({nr},[, {what}]) *getloclist()* +getloclist({nr} [, {what}]) *getloclist()* Returns a |List| with all the entries in the location list for window {nr}. {nr} can be the window number or the |window-ID|. When {nr} is zero the current window is used. @@ -3079,14 +3148,15 @@ getpos({expr}) Get the position for String {expr}. For possible values of (visual line mode) the column of '< is zero and the column of '> is a large number. The column number in the returned List is the byte position - within the line. + within the line. To get the character position in the line, + use |getcharpos()|. The column number can be very large, e.g. 2147483647, in which case it means "after the end of the line". This can be used to save and restore the position of a mark: > let save_a_mark = getpos("'a") ... call setpos("'a", save_a_mark) -< Also see |getcurpos()| and |setpos()|. +< Also see |getcharpos()|, |getcurpos()| and |setpos()|. Can also be used as a |method|: > GetMark()->getpos() @@ -3545,24 +3615,25 @@ has({feature}) Returns 1 if {feature} is supported, 0 otherwise. The :if has("win32") < *feature-list* List of supported pseudo-feature names: - acl |ACL| support + acl |ACL| support. bsd BSD system (not macOS, use "mac" for that). - iconv Can use |iconv()| for conversion. - +shellslash Can use backslashes in filenames (Windows) clipboard |clipboard| provider is available. fname_case Case in file names matters (for Darwin and MS-Windows this is not present). + iconv Can use |iconv()| for conversion. + linux Linux system. mac MacOS system. nvim This is Nvim. python3 Legacy Vim |python3| interface. |has-python| pythonx Legacy Vim |python_x| interface. |has-pythonx| - ttyin input is a terminal (tty) - ttyout output is a terminal (tty) + sun SunOS system. + ttyin input is a terminal (tty). + ttyout output is a terminal (tty). unix Unix system. *vim_starting* True during |startup|. win32 Windows system (32 or 64 bit). win64 Windows system (64 bit). - wsl WSL (Windows Subsystem for Linux) system + wsl WSL (Windows Subsystem for Linux) system. *has-patch* 3. Vim patch. For example the "patch123" feature means that @@ -3582,7 +3653,7 @@ has_key({dict}, {key}) *has_key()* Can also be used as a |method|: > mydict->has_key(key) -haslocaldir([{winnr}[, {tabnr}]]) *haslocaldir()* +haslocaldir([{winnr} [, {tabnr}]]) *haslocaldir()* The result is a Number, which is 1 when the window has set a local path via |:lcd| or when {winnr} is -1 and the tabpage has set a local path via |:tcd|, otherwise 0. @@ -4084,7 +4155,7 @@ jobresize({job}, {width}, {height}) *jobresize()* columns and {height} rows. Fails if the job was not started with `"pty":v:true`. -jobstart({cmd}[, {opts}]) *jobstart()* +jobstart({cmd} [, {opts}]) *jobstart()* Spawns {cmd} as a job. If {cmd} is a List it runs directly (no 'shell'). If {cmd} is a String it runs in the 'shell', like this: > @@ -4171,7 +4242,7 @@ jobstop({id}) *jobstop()* Returns 1 for valid job id, 0 for invalid id, including jobs have exited or stopped. -jobwait({jobs}[, {timeout}]) *jobwait()* +jobwait({jobs} [, {timeout}]) *jobwait()* Waits for jobs and their |on_exit| handlers to complete. {jobs} is a List of |job-id|s to wait for. @@ -4349,6 +4420,9 @@ line({expr} [, {winid}]) *line()* line("'t") line number of mark t line("'" . marker) line number of mark marker < + To jump to the last known position when opening a file see + |last-position-jump|. + Can also be used as a |method|: > GetValue()->line() @@ -4425,7 +4499,7 @@ log10({expr}) *log10()* Can also be used as a |method|: > Compute()->log10() -luaeval({expr}[, {expr}]) +luaeval({expr} [, {expr}]) Evaluate Lua expression {expr} and return its result converted to Vim data structures. See |lua-eval| for more details. @@ -4645,7 +4719,7 @@ match({expr}, {pat} [, {start} [, {count}]]) *match()* GetList()->match('word') < *matchadd()* *E798* *E799* *E801* *E957* -matchadd({group}, {pattern}[, {priority}[, {id} [, {dict}]]]) +matchadd({group}, {pattern} [, {priority} [, {id} [, {dict}]]]) Defines a pattern to be highlighted in the current window (a "match"). It will be highlighted with {group}. Returns an identification number (ID), which can be used to delete the @@ -4791,6 +4865,87 @@ matchend({expr}, {pat} [, {start} [, {count}]]) *matchend()* Can also be used as a |method|: > GetText()->matchend('word') +matchfuzzy({list}, {str} [, {dict}]) *matchfuzzy()* + If {list} is a list of strings, then returns a |List| with all + the strings in {list} that fuzzy match {str}. The strings in + the returned list are sorted based on the matching score. + + The optional {dict} argument always supports the following + items: + matchseq When this item is present and {str} contains + multiple words separated by white space, then + returns only matches that contain the words in + the given sequence. + + If {list} is a list of dictionaries, then the optional {dict} + argument supports the following additional items: + key key of the item which is fuzzy matched against + {str}. The value of this item should be a + string. + text_cb |Funcref| that will be called for every item + in {list} to get the text for fuzzy matching. + This should accept a dictionary item as the + argument and return the text for that item to + use for fuzzy matching. + + {str} is treated as a literal string and regular expression + matching is NOT supported. The maximum supported {str} length + is 256. + + When {str} has multiple words each separated by white space, + then the list of strings that have all the words is returned. + + If there are no matching strings or there is an error, then an + empty list is returned. If length of {str} is greater than + 256, then returns an empty list. + + Refer to |fuzzy-match| for more information about fuzzy + matching strings. + + Example: > + :echo matchfuzzy(["clay", "crow"], "cay") +< results in ["clay"]. > + :echo getbufinfo()->map({_, v -> v.name})->matchfuzzy("ndl") +< results in a list of buffer names fuzzy matching "ndl". > + :echo getbufinfo()->matchfuzzy("ndl", {'key' : 'name'}) +< results in a list of buffer information dicts with buffer + names fuzzy matching "ndl". > + :echo getbufinfo()->matchfuzzy("spl", + \ {'text_cb' : {v -> v.name}}) +< results in a list of buffer information dicts with buffer + names fuzzy matching "spl". > + :echo v:oldfiles->matchfuzzy("test") +< results in a list of file names fuzzy matching "test". > + :let l = readfile("buffer.c")->matchfuzzy("str") +< results in a list of lines in "buffer.c" fuzzy matching "str". > + :echo ['one two', 'two one']->matchfuzzy('two one') +< results in ['two one', 'one two']. > + :echo ['one two', 'two one']->matchfuzzy('two one', + \ {'matchseq': 1}) +< results in ['two one']. + +matchfuzzypos({list}, {str} [, {dict}]) *matchfuzzypos()* + Same as |matchfuzzy()|, but returns the list of matched + strings, the list of character positions where characters + in {str} matches and a list of matching scores. You can + use |byteidx()| to convert a character position to a byte + position. + + If {str} matches multiple times in a string, then only the + positions for the best match is returned. + + If there are no matching strings or there is an error, then a + list with three empty list items is returned. + + Example: > + :echo matchfuzzypos(['testing'], 'tsg') +< results in [['testing'], [[0, 2, 6]], [99]] > + :echo matchfuzzypos(['clay', 'lacy'], 'la') +< results in [['lacy', 'clay'], [[0, 1], [1, 2]], [153, 133]] > + :echo [{'text': 'hello', 'id' : 10}] + \ ->matchfuzzypos('ll', {'key' : 'text'}) +< results in [[{'id': 10, 'text': 'hello'}], [[2, 3]], [127]] + matchlist({expr}, {pat} [, {start} [, {count}]]) *matchlist()* Same as |match()|, but return a |List|. The first item in the list is the matched string, same as what matchstr() would @@ -4844,8 +4999,10 @@ matchstrpos({expr}, {pat} [, {start} [, {count}]]) *matchstrpos()* GetText()->matchstrpos('word') < *max()* -max({expr}) Return the maximum value of all items in {expr}. - {expr} can be a |List| or a |Dictionary|. For a Dictionary, +max({expr}) Return the maximum value of all items in {expr}. Example: > + echo max([apples, pears, oranges]) + +< {expr} can be a |List| or a |Dictionary|. For a Dictionary, it returns the maximum of all values in the Dictionary. If {expr} is neither a List nor a Dictionary, or one of the items in {expr} cannot be used as a Number this results in @@ -4901,8 +5058,10 @@ menu_get({path} [, {modes}]) *menu_get()* < *min()* -min({expr}) Return the minimum value of all items in {expr}. - {expr} can be a |List| or a |Dictionary|. For a Dictionary, +min({expr}) Return the minimum value of all items in {expr}. Example: > + echo min([apples, pears, oranges]) + +< {expr} can be a |List| or a |Dictionary|. For a Dictionary, it returns the minimum of all values in the Dictionary. If {expr} is neither a List nor a Dictionary, or one of the items in {expr} cannot be used as a Number this results in @@ -4914,8 +5073,10 @@ min({expr}) Return the minimum value of all items in {expr}. < *mkdir()* *E739* mkdir({name} [, {path} [, {prot}]]) Create directory {name}. + If {path} is "p" then intermediate directories are created as necessary. Otherwise it must be "". + If {prot} is given it is used to set the protection bits of the new directory. The default is 0o755 (rwxr-xr-x: r/w for the user, readable for others). Use 0o700 to make it @@ -4924,6 +5085,7 @@ mkdir({name} [, {path} [, {prot}]]) {prot} is applied for all parts of {name}. Thus if you create /tmp/foo/bar then /tmp/foo will be created with 0o700. Example: > :call mkdir($HOME . "/tmp/foo/bar", "p", 0o700) + < This function is not available in the |sandbox|. If you try to create an existing directory with {path} set to @@ -5131,13 +5293,17 @@ or({expr}, {expr}) *or()* < Can also be used as a |method|: > :let bits = bits->or(0x80) -pathshorten({path}) *pathshorten()* +pathshorten({expr} [, {len}]) *pathshorten()* Shorten directory names in the path {path} and return the result. The tail, the file name, is kept as-is. The other - components in the path are reduced to single letters. Leading - '~' and '.' characters are kept. Example: > + components in the path are reduced to {len} letters in length. + If {len} is omitted or smaller than 1 then 1 is used (single + letters). Leading '~' and '.' characters are kept. Examples: > :echo pathshorten('~/.config/nvim/autoload/file1.vim') < ~/.c/n/a/file1.vim ~ +> + :echo pathshorten('~/.config/nvim/autoload/file2.vim', 2) +< ~/.co/nv/au/file2.vim ~ It doesn't matter if the path exists or not. Can also be used as a |method|: > @@ -5525,6 +5691,23 @@ range({expr} [, {max} [, {stride}]]) *range()* Can also be used as a |method|: > GetExpr()->range() < +rand([{expr}]) *rand()* + Return a pseudo-random Number generated with an xoshiro128** + algorithm using seed {expr}. The returned number is 32 bits, + also on 64 bits systems, for consistency. + {expr} can be initialized by |srand()| and will be updated by + rand(). If {expr} is omitted, an internal seed value is used + and updated. + + Examples: > + :echo rand() + :let seed = srand() + :echo rand(seed) + :echo rand(seed) % 16 " random number 0 - 15 +< + Can also be used as a |method|: > + seed->rand() +< *readdir()* readdir({directory} [, {expr}]) Return a list with file and directory names in {directory}. @@ -5779,8 +5962,10 @@ remove({list}, {idx} [, {end}]) *remove()* Example: > :echo "last item: " . remove(mylist, -1) :call remove(mylist, 0, 9) +< + Use |delete()| to remove a file. -< Can also be used as a |method|: > + Can also be used as a |method|: > mylist->remove(idx) remove({blob}, {idx} [, {end}]) @@ -5800,8 +5985,6 @@ remove({dict}, {key}) :echo "removed " . remove(dict, "one") < If there is no {key} in {dict} this is an error. - Use |delete()| to remove a file. - rename({from}, {to}) *rename()* Rename the file by the name {from} to the name {to}. This should also work to move files across file systems. The @@ -5868,19 +6051,19 @@ round({expr}) *round()* Can also be used as a |method|: > Compute()->round() -rpcnotify({channel}, {event}[, {args}...]) *rpcnotify()* +rpcnotify({channel}, {event} [, {args}...]) *rpcnotify()* Sends {event} to {channel} via |RPC| and returns immediately. If {channel} is 0, the event is broadcast to all channels. Example: > :au VimLeave call rpcnotify(0, "leaving") -rpcrequest({channel}, {method}[, {args}...]) *rpcrequest()* +rpcrequest({channel}, {method} [, {args}...]) *rpcrequest()* Sends a request to {channel} to invoke {method} via |RPC| and blocks until a response is received. Example: > :let result = rpcrequest(rpc_chan, "func", 1, 2, 3) -rpcstart({prog}[, {argv}]) *rpcstart()* +rpcstart({prog} [, {argv}]) *rpcstart()* Deprecated. Replace > :let id = rpcstart('prog', ['arg1', 'arg2']) < with > @@ -6442,6 +6625,20 @@ setbufvar({buf}, {varname}, {val}) *setbufvar()* third argument: > GetValue()->setbufvar(buf, varname) +setcharpos({expr}, {list}) *setcharpos()* + Same as |setpos()| but uses the specified column number as the + character index instead of the byte index in the line. + + Example: + With the text "여보세요" in line 8: > + call setcharpos('.', [0, 8, 4, 0]) +< positions the cursor on the fourth character '요'. > + call setpos('.', [0, 8, 4, 0]) +< positions the cursor on the second character '보'. + + Can also be used as a |method|: > + GetPosition()->setcharpos('.') + setcharsearch({dict}) *setcharsearch()* Set the current character search information to {dict}, which contains one or more of the following entries: @@ -6483,6 +6680,21 @@ setcmdpos({pos}) *setcmdpos()* Can also be used as a |method|: > GetPos()->setcmdpos() +setcursorcharpos({lnum}, {col} [, {off}]) *setcursorcharpos()* +setcursorcharpos({list}) + Same as |cursor()| but uses the specified column number as the + character index instead of the byte index in the line. + + Example: + With the text "여보세요" in line 4: > + call setcursorcharpos(4, 3) +< positions the cursor on the third character '세'. > + call cursor(4, 3) +< positions the cursor on the first character '여'. + + Can also be used as a |method|: > + GetCursorPos()->setcursorcharpos() + setenv({name}, {val}) *setenv()* Set environment variable {name} to {val}. Example: > call setenv('HOME', '/home/myhome') @@ -6595,7 +6807,8 @@ setpos({expr}, {list}) "lnum" and "col" are the position in the buffer. The first column is 1. Use a zero "lnum" to delete a mark. If "col" is - smaller than 1 then 1 is used. + smaller than 1 then 1 is used. To use the character count + instead of the byte count, use |setcharpos()|. The "off" number is only used when 'virtualedit' is set. Then it is the offset in screen columns from the start of the @@ -6615,7 +6828,7 @@ setpos({expr}, {list}) Returns 0 when the position could be set, -1 otherwise. An error message is given if {expr} is invalid. - Also see |getpos()| and |getcurpos()|. + Also see |setcharpos()|, |getpos()| and |getcurpos()|. This does not restore the preferred column for moving vertically; if you set the cursor position with this, |j| and @@ -7192,6 +7405,23 @@ sqrt({expr}) *sqrt()* Can also be used as a |method|: > Compute()->sqrt() +srand([{expr}]) *srand()* + Initialize seed used by |rand()|: + - If {expr} is not given, seed values are initialized by + reading from /dev/urandom, if possible, or using time(NULL) + a.k.a. epoch time otherwise; this only has second accuracy. + - If {expr} is given it must be a Number. It is used to + initialize the seed values. This is useful for testing or + when a predictable sequence is intended. + + Examples: > + :let seed = srand() + :let seed = srand(userinput) + :echo rand(seed) +< + Can also be used as a |method|: > + userinput->srand() + stdioopen({opts}) *stdioopen()* With |--headless| this opens stdin and stdout as a |channel|. May be called only once. See |channel-stdio|. stderr is not @@ -7601,11 +7831,11 @@ substitute({string}, {pat}, {sub}, {flags}) *substitute()* swapinfo({fname}) *swapinfo()* The result is a dictionary, which holds information about the swapfile {fname}. The available fields are: - version VIM version + version Vim version user user name host host name fname original file name - pid PID of the VIM process that created the swap + pid PID of the Vim process that created the swap file mtime last modification time in seconds inode Optional: INODE number of the file @@ -7916,7 +8146,7 @@ tempname() *tempname()* *temp-file-name* For MS-Windows forward slashes are used when the 'shellslash' option is set or when 'shellcmdflag' starts with '-'. -termopen({cmd}[, {opts}]) *termopen()* +termopen({cmd} [, {opts}]) *termopen()* Spawns {cmd} in a new pseudo-terminal session connected to the current buffer. {cmd} is the same as the one passed to |jobstart()|. This function fails if the current buffer is @@ -8275,7 +8505,7 @@ visualmode([{expr}]) *visualmode()* a non-empty String, then the Visual mode will be cleared and the old value is returned. See |non-zero-arg|. -wait({timeout}, {condition}[, {interval}]) *wait()* +wait({timeout}, {condition} [, {interval}]) *wait()* Waits until {condition} evaluates to |TRUE|, where {condition} is a |Funcref| or |string| containing an expression. diff --git a/runtime/doc/change.txt b/runtime/doc/change.txt index 953f097a92..e26a84f80f 100644 --- a/runtime/doc/change.txt +++ b/runtime/doc/change.txt @@ -1121,7 +1121,7 @@ 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 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, the +replace and use "0p . You can repeat this as many times as you like, and the unnamed register will be changed each time. When you use a blockwise Visual mode command and yank only a single line into diff --git a/runtime/doc/cmdline.txt b/runtime/doc/cmdline.txt index 641cd93386..f2f6ebb2c9 100644 --- a/runtime/doc/cmdline.txt +++ b/runtime/doc/cmdline.txt @@ -908,8 +908,7 @@ These modifiers can be given, in this order: directory. :. Reduce file name to be relative to current directory, if possible. File name is unmodified if it is not below the - current directory, but on MS-Windows the drive is removed if - it is the current drive. + current directory. For maximum shortness, use ":~:.". :h Head of the file name (the last component and any separators removed). Cannot be used with :e, :r or :t. diff --git a/runtime/doc/editing.txt b/runtime/doc/editing.txt index 44987f3b7b..3d0287b0cd 100644 --- a/runtime/doc/editing.txt +++ b/runtime/doc/editing.txt @@ -1569,6 +1569,12 @@ There are three different types of searching: /u/user_x/work/include /u/user_x/include +< Note: If your 'path' setting includes a non-existing directory, Vim will + skip the non-existing directory, but continues searching in the parent of + the non-existing directory if upwards searching is used. E.g. when + searching "../include" and that doesn't exist, and upward searching is + used, also searches in "..". + 3) Combined up/downward search: If Vim's current path is /u/user_x/work/release and you do > set path=**;/u/user_x diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index f2b014dde6..fc788fba59 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -372,8 +372,8 @@ Changing the order of items in a list: > For loop ~ -The |:for| loop executes commands for each item in a |List| or |Blob|. -A variable is set to each item in the sequence. Example with a List: > +The |:for| loop executes commands for each item in a |List|, |String| or |Blob|. +A variable is set to each item in sequence. Example with a List: > :for item in mylist : call Doit(item) :endfor @@ -390,7 +390,7 @@ If all you want to do is modify each item in the list then the |map()| function will be a simpler method than a for loop. Just like the |:let| command, |:for| also accepts a list of variables. This -requires the argument to be a list of lists. > +requires the argument to be a List of Lists. > :for [lnum, col] in [[1, 3], [2, 8], [3, 0]] : call Doit(lnum, col) :endfor @@ -408,6 +408,12 @@ It is also possible to put remaining items in a List variable: > For a Blob one byte at a time is used. +For a String one character, including any composing characters, is used as a +String. Example: > + for c in text + echo 'This character is ' .. c + endfor + List functions ~ *E714* @@ -676,7 +682,7 @@ similar to -1. > :let otherblob = myblob[:] " make a copy of the Blob If the first index is beyond the last byte of the Blob or the second byte is -before the first byte, the result is an empty Blob. There is no error +before the first index, the result is an empty Blob. There is no error message. If the second index is equal to or greater than the length of the Blob the @@ -1066,7 +1072,7 @@ expr7 *expr7* For '!' |TRUE| becomes |FALSE|, |FALSE| becomes |TRUE| (one). For '-' the sign of the number is changed. -For '+' the number is unchanged. +For '+' the number is unchanged. Note: "++" has no effect. A String will be converted to a Number first. @@ -1228,8 +1234,8 @@ And NOT: > number ------ number number constant *expr-number* - *hex-number* *octal-number* *binary-number* + *0x* *hex-number* *0o* *octal-number* *binary-number* Decimal, Hexadecimal (starting with 0x or 0X), Binary (starting with 0b or 0B) and Octal (starting with 0, 0o or 0O). @@ -1486,7 +1492,7 @@ Notice how execute() is used to execute an Ex command. That's ugly though. Lambda expressions have internal names like '<lambda>42'. If you get an error for a lambda expression, you can find what it is with the following command: > - :function {'<lambda>42'} + :function <lambda>42 See also: |numbered-function| ============================================================================== diff --git a/runtime/doc/indent.txt b/runtime/doc/indent.txt index a76f8636f8..d0d4ddad32 100644 --- a/runtime/doc/indent.txt +++ b/runtime/doc/indent.txt @@ -771,6 +771,15 @@ You can set the indent for the first line after <script> and <style> "auto" auto indent (same indent as the blocktag) "inc" auto indent + one indent step +You can set the indent for attributes after an open <tag line: > + + :let g:html_indent_attribute = 1 +< + VALUE MEANING ~ + 1 auto indent, one indent step more than <tag + 2 auto indent, two indent steps (default) + > 2 auto indent, more indent steps + Many tags increase the indent for what follows per default (see "Add Indent Tags" in the script). You can add further tags with: > diff --git a/runtime/doc/index.txt b/runtime/doc/index.txt index d8689e2c65..f02f9f8032 100644 --- a/runtime/doc/index.txt +++ b/runtime/doc/index.txt @@ -923,7 +923,7 @@ 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. Q does not start Ex mode |v_R| R 2 delete the highlighted lines and start insert @@ -986,6 +986,8 @@ tag command note action in Visual mode ~ |v_i{| i{ same as iB |v_i}| i} same as iB |v_o| o move cursor to other corner of area +|v_p| p replace highlighted area with register + contents; deleted text in unnamed register |v_r| r 2 replace highlighted area with a character |v_s| s 2 delete highlighted area and start insert |v_u| u 2 make highlighted area lowercase diff --git a/runtime/doc/message.txt b/runtime/doc/message.txt index 950028d9cc..fa1bc6f7da 100644 --- a/runtime/doc/message.txt +++ b/runtime/doc/message.txt @@ -27,8 +27,7 @@ depends on the 'shortmess' option. Clear messages, keeping only the {count} most recent ones. -The number of remembered messages is fixed at 20 for the tiny version and 200 -for other versions. +The number of remembered messages is fixed at 200. *g<* The "g<" command can be used to see the last page of previous command output. diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index df5fad06a0..6e2bc228d0 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -1220,8 +1220,8 @@ A jump table for the options with a short description can be found at |Q_op|. preferred, because it is much faster. 'charconvert' is not used when reading stdin |--|, because there is no file to convert from. You will have to save the text in a file first. - The expression must return zero or an empty string for success, - non-zero for failure. + The expression must return zero, false or an empty string for success, + non-zero or true for failure. See |encoding-names| for possible encoding names. Additionally, names given in 'fileencodings' and 'fileencoding' are used. @@ -2462,7 +2462,8 @@ A jump table for the options with a short description can be found at |Q_op|. < This is similar to the default, except that these characters will also be used when there is highlighting. - for "stl" and "stlnc" only single-byte values are supported. + For "stl" and "stlnc" single-byte and multibyte characters are + supported. But double-width characters are not supported. The highlighting used for these items: item highlight group ~ @@ -5461,7 +5462,7 @@ A jump table for the options with a short description can be found at |Q_op|. flag meaning when present ~ f use "(3 of 5)" instead of "(file 3 of 5)" i use "[noeol]" instead of "[Incomplete last line]" - l use "999L, 888C" instead of "999 lines, 888 characters" + l use "999L, 888B" instead of "999 lines, 888 bytes" m use "[+]" instead of "[Modified]" n use "[New]" instead of "[New File]" r use "[RO]" instead of "[readonly]" @@ -6254,10 +6255,11 @@ A jump table for the options with a short description can be found at |Q_op|. 'tabstop' 'ts' number (default 8) local to buffer Number of spaces that a <Tab> in the file counts for. Also see - |:retab| command, and 'softtabstop' option. + the |:retab| command, and the 'softtabstop' option. Note: Setting 'tabstop' to any other value than 8 can make your file - appear wrong in many places (e.g., when printing it). + appear wrong in many places, e.g., when printing it. + The value must be more than 0 and less than 10000. There are four main ways to use tabs in Vim: 1. Always keep 'tabstop' at 8, set 'softtabstop' and 'shiftwidth' to 4 diff --git a/runtime/doc/pattern.txt b/runtime/doc/pattern.txt index 634145da3e..42005b0d78 100644 --- a/runtime/doc/pattern.txt +++ b/runtime/doc/pattern.txt @@ -1421,5 +1421,38 @@ Finally, these constructs are unique to Perl: are suggested to use ":match" for manual matching and ":2match" for another plugin. +============================================================================== +11. Fuzzy matching *fuzzy-match* + +Fuzzy matching refers to matching strings using a non-exact search string. +Fuzzy matching will match a string, if all the characters in the search string +are present anywhere in the string in the same order. Case is ignored. In a +matched string, other characters can be present between two consecutive +characters in the search string. If the search string has multiple words, then +each word is matched separately. So the words in the search string can be +present in any order in a string. + +Fuzzy matching assigns a score for each matched string based on the following +criteria: + - The number of sequentially matching characters. + - The number of characters (distance) between two consecutive matching + characters. + - Matches at the beginning of a word + - Matches at a camel case character (e.g. Case in CamelCase) + - Matches after a path separator or a hyphen. + - The number of unmatched characters in a string. +The matching string with the highest score is returned first. + +For example, when you search for the "get pat" string using fuzzy matching, it +will match the strings "GetPattern", "PatternGet", "getPattern", "patGetter", +"getSomePattern", "MatchpatternGet" etc. + +The functions |matchfuzzy()| and |matchfuzzypos()| can be used to fuzzy search +a string in a List of strings. The matchfuzzy() function returns a List of +matching strings. The matchfuzzypos() functions returns the List of matches, +the matching positions and the fuzzy match scores. + +The "f" flag of `:vimgrep` enables fuzzy matching. + vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/runtime/doc/quickfix.txt b/runtime/doc/quickfix.txt index bb4d807413..ed736ad4eb 100644 --- a/runtime/doc/quickfix.txt +++ b/runtime/doc/quickfix.txt @@ -989,7 +989,7 @@ commands can be combined to create a NewGrep command: > 5.1 using Vim's internal grep *:vim* *:vimgrep* *E682* *E683* -:vim[grep][!] /{pattern}/[g][j] {file} ... +:vim[grep][!] /{pattern}/[g][j][f] {file} ... Search for {pattern} in the files {file} ... and set the error list to the matches. Files matching 'wildignore' are ignored; files in 'suffixes' are @@ -1014,6 +1014,13 @@ commands can be combined to create a NewGrep command: > updated. With the [!] any changes in the current buffer are abandoned. + 'f' When the 'f' flag is specified, fuzzy string + matching is used to find matching lines. In this + case, {pattern} is treated as a literal string + instead of a regular expression. See + |fuzzy-match| for more information about fuzzy + matching strings. + |QuickFixCmdPre| and |QuickFixCmdPost| are triggered. A file that is opened for matching may use a buffer number, but it is reused if possible to avoid @@ -1042,20 +1049,20 @@ commands can be combined to create a NewGrep command: > :vimgrep Error *.c < *:lv* *:lvimgrep* -:lv[imgrep][!] /{pattern}/[g][j] {file} ... +:lv[imgrep][!] /{pattern}/[g][j][f] {file} ... :lv[imgrep][!] {pattern} {file} ... Same as ":vimgrep", except the location list for the current window is used instead of the quickfix list. *:vimgrepa* *:vimgrepadd* -:vimgrepa[dd][!] /{pattern}/[g][j] {file} ... +:vimgrepa[dd][!] /{pattern}/[g][j][f] {file} ... :vimgrepa[dd][!] {pattern} {file} ... Just like ":vimgrep", but instead of making a new list of errors the matches are appended to the current list. *:lvimgrepa* *:lvimgrepadd* -:lvimgrepa[dd][!] /{pattern}/[g][j] {file} ... +:lvimgrepa[dd][!] /{pattern}/[g][j][f] {file} ... :lvimgrepa[dd][!] {pattern} {file} ... Same as ":vimgrepadd", except the location list for the current window is used instead of the quickfix diff --git a/runtime/doc/repeat.txt b/runtime/doc/repeat.txt index c7481ad290..a022049766 100644 --- a/runtime/doc/repeat.txt +++ b/runtime/doc/repeat.txt @@ -465,8 +465,8 @@ flag when defining the function, it is not relevant when executing it. > :set cpo-=C < *line-continuation-comment* -To add a comment in between the lines start with '\" '. Notice the space -after the double quote. Example: > +To add a comment in between the lines start with '"\ '. Notice the space +after the backslash. Example: > let array = [ "\ first entry comment \ 'first', diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt index 5dde170a12..bf024315f6 100644 --- a/runtime/doc/usr_41.txt +++ b/runtime/doc/usr_41.txt @@ -608,6 +608,8 @@ String manipulation: *string-functions* toupper() turn a string to uppercase match() position where a pattern matches in a string matchend() position where a pattern match ends in a string + matchfuzzy() fuzzy matches a string in a list of strings + matchfuzzypos() fuzzy matches a string in a list of strings matchstr() match of a pattern in a string matchstrpos() match and positions of a pattern in a string matchlist() like matchstr() and also return submatches @@ -746,6 +748,11 @@ Cursor and mark position: *cursor-functions* *mark-functions* screenchar() get character code at a screen line/row screenchars() get character codes at a screen line/row screenstring() get string of characters at a screen line/row + charcol() character number of the cursor or a mark + getcharpos() get character position of cursor, mark, etc. + setcharpos() set character position of cursor, mark, etc. + getcursorcharpos() get character position of the cursor + setcursorcharpos() set character position of the cursor Working with text in the current buffer: *text-functions* getline() get a line or list of lines from the buffer diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 7e61eac404..7892b82137 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -322,6 +322,8 @@ coerced to strings. See |id()| for more details, currently it uses |c_CTRL-R| pasting a non-special register into |cmdline| omits the last <CR>. +|CursorMoved| always triggers when moving between windows. + Lua interface (|lua.txt|): - `:lua print("a\0b")` will print `a^@b`, like with `:echomsg "a\nb"` . In Vim @@ -483,7 +485,6 @@ Commands: :tearoff Compile-time features: - EBCDIC Emacs tags support X11 integration (see |x11-selection|) @@ -574,6 +575,7 @@ Test functions: test_scrollbar() test_setmouse() test_settime() + test_srand_seed() TUI: *t_xx* *termcap-options* *t_AB* *t_Sb* *t_vb* *t_SI* diff --git a/runtime/filetype.vim b/runtime/filetype.vim index 28ecf8844e..8b40b43a04 100644 --- a/runtime/filetype.vim +++ b/runtime/filetype.vim @@ -1,7 +1,7 @@ " Vim support file to detect file types " " Maintainer: Bram Moolenaar <Bram@vim.org> -" Last Change: 2022 Jan 23 +" Last Change: 2022 Jan 31 " Listen very carefully, I will say this only once if exists("did_load_filetypes") @@ -2050,7 +2050,7 @@ au BufRead,BufNewFile *.hw,*.module,*.pkg \ endif " Visual Basic (also uses *.bas) or FORM -au BufNewFile,BufRead *.frm call dist#ft#FTVB("form") +au BufNewFile,BufRead *.frm call dist#ft#FTfrm() " SaxBasic is close to Visual Basic au BufNewFile,BufRead *.sba setf vb diff --git a/runtime/ftplugin/basic.vim b/runtime/ftplugin/basic.vim index c6ec254dfc..a8f6b088d1 100644 --- a/runtime/ftplugin/basic.vim +++ b/runtime/ftplugin/basic.vim @@ -1,7 +1,7 @@ " Vim filetype plugin file -" Language: BASIC +" Language: BASIC (QuickBASIC 4.5) " Maintainer: Doug Kearns <dougkearns@gmail.com> -" Last Change: 2015 Jan 10 +" Last Change: 2021 Mar 16 if exists("b:did_ftplugin") finish @@ -11,17 +11,46 @@ let b:did_ftplugin = 1 let s:cpo_save = &cpo set cpo&vim -setlocal comments=:REM,:' +setlocal comments=:REM\ ,:Rem\ ,:rem\ ,:' setlocal commentstring='\ %s setlocal formatoptions-=t formatoptions+=croql +" TODO: support exit ... as middle matches? +if exists("loaded_matchit") && !exists("b:match_words") + let s:line_start = '\%(^\s*\)\@<=' + let s:not_end = '\%(end\s\+\)\@<!' + let s:not_end_or_exit = '\%(\%(end\|exit\)\s\+\)\@<!' + + let b:match_ignorecase = 1 + let b:match_words = + \ s:not_end_or_exit .. '\<def\s\+fn:\<end\s\+def\>,' .. + \ s:not_end_or_exit .. '\<function\>:\<end\s\+function\>,' .. + \ s:not_end_or_exit .. '\<sub\>:\<end\s\+sub\>,' .. + \ s:not_end .. '\<type\>:\<end\s\+type\>,' .. + \ s:not_end .. '\<select\>:\%(select\s\+\)\@<!\<case\%(\s\+\%(else\|is\)\)\=\>:\<end\s\+select\>,' .. + \ '\<do\>:\<loop\>,' .. + \ '\<for\>\%(\s\+\%(input\|output\|random\|append\|binary\)\)\@!:\<next\>,' .. + \ '\<while\>:\<wend\>,' .. + \ s:line_start .. 'if\%(.*\<then\s*\%($\|''\)\)\@=:\<\%(' .. s:line_start .. 'else\|elseif\)\>:\<end\s\+if\>,' .. + \ '\<lock\>:\<unlock\>' + + let b:match_skip = 'synIDattr(synID(line("."),col("."),1),"name") =~? "comment\\|string" || ' .. + \ 'strpart(getline("."), 0, col(".") ) =~? "\\<exit\\s\\+"' + + unlet s:line_start s:not_end s:not_end_or_exit +endif + if (has("gui_win32") || has("gui_gtk")) && !exists("b:browsefilter") - let b:browsefilter = "BASIC Source Files (*.bas)\t*.bas\n" . - \ "All Files (*.*)\t*.*\n" + let b:browsefilter = "BASIC Source Files (*.bas)\t*.bas\n" .. + \ "BASIC Include Files (*.bi, *.bm)\t*.bi;*.bm\n" .. + \ "All Files (*.*)\t*.*\n" endif -let b:undo_ftplugin = "setl fo< com< cms< sua<" . - \ " | unlet! b:browsefilter" +let b:undo_ftplugin = "setl fo< com< cms<" .. + \ " | unlet! b:match_ignorecase b:match_skip b:match_words" .. + \ " | unlet! b:browsefilter" let &cpo = s:cpo_save unlet s:cpo_save + +" vim: nowrap sw=2 sts=2 ts=8 noet fdm=marker: diff --git a/runtime/ftplugin/freebasic.vim b/runtime/ftplugin/freebasic.vim index a2bb459f20..58c2b4c9e2 100644 --- a/runtime/ftplugin/freebasic.vim +++ b/runtime/ftplugin/freebasic.vim @@ -1,13 +1,65 @@ " Vim filetype plugin file -" Language: FreeBasic +" Language: FreeBASIC " Maintainer: Doug Kearns <dougkearns@gmail.com> -" Last Change: 2015 Jan 10 +" Last Change: 2021 Mar 16 +" Setup {{{1 if exists("b:did_ftplugin") finish endif -let b:did_ftplugin = 1 + +let s:cpo_save = &cpo +set cpo&vim runtime! ftplugin/basic.vim -" vim: ts=8 +let s:dialect = freebasic#GetDialect() + +" Comments {{{1 +" add ''comments before 'comments +let &l:comments = "sO:*\ -,mO:*\ \ ,exO:*/,s1:/',mb:',ex:'/,:''," .. &l:comments + +" Match words {{{1 +if exists("loaded_matchit") + let s:not_end = '\%(end\s\+\)\@<!' + + let b:match_words ..= ',' + + if s:dialect == 'fb' + let b:match_words ..= s:not_end .. '\<constructor\>:\<end\s\+constructor\>,' .. + \ s:not_end .. '\<destructor\>:\<end\s\+destructor\>,' .. + \ s:not_end .. '\<property\>:\<end\s\+property\>,' .. + \ s:not_end .. '\<operator\>:\<end\s\+operator\>,' .. + \ s:not_end .. '\<extern\%(\s\+"\)\@=:\<end\s\+extern\>,' + endif + + if s:dialect == 'fb' || s:dialect == 'deprecated' + let b:match_words ..= s:not_end .. '\<scope\>:\<end\s\+scope\>,' + endif + + if s:dialect == 'qb' + let b:match_words ..= s:not_end .. '\<__asm\>:\<end\s\+__asm\>,' .. + \ s:not_end .. '\<__union\>:\<end\s\+__union\>,' .. + \ s:not_end .. '\<__with\>:\<end\s\+__with\>,' + else + let b:match_words ..= s:not_end .. '\<asm\>:\<end\s\+asm\>,' .. + \ s:not_end .. '\<namespace\>:\<end\s\+namespace\>,' .. + \ s:not_end .. '\<union\>:\<end\s\+union\>,' .. + \ s:not_end .. '\<with\>:\<end\s\+with\>,' + endif + + let b:match_words ..= s:not_end .. '\<enum\>:\<end\s\+enum\>,' .. + \ '^#\s*\%(if\|ifdef\|ifndef\)\>:^#\s*\%(else\|elseif\)\>:^#\s*endif\>,' .. + \ '^#\s*macro\>:^#\s*endmacro\>' + + " skip "function = <retval>" + let b:match_skip ..= '|| strpart(getline("."), col(".") - 1) =~? "^\\<function\\s\\+="' + + unlet s:not_end +endif + +" Cleanup {{{1 +let &cpo = s:cpo_save +unlet s:cpo_save + +" vim: nowrap sw=2 sts=2 ts=8 noet fdm=marker: diff --git a/runtime/ftplugin/qb64.vim b/runtime/ftplugin/qb64.vim new file mode 100644 index 0000000000..0fa36fc3d2 --- /dev/null +++ b/runtime/ftplugin/qb64.vim @@ -0,0 +1,26 @@ +" Vim filetype plugin file +" Language: QB64 +" Maintainer: Doug Kearns <dougkearns@gmail.com> + +if exists("b:did_ftplugin") + finish +endif + +let s:cpo_save = &cpo +set cpo&vim + +runtime! ftplugin/basic.vim + +let s:not_end = '\%(end\s\+\)\@<!' + +let b:match_words ..= ',' .. + \ s:not_end .. '\<declare\>:\<end\s\+declare\>,' .. + \ '\<select\s\+everycase\>:\%(select\s\+\)\@<!\<case\%(\s\+\%(else\|is\)\)\=\>:\<end\s\+select\>,' .. + \ '$IF\>:$\%(ELSEIF\|ELSE\)\>:$END\s*IF\>' + +unlet s:not_end + +let &cpo = s:cpo_save +unlet s:cpo_save + +" vim: nowrap sw=2 sts=2 ts=8 noet fdm=marker: diff --git a/runtime/indent/basic.vim b/runtime/indent/basic.vim new file mode 100644 index 0000000000..7228772251 --- /dev/null +++ b/runtime/indent/basic.vim @@ -0,0 +1,11 @@ +" Vim indent file +" Language: BASIC (QuickBASIC 4.5) +" Maintainer: Doug Kearns <dougkearns@gmail.com> +" Last Change: 2022 Jan 24 + +" Only load this indent file when no other was loaded. +if exists("b:did_indent") + finish +endif + +runtime! indent/vb.vim diff --git a/runtime/indent/freebasic.vim b/runtime/indent/freebasic.vim new file mode 100644 index 0000000000..248b928635 --- /dev/null +++ b/runtime/indent/freebasic.vim @@ -0,0 +1,11 @@ +" Vim indent file +" Language: FreeBASIC +" Maintainer: Doug Kearns <dougkearns@gmail.com> +" Last Change: 2022 Jan 24 + +" Only load this indent file when no other was loaded. +if exists("b:did_indent") + finish +endif + +runtime! indent/vb.vim diff --git a/runtime/indent/html.vim b/runtime/indent/html.vim index d4b91f6421..a3c32d6342 100644 --- a/runtime/indent/html.vim +++ b/runtime/indent/html.vim @@ -1,7 +1,7 @@ " Vim indent script for HTML " Maintainer: Bram Moolenaar " Original Author: Andy Wokula <anwoku@yahoo.de> -" Last Change: 2021 Jun 13 +" Last Change: 2022 Jan 31 " Version: 1.0 "{{{ " Description: HTML indent script with cached state for faster indenting on a " range of lines. @@ -149,6 +149,15 @@ func HtmlIndent_CheckUserSettings() let b:html_indent_line_limit = 200 endif endif + + if exists('b:html_indent_attribute') + let b:hi_attr_indent = b:html_indent_attribute + elseif exists('g:html_indent_attribute') + let b:hi_attr_indent = g:html_indent_attribute + else + let b:hi_attr_indent = 2 + endif + endfunc "}}} " Init Script Vars @@ -946,11 +955,11 @@ func s:InsideTag(foundHtmlString) let idx = match(text, '<' . s:tagname . '\s\+\zs\w') endif if idx == -1 - " after just "<tag" indent two levels more + " after just "<tag" indent two levels more by default let idx = match(text, '<' . s:tagname . '$') if idx >= 0 call cursor(lnum, idx + 1) - return virtcol('.') - 1 + shiftwidth() * 2 + return virtcol('.') - 1 + shiftwidth() * b:hi_attr_indent endif endif if idx > 0 diff --git a/runtime/indent/qb64.vim b/runtime/indent/qb64.vim new file mode 100644 index 0000000000..09f815c43d --- /dev/null +++ b/runtime/indent/qb64.vim @@ -0,0 +1,11 @@ +" Vim indent file +" Language: QB64 +" Maintainer: Doug Kearns <dougkearns@gmail.com> +" Last Change: 2022 Jan 24 + +" Only load this indent file when no other was loaded. +if exists("b:did_indent") + finish +endif + +runtime! indent/vb.vim diff --git a/runtime/indent/testdir/html.in b/runtime/indent/testdir/html.in index 1acf8c0402..b62c67ddb2 100644 --- a/runtime/indent/testdir/html.in +++ b/runtime/indent/testdir/html.in @@ -1,4 +1,4 @@ -" vim: set ft=html sw=4 : +" vim: set ft=html sw=4 ts=8 : " START_INDENT @@ -41,6 +41,11 @@ dd text dt text </dt> </dl> +<div +class="test" +style="color: yellow"> +text +</div> </body> </html> @@ -50,6 +55,7 @@ dt text % START_INDENT % INDENT_EXE let g:html_indent_style1 = "inc" % INDENT_EXE let g:html_indent_script1 = "zero" +% INDENT_EXE let g:html_indent_attribute = 1 % INDENT_EXE call HtmlIndent_CheckUserSettings() <html> <body> @@ -61,6 +67,11 @@ div#d2 { color: green; } var v1 = "v1"; var v2 = "v2"; </script> +<div +class="test" +style="color: yellow"> +text +</div> </body> </html> % END_INDENT diff --git a/runtime/indent/testdir/html.ok b/runtime/indent/testdir/html.ok index c0dfc9dc72..938e965d8c 100644 --- a/runtime/indent/testdir/html.ok +++ b/runtime/indent/testdir/html.ok @@ -1,4 +1,4 @@ -" vim: set ft=html sw=4 : +" vim: set ft=html sw=4 ts=8 : " START_INDENT @@ -41,6 +41,11 @@ div#d2 { color: green; } dt text </dt> </dl> + <div + class="test" + style="color: yellow"> + text + </div> </body> </html> @@ -50,6 +55,7 @@ div#d2 { color: green; } % START_INDENT % INDENT_EXE let g:html_indent_style1 = "inc" % INDENT_EXE let g:html_indent_script1 = "zero" +% INDENT_EXE let g:html_indent_attribute = 1 % INDENT_EXE call HtmlIndent_CheckUserSettings() <html> <body> @@ -61,6 +67,11 @@ div#d2 { color: green; } var v1 = "v1"; var v2 = "v2"; </script> + <div + class="test" + style="color: yellow"> + text + </div> </body> </html> % END_INDENT diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index adc838578d..e2cf408f3b 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -789,7 +789,7 @@ local extension = { ex = function() vim.fn["dist#ft#ExCheck"]() end, exu = function() vim.fn["dist#ft#EuphoriaCheck"]() end, exw = function() vim.fn["dist#ft#EuphoriaCheck"]() end, - frm = function() vim.fn["dist#ft#FTVB"]("form") end, + frm = function() vim.fn["dist#ft#FTfrm"]() end, fs = function() vim.fn["dist#ft#FTfs"]() end, h = function() vim.fn["dist#ft#FTheader"]() end, htm = function() vim.fn["dist#ft#FThtml"]() end, diff --git a/runtime/lua/vim/ui.lua b/runtime/lua/vim/ui.lua index 0f2de6ce5c..9d4b38f08a 100644 --- a/runtime/lua/vim/ui.lua +++ b/runtime/lua/vim/ui.lua @@ -78,7 +78,7 @@ end --- --- Example: --- <pre> ---- vim.ui.input({ prompt = 'Select value for shiftwidth: ' }, function(input) +--- vim.ui.input({ prompt = 'Enter value for shiftwidth: ' }, function(input) --- vim.o.shiftwidth = tonumber(input) --- end) --- </pre> diff --git a/runtime/pack/dist/opt/matchit/autoload/matchit.vim b/runtime/pack/dist/opt/matchit/autoload/matchit.vim index e8689980ae..eafb7c0551 100644 --- a/runtime/pack/dist/opt/matchit/autoload/matchit.vim +++ b/runtime/pack/dist/opt/matchit/autoload/matchit.vim @@ -763,9 +763,9 @@ fun! s:ParseSkip(str) let skip = "synIDattr(synID(line('.'),col('.'),1),'name') !~? '" .. \ strpart(skip,2) .. "'" elseif skip[0] == "r" - let skip = "strpart(getline('.'),0,col('.'))=~'" . strpart(skip,2). "'" + let skip = "strpart(getline('.'),0,col('.'))=~'" .. strpart(skip,2) .. "'" elseif skip[0] == "R" - let skip = "strpart(getline('.'),0,col('.'))!~'" . strpart(skip,2). "'" + let skip = "strpart(getline('.'),0,col('.'))!~'" .. strpart(skip,2) .. "'" endif endif return skip diff --git a/runtime/syntax/basic.vim b/runtime/syntax/basic.vim index ad9450b3b8..7fe411a869 100644 --- a/runtime/syntax/basic.vim +++ b/runtime/syntax/basic.vim @@ -1,14 +1,15 @@ " Vim syntax file -" Language: BASIC +" Language: BASIC (QuickBASIC 4.5) " Maintainer: Doug Kearns <dougkearns@gmail.com> " Previous Maintainer: Allan Kelly <allan@fruitloaf.co.uk> " Contributors: Thilo Six -" Last Change: 2015 Jan 10 +" Last Change: 2021 Aug 08 " First version based on Micro$soft QBASIC circa 1989, as documented in " 'Learn BASIC Now' by Halvorson&Rygmyr. Microsoft Press 1989. -" This syntax file not a complete implementation yet. Send suggestions to the -" maintainer. +" +" Second version attempts to match Microsoft QuickBASIC 4.5 while keeping FreeBASIC +" (-lang qb) and QB64 (excluding extensions) in mind. -- DJK " Prelude {{{1 if exists("b:current_syntax") @@ -18,154 +19,357 @@ endif let s:cpo_save = &cpo set cpo&vim +syn iskeyword @,48-57,.,!,#,%,&,$ +syn case ignore + +" Whitespace Errors {{{1 +if exists("basic_space_errors") + if !exists("basic_no_trail_space_error") + syn match basicSpaceError display excludenl "\s\+$" + endif + if !exists("basic_no_tab_space_error") + syn match basicSpaceError display " \+\t"me=e-1 + endif +endif + +" Comment Errors {{{1 +if !exists("basic_no_comment_errors") + syn match basicCommentError "\<REM\>.*" +endif + +" Not Top Cluster {{{1 +syn cluster basicNotTop contains=@basicLineIdentifier,basicDataString,basicDataSeparator,basicTodo + +" Statements {{{1 + +syn cluster basicStatements contains=basicStatement,basicDataStatement,basicMetaRemStatement,basicPutStatement,basicRemStatement + +let s:statements =<< trim EOL " {{{2 + beep + bload + bsave + call + calls + case + chain + chdir + circle + clear + close + cls + color + com + common + const + declare + def + def\s\+seg + defdbl + defint + deflng + defsng + defstr + dim + do + draw + elseif + end + end\s\+\%(def\|function\|if\|select\|sub\|type\) + environ + erase + error + exit\s\+\%(def\|do\|for\|function\|sub\) + field + files + for + function + get + gosub + goto + if + input + ioctl + key + kill + let + line + line\s\+input + locate + lock + loop + lprint + lset + mkdir + name + next + on + on\s\+error + on\s\+uevent + open + open\s\+com + option + out + paint + palette + palette\s\+using + pcopy + pen + pmap + poke + preset + print + pset + randomize + read + redim + reset + restore + resume + return + rmdir + rset + run + select\s\+case + shared + shell + sleep + sound + static + stop + strig + sub + swap + system + troff + tron + type + uevent + unlock + using + view + view\s\+print + wait + wend + while + width + window + write +EOL +" }}} + +for s in s:statements + exe 'syn match basicStatement "\<' .. s .. '\>" contained' +endfor + +syn match basicStatement "\<\%(then\|else\)\>" nextgroup=@basicStatements skipwhite + +" DATA Statement +syn match basicDataSeparator "," contained +syn region basicDataStatement matchgroup=basicStatement start="\<data\>" matchgroup=basicStatementSeparator end=":\|$" contained contains=basicDataSeparator,basicDataString,basicNumber,basicFloat,basicString + +if !exists("basic_no_data_fold") + syn region basicMultilineData start="^\s*\<data\>.*\n\%(^\s*\<data\>\)\@=" end="^\s*\<data\>.*\n\%(^\s*\<data\>\)\@!" contains=basicDataStatement transparent fold keepend +endif + +" PUT File I/O and Graphics statements - needs special handling for graphics +" action verbs +syn match basicPutAction "\<\%(pset\|preset\|and\|or\|xor\)\>" contained +syn region basicPutStatement matchgroup=basicStatement start="\<put\>" matchgroup=basicStatementSeparator end=":\|$" contained contains=basicKeyword,basicPutAction,basicFilenumber + " Keywords {{{1 -syn keyword basicStatement BEEP beep Beep BLOAD bload Bload BSAVE bsave Bsave -syn keyword basicStatement CALL call Call ABSOLUTE absolute Absolute -syn keyword basicStatement CHAIN chain Chain CHDIR chdir Chdir -syn keyword basicStatement CIRCLE circle Circle CLEAR clear Clear -syn keyword basicStatement CLOSE close Close CLS cls Cls COLOR color Color -syn keyword basicStatement COM com Com COMMON common Common -syn keyword basicStatement CONST const Const DATA data Data -syn keyword basicStatement DECLARE declare Declare DEF def Def -syn keyword basicStatement DEFDBL defdbl Defdbl DEFINT defint Defint -syn keyword basicStatement DEFLNG deflng Deflng DEFSNG defsng Defsng -syn keyword basicStatement DEFSTR defstr Defstr DIM dim Dim -syn keyword basicStatement DO do Do LOOP loop Loop -syn keyword basicStatement DRAW draw Draw END end End -syn keyword basicStatement ENVIRON environ Environ ERASE erase Erase -syn keyword basicStatement ERROR error Error EXIT exit Exit -syn keyword basicStatement FIELD field Field FILES files Files -syn keyword basicStatement FOR for For NEXT next Next -syn keyword basicStatement FUNCTION function Function GET get Get -syn keyword basicStatement GOSUB gosub Gosub GOTO goto Goto -syn keyword basicStatement IF if If THEN then Then ELSE else Else -syn keyword basicStatement INPUT input Input INPUT# input# Input# -syn keyword basicStatement IOCTL ioctl Ioctl KEY key Key -syn keyword basicStatement KILL kill Kill LET let Let -syn keyword basicStatement LINE line Line LOCATE locate Locate -syn keyword basicStatement LOCK lock Lock UNLOCK unlock Unlock -syn keyword basicStatement LPRINT lprint Lprint USING using Using -syn keyword basicStatement LSET lset Lset MKDIR mkdir Mkdir -syn keyword basicStatement NAME name Name ON on On -syn keyword basicStatement ERROR error Error OPEN open Open -syn keyword basicStatement OPTION option Option BASE base Base -syn keyword basicStatement OUT out Out PAINT paint Paint -syn keyword basicStatement PALETTE palette Palette PCOPY pcopy Pcopy -syn keyword basicStatement PEN pen Pen PLAY play Play -syn keyword basicStatement PMAP pmap Pmap POKE poke Poke -syn keyword basicStatement PRESET preset Preset PRINT print Print -syn keyword basicStatement PRINT# print# Print# USING using Using -syn keyword basicStatement PSET pset Pset PUT put Put -syn keyword basicStatement RANDOMIZE randomize Randomize READ read Read -syn keyword basicStatement REDIM redim Redim RESET reset Reset -syn keyword basicStatement RESTORE restore Restore RESUME resume Resume -syn keyword basicStatement RETURN return Return RMDIR rmdir Rmdir -syn keyword basicStatement RSET rset Rset RUN run Run -syn keyword basicStatement SEEK seek Seek SELECT select Select -syn keyword basicStatement CASE case Case SHARED shared Shared -syn keyword basicStatement SHELL shell Shell SLEEP sleep Sleep -syn keyword basicStatement SOUND sound Sound STATIC static Static -syn keyword basicStatement STOP stop Stop STRIG strig Strig -syn keyword basicStatement SUB sub Sub SWAP swap Swap -syn keyword basicStatement SYSTEM system System TIMER timer Timer -syn keyword basicStatement TROFF troff Troff TRON tron Tron -syn keyword basicStatement TYPE type Type UNLOCK unlock Unlock -syn keyword basicStatement VIEW view View WAIT wait Wait -syn keyword basicStatement WHILE while While WEND wend Wend -syn keyword basicStatement WIDTH width Width WINDOW window Window -syn keyword basicStatement WRITE write Write DATE$ date$ Date$ -syn keyword basicStatement MID$ mid$ Mid$ TIME$ time$ Time$ - -syn keyword basicFunction ABS abs Abs ASC asc Asc -syn keyword basicFunction ATN atn Atn CDBL cdbl Cdbl -syn keyword basicFunction CINT cint Cint CLNG clng Clng -syn keyword basicFunction COS cos Cos CSNG csng Csng -syn keyword basicFunction CSRLIN csrlin Csrlin CVD cvd Cvd -syn keyword basicFunction CVDMBF cvdmbf Cvdmbf CVI cvi Cvi -syn keyword basicFunction CVL cvl Cvl CVS cvs Cvs -syn keyword basicFunction CVSMBF cvsmbf Cvsmbf EOF eof Eof -syn keyword basicFunction ERDEV erdev Erdev ERL erl Erl -syn keyword basicFunction ERR err Err EXP exp Exp -syn keyword basicFunction FILEATTR fileattr Fileattr FIX fix Fix -syn keyword basicFunction FRE fre Fre FREEFILE freefile Freefile -syn keyword basicFunction INP inp Inp INSTR instr Instr -syn keyword basicFunction INT int Int LBOUND lbound Lbound -syn keyword basicFunction LEN len Len LOC loc Loc -syn keyword basicFunction LOF lof Lof LOG log Log -syn keyword basicFunction LPOS lpos Lpos PEEK peek Peek -syn keyword basicFunction PEN pen Pen POINT point Point -syn keyword basicFunction POS pos Pos RND rnd Rnd -syn keyword basicFunction SADD sadd Sadd SCREEN screen Screen -syn keyword basicFunction SEEK seek Seek SETMEM setmem Setmem -syn keyword basicFunction SGN sgn Sgn SIN sin Sin -syn keyword basicFunction SPC spc Spc SQR sqr Sqr -syn keyword basicFunction STICK stick Stick STRIG strig Strig -syn keyword basicFunction TAB tab Tab TAN tan Tan -syn keyword basicFunction UBOUND ubound Ubound VAL val Val -syn keyword basicFunction VALPTR valptr Valptr VALSEG valseg Valseg -syn keyword basicFunction VARPTR varptr Varptr VARSEG varseg Varseg -syn keyword basicFunction CHR$ Chr$ chr$ COMMAND$ command$ Command$ -syn keyword basicFunction DATE$ date$ Date$ ENVIRON$ environ$ Environ$ -syn keyword basicFunction ERDEV$ erdev$ Erdev$ HEX$ hex$ Hex$ -syn keyword basicFunction INKEY$ inkey$ Inkey$ INPUT$ input$ Input$ -syn keyword basicFunction IOCTL$ ioctl$ Ioctl$ LCASES$ lcases$ Lcases$ -syn keyword basicFunction LAFT$ laft$ Laft$ LTRIM$ ltrim$ Ltrim$ -syn keyword basicFunction MID$ mid$ Mid$ MKDMBF$ mkdmbf$ Mkdmbf$ -syn keyword basicFunction MKD$ mkd$ Mkd$ MKI$ mki$ Mki$ -syn keyword basicFunction MKL$ mkl$ Mkl$ MKSMBF$ mksmbf$ Mksmbf$ -syn keyword basicFunction MKS$ mks$ Mks$ OCT$ oct$ Oct$ -syn keyword basicFunction RIGHT$ right$ Right$ RTRIM$ rtrim$ Rtrim$ -syn keyword basicFunction SPACE$ space$ Space$ STR$ str$ Str$ -syn keyword basicFunction STRING$ string$ String$ TIME$ time$ Time$ -syn keyword basicFunction UCASE$ ucase$ Ucase$ VARPTR$ varptr$ Varptr$ +let s:keywords =<< trim EOL " {{{2 + absolute + access + alias + append + as + base + binary + byval + cdecl + com + def + do + for + function + gosub + goto + input + int86old + int86xold + interrupt + interruptx + is + key + len + list + local + lock + lprint + next + off + on + output + pen + play + random + read + resume + screen + seg + shared + signal + static + step + stop + strig + sub + timer + to + until + using + while + write +EOL +" }}} + +for k in s:keywords + exe 'syn match basicKeyword "\<' .. k .. '\>"' +endfor + +" Functions {{{1 +syn keyword basicFunction abs asc atn cdbl chr$ cint clng command$ cos csng +syn keyword basicFunction csrlin cvd cvdmbf cvi cvl cvs cvsmbf environ$ eof +syn keyword basicFunction erdev erdev$ erl err exp fileattr fix fre freefile +syn keyword basicFunction hex$ inkey$ inp input$ instr int ioctl$ left$ lbound +syn keyword basicFunction lcase$ len loc lof log lpos ltrim$ mkd$ mkdmbf$ mki$ +syn keyword basicFunction mkl$ mks$ mksmbf$ oct$ peek pen point pos right$ rnd +syn keyword basicFunction rtrim$ sadd setmem sgn sin space$ spc sqr stick str$ +syn keyword basicFunction strig string$ tab tan ubound ucase$ val valptr +syn keyword basicFunction valseg varptr varptr$ varseg + +" Functions and statements (same name) {{{1 +syn match basicStatement "\<\%(date\$\|mid\$\|play\|screen\|seek\|time\$\|timer\)\>" contained +syn match basicFunction "\<\%(date\$\|mid\$\|play\|screen\|seek\|time\$\|timer\)\>" + +" Types {{{1 +syn keyword basicType integer long single double string any + +" Strings {{{1 + +" Unquoted DATA strings - anything except [:,] and leading or trailing whitespace +" Needs lower priority than numbers +syn match basicDataString "[^[:space:],:]\+\%(\s\+[^[:space:],:]\+\)*" contained + +syn region basicString start=+"+ end=+"+ oneline + +" Booleans {{{1 +if exists("basic_booleans") + syn keyword basicBoolean true false +endif " Numbers {{{1 -" Integer number, or floating point number without a dot. -syn match basicNumber "\<\d\+\>" -" Floating point number, with dot -syn match basicNumber "\<\d\+\.\d*\>" -" Floating point number, starting with a dot -syn match basicNumber "\.\d\+\>" -" String and Character constants {{{1 -syn match basicSpecial "\\\d\d\d\|\\." contained -syn region basicString start=+"+ skip=+\\\\\|\\"+ end=+"+ contains=basicSpecial +" Integers +syn match basicNumber "-\=&o\=\o\+[%&]\=\>" +syn match basicNumber "-\=&h\x\+[%&]\=\>" +syn match basicNumber "-\=\<\d\+[%&]\=\>" + +" Floats +syn match basicFloat "-\=\<\d\+\.\=\d*\%(\%([ed][+-]\=\d*\)\|[!#]\)\=\>" +syn match basicFloat "-\=\<\.\d\+\%(\%([ed][+-]\=\d*\)\|[!#]\)\=\>" -" Line numbers {{{1 -syn region basicLineNumber start="^\d" end="\s" +" Statement anchors {{{1 +syn match basicLineStart "^" nextgroup=@basicStatements,@basicLineIdentifier skipwhite +syn match basicStatementSeparator ":" nextgroup=@basicStatements skipwhite -" Data-type suffixes {{{1 -syn match basicTypeSpecifier "[a-zA-Z0-9][$%&!#]"ms=s+1 -" Used with OPEN statement -syn match basicFilenumber "#\d\+" +" Line numbers and labels {{{1 + +" QuickBASIC limits these to 65,529 and 40 chars respectively +syn match basicLineNumber "\d\+" nextgroup=@basicStatements skipwhite contained +syn match basicLineLabel "\a[[:alnum:]]*\ze\s*:" nextgroup=@basicStatements skipwhite contained + +syn cluster basicLineIdentifier contains=basicLineNumber,basicLineLabel + +" Line Continuation {{{1 +syn match basicLineContinuation "\s*\zs_\ze\s*$" + +" Type suffixes {{{1 +if exists("basic_type_suffixes") + syn match basicTypeSuffix "\a[[:alnum:].]*\zs[$%&!#]" +endif -" Mathematical operators {{{1 -" syn match basicMathsOperator "[<>+\*^/\\=-]" -syn match basicMathsOperator "-\|=\|[:<>+\*^/\\]\|AND\|OR" +" File numbers {{{1 +syn match basicFilenumber "#\d\+" +syn match basicFilenumber "#\a[[:alnum:].]*[%&!#]\=" + +" Operators {{{1 +if exists("basic_operators") + syn match basicArithmeticOperator "[-+*/\\^]" + syn match basicRelationalOperator "<>\|<=\|>=\|[><=]" +endif +syn match basicLogicalOperator "\<\%(not\|and\|or\|xor\|eqv\|imp\)\>" +syn match basicArithmeticOperator "\<mod\>" + +" Metacommands {{{1 +" Note: No trailing word boundaries. Text may be freely mixed however there +" must be only leading whitespace prior to the first metacommand +syn match basicMetacommand "$INCLUDE\s*:\s*'[^']\+'" contained containedin=@basicMetaComments +syn match basicMetacommand "$\%(DYNAMIC\|STATIC\)" contained containedin=@basicMetaComments " Comments {{{1 -syn keyword basicTodo TODO FIXME XXX NOTE contained -syn region basicComment start="^\s*\zsREM\>" start="\%(:\s*\)\@<=REM\>" end="$" contains=basicTodo -syn region basicComment start="'" end="$" contains=basicTodo +syn keyword basicTodo TODO FIXME XXX NOTE contained + +syn region basicRemStatement matchgroup=basicStatement start="REM\>" end="$" contains=basicTodo,@Spell contained +syn region basicComment start="'" end="$" contains=basicTodo,@Spell + +if !exists("basic_no_comment_fold") + syn region basicMultilineComment start="^\s*'.*\n\%(\s*'\)\@=" end="^\s*'.*\n\%(\s*'\)\@!" contains=@basicComments transparent fold keepend +endif + +" Metacommands +syn region basicMetaRemStatement matchgroup=basicStatement start="REM\>\s*\$\@=" end="$" contains=basicTodo contained +syn region basicMetaComment start="'\s*\$\@=" end="$" contains=basicTodo + +syn cluster basicMetaComments contains=basicMetaComment,basicMetaRemStatement +syn cluster basicComments contains=basicComment,basicMetaComment "syn sync ccomment basicComment " Default Highlighting {{{1 -hi def link basicLabel Label -hi def link basicConditional Conditional -hi def link basicRepeat Repeat -hi def link basicLineNumber Comment -hi def link basicNumber Number -hi def link basicError Error -hi def link basicStatement Statement -hi def link basicString String -hi def link basicComment Comment -hi def link basicSpecial Special -hi def link basicTodo Todo -hi def link basicFunction Identifier -hi def link basicTypeSpecifier Type -hi def link basicFilenumber basicTypeSpecifier -"hi basicMathsOperator term=bold cterm=bold gui=bold +hi def link basicArithmeticOperator basicOperator +hi def link basicBoolean Boolean +hi def link basicComment Comment +hi def link basicCommentError Error +hi def link basicDataString basicString +hi def link basicFilenumber basicTypeSuffix " TODO: better group +hi def link basicFloat Float +hi def link basicFunction Identifier +hi def link basicKeyword Keyword +hi def link basicLineIdentifier LineNr +hi def link basicLineContinuation Special +hi def link basicLineLabel basicLineIdentifier +hi def link basicLineNumber basicLineIdentifier +hi def link basicLogicalOperator basicOperator +hi def link basicMetacommand SpecialComment +hi def link basicMetaComment Comment +hi def link basicMetaRemStatement Comment +hi def link basicNumber Number +hi def link basicOperator Operator +hi def link basicPutAction Keyword +hi def link basicRelationalOperator basicOperator +hi def link basicRemStatement Comment +hi def link basicSpaceError Error +hi def link basicStatementSeparator Special +hi def link basicStatement Statement +hi def link basicString String +hi def link basicTodo Todo +hi def link basicType Type +hi def link basicTypeSuffix Special +if exists("basic_legacy_syntax_groups") + hi def link basicTypeSpecifier Type + hi def link basicTypeSuffix basicTypeSpecifier +endif " Postscript {{{1 let b:current_syntax = "basic" diff --git a/runtime/syntax/qb64.vim b/runtime/syntax/qb64.vim new file mode 100644 index 0000000000..a777e14481 --- /dev/null +++ b/runtime/syntax/qb64.vim @@ -0,0 +1,409 @@ +" Vim syntax file +" Language: QB64 +" Maintainer: Doug Kearns <dougkearns@gmail.com> +" Last Change: 2022 Jan 21 + +" Prelude {{{1 +if exists("b:current_syntax") + finish +endif + +let s:cpo_save = &cpo +set cpo&vim + +" syn iskeyword set after sourcing of basic.vim + +syn case ignore + +let s:prefix = search('\c^\s*$NOPREFIX\>', 'n') ? '_\=' : '_' + +" Statements {{{1 + +let s:statements =<< trim EOL " {{{2 + acceptfiledrop + allowfullscreen + assert + console + consolecursor + consolefont + consoletitle + continue + copypalette + define + delay + depthbuffer + displayorder + dontblend + echo + exit\s\+\%(select\|case\) + finishdrop + freefont + freeimage + icon + keyclear + limit + maptriangle + memcopy + memfill + memfree + memput + mousehide + mousemove + mouseshow + printimage + printstring + putimage + screenclick + screenhide + screenmove + screenprint + screenshow + setalpha + sndbal + sndclose + sndlimit + sndloop + sndpause + sndplay + sndplaycopy + sndplayfile + sndraw + sndrawdone + sndsetpos + sndstop + sndvol + title +EOL +" }}} + +for s in s:statements + exe 'syn match qb64Statement "\<' .. s:prefix .. s .. '\>" contained contains=qb64Underscore' +endfor + +" Functions {{{1 + +let s:functions =<< trim EOL " {{{2 + acos + acosh + alpha + alpha32 + arccot + arccsc + arcsec + asin + asinh + atan2 + atanh + axis + backgroundcolor + blue + blue32 + button + buttonchange + ceil + cinp + commandcount + connected + connectionaddress + connectionaddress$ + consoleinput + copyimage + cot + coth + cosh + csc + csch + cv + cwd$ + d2g + d2r + defaultcolor + deflate$ + desktopheight + desktopwidth + device$ + deviceinput + devices + dir$ + direxists + droppedfile + droppedfile$ + errorline + errormessage$ + exit + fileexists + fontheight + fontwidth + freetimer + g2d + g2r + green + green32 + height + hypot + inclerrorfile$ + inclerrorline + inflate$ + instrrev + keyhit + keydown + lastaxis + lastbutton + lastwheel + loadfont + loadimage + mem + memelement + memexists + memimage + memnew + memsound + mk$ + mousebutton + mouseinput + mousemovementx + mousemovementy + mousepipeopen + mousewheel + mousex + mousey + newimage + offset + openclient + os$ + pi + pixelsize + printwidth + r2d + r2g + red + red32 + readbit + resetbit + resizeheight + resizewidth + rgb + rgb32 + rgba + rgba32 + round + sec + sech + screenexists + screenimage + screenx + screeny + setbit + shellhide + shl + shr + sinh + sndcopy + sndgetpos + sndlen + sndopen + sndopenraw + sndpaused + sndplaying + sndrate + sndrawlen + startdir$ + strcmp + stricmp + tanh + title$ + togglebit + totaldroppedfiles + trim$ + wheel + width + windowhandle + windowhasfocus +EOL +" }}} + +for f in s:functions + exe 'syn match qb64Function "\<' .. s:prefix .. f .. '\>" contains=qb64Underscore' +endfor + +" Functions and statements (same name) {{{1 + +let s:common =<< trim EOL " {{{2 + autodisplay + blend + blink + capslock + clearcolor + clipboard$ + clipboardimage + controlchr + dest + display + font + fullscreen + mapunicode + memget + numlock + palettecolor + printmode + resize + screenicon + scrolllock + source +EOL +" }}} + +for c in s:common + exe 'syn match qb64Statement "\<' .. s:prefix .. c .. '\>" contains=qb64Underscore contained' + exe 'syn match qb64Function "\<' .. s:prefix .. c .. '\>" contains=qb64Underscore' +endfor + +" Keywords {{{1 + +" Non-prefixed keywords {{{2 +" TIMER FREE +" _DEPTH_BUFFER LOCK +syn keyword qb64Keyword free lock + +let s:keywords =<< trim EOL " {{{2 + all + anticlockwise + behind + clear + clip + console + dontwait + explicit + explicitarray + fillbackground + hardware + hardware1 + hide + keepbackground + middle + none + off + only + onlybackground + ontop + openconnection + openhost + preserve + seamless + smooth + smoothshrunk + smoothstretched + software + squarepixels + stretch + toggle +EOL +" }}} + +for k in s:keywords + exe 'syn match qb64Keyword "\<' .. s:prefix .. k .. '\>" contains=qb64Underscore' +endfor + +syn match qb64Underscore "\<_" contained conceal transparent + +" Source QuickBASIC syntax {{{1 +runtime! syntax/basic.vim + +" add after the BASIC syntax file is sourced so cluster already exists +syn cluster basicStatements add=qb64Statement,qb64Metacommand,qb64IfMetacommand +syn cluster basicLineIdentifier add=qb64LineLabel +syn cluster qb64NotTop contains=@basicNotTop,qb64Metavariable + +syn iskeyword @,48-57,.,_,!,#,$,%,&,` + +" Unsupported QuickBASIC features {{{1 +" TODO: add linux only missing features +syn keyword qb64Unsupported alias any byval calls cdecl erdev erdev$ fileattr +syn keyword qb64Unsupported fre ioctl ioctl$ pen play setmem signal uevent +syn keyword qb64Unsupported tron troff +syn match qb64Unsupported "\<declare\%(\s\+\%(sub\|function\)\>\)\@=" +syn match qb64Unsupported "\<\%(date\|time\)$\ze\s*=" " statements only +syn match qb64Unsupported "\<def\zs\s\+FN" +syn match qb64Unsupported "\<\%(exit\|end\)\s\+def\>" +syn match qb64Unsupported "\<width\s\+lprint\>" + +" Types {{{1 +syn keyword qb64Type _BIT _BYTE _FLOAT _INTEGER64 _MEM _OFFSET _UNSIGNED + +" Type suffixes {{{1 +if exists("basic_type_suffixes") + " TODO: handle leading word boundary and __+ prefix + syn match qb64TypeSuffix "\%(\a[[:alnum:]._]*\)\@<=\~\=`\%(\d\+\)\=" + syn match qb64TypeSuffix "\%(\a[[:alnum:]._]*\)\@<=\~\=\%(%\|%%\|&\|&&\|%&\)" + syn match qb64TypeSuffix "\%(\a[[:alnum:]._]*\)\@<=\%(!\|##\|#\)" + syn match qb64TypeSuffix "\%(\a[[:alnum:]._]*\)\@<=$\%(\d\+\)\=" +endif + +" Numbers {{{1 + +" Integers +syn match qb64Number "-\=&b[01]\+&\>\=" + +syn match qb64Number "-\=\<[01]\~\=`\>" +syn match qb64Number "-\=\<\d\+`\d\+\>" + +syn match qb64Number "-\=\<\d\+\%(%%\|&&\|%&\)\>" +syn match qb64Number "\<\d\+\~\%(%%\|&&\|%&\)\>" + +syn match qb64Number "-\=\<&b[01]\+\%(%%\|&&\|%&\)\>" +syn match qb64Number "\<&b[01]\+\~\%(%%\|&&\|%&\)\>" + +syn match qb64Number "-\=\<&o\=\o\+\%(%%\|&&\|%&\)\>" +syn match qb64Number "\<&o\=\o\+\~\%(%%\|&&\|%&\)\>" + +syn match qb64Number "-\=\<&h\x\+\%(%%\|&&\|%&\)\>" +syn match qb64Number "\<&h\x\+\~\%(%%\|&&\|%&\)\>" + +" Floats +syn match qb64Float "-\=\<\d\+\.\=\d*##\>" +syn match qb64Float "-\=\<\.\d\+##\>" + +" Line numbers and labels {{{1 +syn match qb64LineLabel "\%(_\{2,}\)\=\a[[:alnum:]._]*[[:alnum:]]\ze\s*:" nextgroup=@basicStatements skipwhite contained + +" Metacommands {{{1 +syn match qb64Metacommand contained "$NOPREFIX\>" +syn match qb64Metacommand contained "$ASSERTS\%(:CONSOLE\)\=\>" +syn match qb64Metacommand contained "$CHECKING:\%(ON\|OFF\)\>" +syn match qb64Metacommand contained "$COLOR:\%(0\|32\)\>" +syn match qb64Metacommand contained "$CONSOLE\%(:ONLY\)\=\>" +syn match qb64Metacommand contained "$EXEICON\s*:\s*'[^']\+'" +syn match qb64Metacommand contained "$ERROR\>" +syn match qb64Metacommand contained "$LET\>" +syn match qb64Metacommand contained "$RESIZE:\%(ON\|OFF\|STRETCH\|SMOOTH\)\>" +syn match qb64Metacommand contained "$SCREEN\%(HIDE\|SHOW\)\>" +syn match qb64Metacommand contained "$VERSIONINFO\s*:.*" +syn match qb64Metacommand contained "$VIRTUALKEYBOARD:\%(ON\|OFF\)\>" + +syn region qb64IfMetacommand contained matchgroup=qb64Metacommand start="$\%(IF\|ELSEIF\)\>" end="\<THEN\>" oneline transparent contains=qb64Metavariable +syn match qb64Metacommand contained "$\%(ELSE\|END\s*IF\)\>" + +syn keyword qb64Metavariable contained defined undefined +syn keyword qb64Metavariable contained windows win linux mac maxosx +syn keyword qb64Metavariable contained 32bit 64bit version + +" Default Highlighting {{{1 +hi def link qb64Float basicFloat +hi def link qb64Function Function +hi def link qb64Keyword Keyword +hi def link qb64LineLabel basicLineLabel +hi def link qb64Metacommand PreProc +hi def link qb64Metavariable Identifier +hi def link qb64Number basicNumber +hi def link qb64Statement Statement +hi def link qb64TypeSuffix basicTypeSuffix +hi def link qb64Type Type +hi def link qb64Unsupported Error + +" Postscript {{{1 +let b:current_syntax = "qb64" + +let &cpo = s:cpo_save +unlet s:cpo_save + +" vim: nowrap sw=2 sts=2 ts=8 noet fdm=marker: diff --git a/runtime/syntax/strace.vim b/runtime/syntax/strace.vim index 206c58919e..20516a1853 100644 --- a/runtime/syntax/strace.vim +++ b/runtime/syntax/strace.vim @@ -1,8 +1,7 @@ " Vim syntax file -" This is a GENERATED FILE. Please always refer to source file at the URI below. " Language: strace output " Maintainer: David Necas (Yeti) <yeti@physics.muni.cz> -" Last Change: 2015-01-16 +" Last Change: 2022 Jan 29 " Setup " quit when a syntax file was already loaded diff --git a/scripts/gen_vimdoc.py b/scripts/gen_vimdoc.py index 3e9fb21039..36768953f9 100755 --- a/scripts/gen_vimdoc.py +++ b/scripts/gen_vimdoc.py @@ -52,7 +52,7 @@ import logging from xml.dom import minidom -MIN_PYTHON_VERSION = (3, 5) +MIN_PYTHON_VERSION = (3, 6) if sys.version_info < MIN_PYTHON_VERSION: print("requires Python {}.{}+".format(*MIN_PYTHON_VERSION)) @@ -96,7 +96,6 @@ CONFIG = { 'win_config.c', 'tabpage.c', 'ui.c', - 'extmark.c', ], # List of files/directories for doxygen to read, separated by blanks 'files': os.path.join(base_dir, 'src/nvim/api'), diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh index 1c265f0f40..67a2cc96fd 100755 --- a/scripts/vim-patch.sh +++ b/scripts/vim-patch.sh @@ -576,13 +576,13 @@ show_vimpatches() { runtime_commits[$commit]=1 done - while read -r vim_commit; do + list_missing_vimpatches 1 "$@" | while read -r vim_commit; do if [[ "${runtime_commits[$vim_commit]-}" ]]; then printf ' • %s (+runtime)\n' "${vim_commit}" else printf ' • %s\n' "${vim_commit}" fi - done <<< "$(list_missing_vimpatches 1 "$@")" + done cat << EOF diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 94572b57cd..dcc20194f0 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -493,6 +493,9 @@ add_executable(nvim ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS} ${EXTERNAL_SOURCES} ${EXTERNAL_HEADERS}) target_link_libraries(nvim ${NVIM_EXEC_LINK_LIBRARIES}) install_helper(TARGETS nvim) +if(MSVC) + install(FILES $<TARGET_PDB_FILE:nvim> DESTINATION ${CMAKE_INSTALL_BINDIR} OPTIONAL) +endif() set_property(TARGET nvim APPEND PROPERTY INCLUDE_DIRECTORIES ${LUA_PREFERRED_INCLUDE_DIRS}) diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua index 7d521bbf25..075e2c48d2 100644 --- a/src/nvim/api/keysets.lua +++ b/src/nvim/api/keysets.lua @@ -78,5 +78,32 @@ return { option = { "scope"; }; + highlight = { + "bold"; + "standout"; + "underline"; + "undercurl"; + "italic"; + "reverse"; + "default"; + "global"; + "cterm"; + "foreground"; "fg"; + "background"; "bg"; + "ctermfg"; + "ctermbg"; + "special"; "sp"; + "link"; + "fallback"; + "temp"; + }; + highlight_cterm = { + "bold"; + "standout"; + "underline"; + "undercurl"; + "italic"; + "reverse"; + }; } diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index f540f8f7ee..ddcfff0097 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -139,10 +139,10 @@ bool try_end(Error *err) got_int = false; } else if (msg_list != NULL && *msg_list != NULL) { int should_free; - char *msg = (char *)get_exception_string(*msg_list, - ET_ERROR, - NULL, - &should_free); + char *msg = get_exception_string(*msg_list, + ET_ERROR, + NULL, + &should_free); api_set_error(err, kErrorTypeException, "%s", msg); free_global_msglist(); @@ -720,7 +720,6 @@ fail_and_free: xfree(parsed_args.rhs); xfree(parsed_args.orig_rhs); XFREE_CLEAR(parsed_args.desc); - return; } /// Collects `n` buffer lines into array `l`, optionally replacing newlines @@ -990,18 +989,16 @@ Object copy_object(Object obj) static void set_option_value_for(char *key, int numval, char *stringval, int opt_flags, int opt_type, void *from, Error *err) { - win_T *save_curwin = NULL; - tabpage_T *save_curtab = NULL; + switchwin_T switchwin; aco_save_T aco; try_start(); switch (opt_type) { case SREQ_WIN: - if (switch_win_noblock(&save_curwin, &save_curtab, (win_T *)from, - win_find_tabpage((win_T *)from), true) + if (switch_win_noblock(&switchwin, (win_T *)from, win_find_tabpage((win_T *)from), true) == FAIL) { - restore_win_noblock(save_curwin, save_curtab, true); + restore_win_noblock(&switchwin, true); if (try_end(err)) { return; } @@ -1011,7 +1008,7 @@ static void set_option_value_for(char *key, int numval, char *stringval, int opt return; } set_option_value_err(key, numval, stringval, opt_flags, err); - restore_win_noblock(save_curwin, save_curtab, true); + restore_win_noblock(&switchwin, true); break; case SREQ_BUF: aucmd_prepbuf(&aco, (buf_T *)from); diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 7c194935ce..ada041bab2 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -19,6 +19,7 @@ #include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/buffer_defs.h" +#include "nvim/charset.h" #include "nvim/context.h" #include "nvim/decoration.h" #include "nvim/edit.h" @@ -31,6 +32,7 @@ #include "nvim/fileio.h" #include "nvim/getchar.h" #include "nvim/highlight.h" +#include "nvim/highlight_defs.h" #include "nvim/lua/executor.h" #include "nvim/mark.h" #include "nvim/memline.h" @@ -121,7 +123,9 @@ Dictionary nvim__get_hl_defs(Integer ns_id, Error *err) /// Set a highlight group. /// -/// @param ns_id number of namespace for this highlight +/// @param ns_id number of namespace for this highlight. Use value 0 +/// to set a highlight group in the global (`:highlight`) +/// namespace. /// @param name highlight group name, like ErrorMsg /// @param val highlight definition map, like |nvim_get_hl_by_name|. /// in addition the following keys are also recognized: @@ -135,9 +139,8 @@ Dictionary nvim__get_hl_defs(Integer ns_id, Error *err) /// same as attributes of gui color /// @param[out] err Error details, if any /// -/// TODO: ns_id = 0, should modify :highlight namespace -/// TODO val should take update vs reset flag -void nvim_set_hl(Integer ns_id, String name, Dictionary val, Error *err) +// TODO(bfredl): val should take update vs reset flag +void nvim_set_hl(Integer ns_id, String name, Dict(highlight) *val, Error *err) FUNC_API_SINCE(7) { int hl_id = syn_check_group(name.data, (int)name.size); @@ -145,7 +148,7 @@ void nvim_set_hl(Integer ns_id, String name, Dictionary val, Error *err) HlAttrs attrs = dict2hlattrs(val, true, &link_id, err); if (!ERROR_SET(err)) { - ns_hl_def((NS)ns_id, hl_id, attrs, link_id); + ns_hl_def((NS)ns_id, hl_id, attrs, link_id, val); } } @@ -2236,7 +2239,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * Dictionary result = ARRAY_DICT_INIT; int maxwidth; - char fillchar = 0; + int fillchar = 0; Window window = 0; bool use_tabline = false; bool highlights = false; @@ -2251,12 +2254,12 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * } if (HAS_KEY(opts->fillchar)) { - if (opts->fillchar.type != kObjectTypeString || opts->fillchar.data.string.size > 1) { - api_set_error(err, kErrorTypeValidation, "fillchar must be an ASCII character"); + if (opts->fillchar.type != kObjectTypeString || opts->fillchar.data.string.size == 0 + || char2cells(fillchar = utf_ptr2char((char_u *)opts->fillchar.data.string.data)) != 1 + || (size_t)utf_char2len(fillchar) != opts->fillchar.data.string.size) { + api_set_error(err, kErrorTypeValidation, "fillchar must be a single-width character"); return result; } - - fillchar = opts->fillchar.data.string.data[0]; } if (HAS_KEY(opts->highlights)) { @@ -2292,7 +2295,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * if (fillchar == 0) { int attr; - fillchar = (char)fillchar_status(&attr, wp); + fillchar = fillchar_status(&attr, wp); } } @@ -2320,7 +2323,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * sizeof(buf), (char_u *)str.data, false, - (char_u)fillchar, + fillchar, maxwidth, hltab_ptr, NULL); diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index 907306da7b..9fd4de4bb2 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -455,17 +455,12 @@ Object nvim_win_call(Window window, LuaRef fun, Error *err) } tabpage_T *tabpage = win_find_tabpage(win); - win_T *save_curwin; - tabpage_T *save_curtab; - try_start(); Object res = OBJECT_INIT; - if (switch_win_noblock(&save_curwin, &save_curtab, win, tabpage, true) == - OK) { + WIN_EXECUTE(win, tabpage, { Array args = ARRAY_DICT_INIT; res = nlua_call_ref(fun, NULL, args, true, err); - } - restore_win_noblock(save_curwin, save_curtab, true); + }); try_end(err); return res; } diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index 94ac389139..2e7f9c5136 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -1069,8 +1069,6 @@ void ex_doautoall(exarg_T *eap) do_modelines(0); } } - - check_cursor(); // just in case lines got deleted } /// Check *argp for <nomodeline>. When it is present return false, otherwise @@ -1171,6 +1169,10 @@ void aucmd_prepbuf(aco_save_T *aco, buf_T *buf) curbuf = buf; aco->new_curwin_handle = curwin->handle; set_bufref(&aco->new_curbuf, curbuf); + + // disable the Visual area, the position may be invalid in another buffer + aco->save_VIsual_active = VIsual_active; + VIsual_active = false; } /// Cleanup after executing autocommands for a (hidden) buffer. @@ -1267,6 +1269,12 @@ win_found: check_cursor(); } } + + check_cursor(); // just in case lines got deleted + VIsual_active = aco->save_VIsual_active; + if (VIsual_active) { + check_pos(curbuf, &VIsual); + } } /// Execute autocommands for "event" and file name "fname". diff --git a/src/nvim/autocmd.h b/src/nvim/autocmd.h index ac12e2acf3..63c5abd4f8 100644 --- a/src/nvim/autocmd.h +++ b/src/nvim/autocmd.h @@ -14,6 +14,7 @@ typedef struct { handle_T save_prevwin_handle; ///< ID of saved prevwin bufref_T new_curbuf; ///< new curbuf char_u *globaldir; ///< saved value of globaldir + bool save_VIsual_active; ///< saved VIsual_active } aco_save_T; typedef struct AutoCmd { diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index eee5a0b46c..bb8483f644 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -1899,10 +1899,8 @@ void free_buf_options(buf_T *buf, int free_p_ff) clear_string_option(&buf->b_p_flp); clear_string_option(&buf->b_p_isk); clear_string_option(&buf->b_p_vsts); - xfree(buf->b_p_vsts_nopaste); - buf->b_p_vsts_nopaste = NULL; - xfree(buf->b_p_vsts_array); - buf->b_p_vsts_array = NULL; + XFREE_CLEAR(buf->b_p_vsts_nopaste); + XFREE_CLEAR(buf->b_p_vsts_array); clear_string_option(&buf->b_p_vts); XFREE_CLEAR(buf->b_p_vts_array); clear_string_option(&buf->b_p_keymap); @@ -3421,7 +3419,7 @@ typedef enum { /// /// @return The final width of the statusline int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use_sandbox, - char_u fillchar, int maxwidth, stl_hlrec_t **hltab, StlClickRecord **tabtab) + int fillchar, int maxwidth, stl_hlrec_t **hltab, StlClickRecord **tabtab) { static size_t stl_items_len = 20; // Initial value, grows as needed. static stl_item_t *stl_items = NULL; @@ -3464,9 +3462,6 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use if (fillchar == 0) { fillchar = ' '; - } else if (utf_char2len(fillchar) > 1) { - // Can't handle a multi-byte fill character yet. - fillchar = '-'; } // The cursor in windows other than the current one isn't always @@ -3664,7 +3659,7 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use out_p = out_p - n + 1; // Fill up space left over by half a double-wide char. while (++group_len < stl_items[stl_groupitems[groupdepth]].minwid) { - *out_p++ = fillchar; + MB_CHAR2BYTES(fillchar, out_p); } // } @@ -3687,14 +3682,14 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use if (min_group_width < 0) { min_group_width = 0 - min_group_width; while (group_len++ < min_group_width && out_p < out_end_p) { - *out_p++ = fillchar; + MB_CHAR2BYTES(fillchar, out_p); } // If the group is right-aligned, shift everything to the right and // prepend with filler characters. } else { // { Move the group to the right - memmove(t + min_group_width - group_len, t, (size_t)(out_p - t)); - group_len = min_group_width - group_len; + group_len = (min_group_width - group_len) * utf_char2len(fillchar); + memmove(t + group_len, t, (size_t)(out_p - t)); if (out_p + group_len >= (out_end_p + 1)) { group_len = (long)(out_end_p - out_p); } @@ -3708,7 +3703,7 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use // Prepend the fill characters for (; group_len > 0; group_len--) { - *t++ = fillchar; + MB_CHAR2BYTES(fillchar, t); } } } @@ -4233,7 +4228,7 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use if (l + 1 == minwid && fillchar == '-' && ascii_isdigit(*t)) { *out_p++ = ' '; } else { - *out_p++ = fillchar; + MB_CHAR2BYTES(fillchar, out_p); } } minwid = 0; @@ -4244,20 +4239,21 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use } // { Copy the string text into the output buffer - while (*t && out_p < out_end_p) { - *out_p++ = *t++; + for (; *t && out_p < out_end_p; t++) { // Change a space by fillchar, unless fillchar is '-' and a // digit follows. - if (fillable && out_p[-1] == ' ' - && (!ascii_isdigit(*t) || fillchar != '-')) { - out_p[-1] = fillchar; + if (fillable && *t == ' ' + && (!ascii_isdigit(*(t + 1)) || fillchar != '-')) { + MB_CHAR2BYTES(fillchar, out_p); + } else { + *out_p++ = *t; } } // } // For left-aligned items, fill any remaining space with the fillchar for (; l < minwid && out_p < out_end_p; l++) { - *out_p++ = fillchar; + MB_CHAR2BYTES(fillchar, out_p); } // Otherwise if the item is a number, copy that to the output buffer. @@ -4450,7 +4446,7 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use // Fill up for half a double-wide character. while (++width < maxwidth) { - *trunc_p++ = fillchar; + MB_CHAR2BYTES(fillchar, trunc_p); *trunc_p = NUL; } // } @@ -4501,13 +4497,13 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use standard_spaces * (num_separators - 1); for (int i = 0; i < num_separators; i++) { - int dislocation = (i == (num_separators - 1)) - ? final_spaces : standard_spaces; + int dislocation = (i == (num_separators - 1)) ? final_spaces : standard_spaces; + dislocation *= utf_char2len(fillchar); char_u *start = stl_items[stl_separator_locations[i]].start; char_u *seploc = start + dislocation; STRMOVE(seploc, start); - for (char_u *s = start; s < seploc; s++) { - *s = fillchar; + for (char_u *s = start; s < seploc; ) { + MB_CHAR2BYTES(fillchar, s); } for (int item_idx = stl_separator_locations[i] + 1; diff --git a/src/nvim/change.c b/src/nvim/change.c index 0c16b204e3..6ac759d5e0 100644 --- a/src/nvim/change.c +++ b/src/nvim/change.c @@ -952,11 +952,13 @@ int copy_indent(int size, char_u *src) /// /// "second_line_indent": indent for after ^^D in Insert mode or if flag /// OPENLINE_COM_LIST +/// "did_do_comment" is set to true when intentionally putting the comment +/// leader in fromt of the new line. /// /// @param dir FORWARD or BACKWARD /// /// @return true on success, false on failure -int open_line(int dir, int flags, int second_line_indent) +int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) { char_u *next_line = NULL; // copy of the next line char_u *p_extra = NULL; // what goes to next line @@ -969,6 +971,7 @@ int open_line(int dir, int flags, int second_line_indent) bool retval = false; // return value int extra_len = 0; // length of p_extra string int lead_len; // length of comment leader + int comment_start = 0; // start index of the comment leader char_u *lead_flags; // position in 'comments' for comment leader char_u *leader = NULL; // copy of comment leader char_u *allocated = NULL; // allocated memory @@ -977,6 +980,7 @@ int open_line(int dir, int flags, int second_line_indent) pos_T *pos; bool do_si = (!p_paste && curbuf->b_p_si && !curbuf->b_p_cin && *curbuf->b_p_inde == NUL); + bool do_cindent; bool no_si = false; // reset did_si afterwards int first_char = NUL; // init for GCC int vreplace_mode; @@ -1189,11 +1193,30 @@ int open_line(int dir, int flags, int second_line_indent) did_ai = true; } + // May do indenting after opening a new line. + do_cindent = !p_paste && (curbuf->b_p_cin || *curbuf->b_p_inde != NUL) + && in_cinkeys(dir == FORWARD ? KEY_OPEN_FORW : KEY_OPEN_BACK, + ' ', linewhite(curwin->w_cursor.lnum)); + // Find out if the current line starts with a comment leader. // This may then be inserted in front of the new line. end_comment_pending = NUL; if (flags & OPENLINE_DO_COM) { lead_len = get_leader_len(saved_line, &lead_flags, dir == BACKWARD, true); + 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, + &lead_flags, false, true); + if (lead_len != 0) { + lead_len += comment_start; + if (did_do_comment != NULL) { + *did_do_comment = true; + } + } + } + } } else { lead_len = 0; } @@ -1349,6 +1372,13 @@ int open_line(int dir, int flags, int second_line_indent) STRLCPY(leader, saved_line, lead_len + 1); + // TODO(vim): handle multi-byte and double width chars + for (int li = 0; li < comment_start; li++) { + if (!ascii_iswhite(leader[li])) { + leader[li] = ' '; + } + } + // Replace leader with lead_repl, right or left adjusted if (lead_repl != NULL) { int c = 0; @@ -1758,13 +1788,7 @@ int open_line(int dir, int flags, int second_line_indent) ai_col = (colnr_T)getwhitecols_curline(); } // May do indenting after opening a new line. - if (!p_paste - && (curbuf->b_p_cin - || *curbuf->b_p_inde != NUL - ) - && in_cinkeys(dir == FORWARD - ? KEY_OPEN_FORW - : KEY_OPEN_BACK, ' ', linewhite(curwin->w_cursor.lnum))) { + if (do_cindent) { do_c_expr_indent(); ai_col = (colnr_T)getwhitecols_curline(); } diff --git a/src/nvim/charset.c b/src/nvim/charset.c index 599d662993..583a040ed1 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -217,9 +217,7 @@ int buf_init_chartab(buf_T *buf, int global) } } else if (i == 1) { // (re)set printable - // For double-byte we keep the cell width, so - // that we can detect it from the first byte. - if (((c < ' ') || (c > '~'))) { + if (c < ' ' || c > '~') { if (tilde) { g_chartab[c] = (uint8_t)((g_chartab[c] & ~CT_CELL_MASK) + ((dy_flags & DY_UHEX) ? 4 : 2)); @@ -539,7 +537,7 @@ char_u *transchar_buf(const buf_T *buf, int c) c = K_SECOND(c); } - if ((!chartab_initialized && (((c >= ' ') && (c <= '~')))) + if ((!chartab_initialized && (c >= ' ' && c <= '~')) || ((c <= 0xFF) && vim_isprintc_strict(c))) { // printable character transchar_charbuf[i] = (char_u)c; diff --git a/src/nvim/debugger.c b/src/nvim/debugger.c index b6e35f3047..75ebf0084e 100644 --- a/src/nvim/debugger.c +++ b/src/nvim/debugger.c @@ -268,7 +268,7 @@ void do_debug(char_u *cmd) DOCMD_VERBOSE|DOCMD_EXCRESET); debug_break_level = n; } - lines_left = (int)(Rows - 1); + lines_left = Rows - 1; } xfree(cmdline); @@ -277,7 +277,7 @@ void do_debug(char_u *cmd) redraw_all_later(NOT_VALID); need_wait_return = false; msg_scroll = save_msg_scroll; - lines_left = (int)(Rows - 1); + lines_left = Rows - 1; State = save_State; debug_mode = false; did_emsg = save_did_emsg; diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 095fa14752..00ffa7cba1 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -6021,6 +6021,7 @@ static void internal_format(int textwidth, int second_indent, int flags, int for char_u *saved_text = NULL; colnr_T col; colnr_T end_col; + bool did_do_comment = false; virtcol = get_nolist_virtcol() + char2cells(c != NUL ? c : gchar_cursor()); @@ -6136,8 +6137,7 @@ static void internal_format(int textwidth, int second_indent, int flags, int for if (curwin->w_cursor.col <= (colnr_T)wantcol) { break; } - } else if ((cc >= 0x100 || !utf_allow_break_before(cc)) - && fo_multibyte) { + } else if ((cc >= 0x100 || !utf_allow_break_before(cc)) && fo_multibyte) { int ncc; bool allow_break; @@ -6294,11 +6294,18 @@ static void internal_format(int textwidth, int second_indent, int flags, int for + (fo_white_par ? OPENLINE_KEEPTRAIL : 0) + (do_comments ? OPENLINE_DO_COM : 0) + ((flags & INSCHAR_COM_LIST) ? OPENLINE_COM_LIST : 0), - ((flags & INSCHAR_COM_LIST) ? second_indent : old_indent)); + ((flags & INSCHAR_COM_LIST) ? second_indent : old_indent), + &did_do_comment); if (!(flags & INSCHAR_COM_LIST)) { old_indent = 0; } + // If a comment leader was inserted, may also do this on a following + // line. + if (did_do_comment) { + no_leader = false; + } + replace_offset = 0; if (first_line) { if (!(flags & INSCHAR_COM_LIST)) { @@ -7124,9 +7131,7 @@ int stuff_inserted(int c, long count, int no_esc) stuffReadbuff((const char *)ptr); // A trailing "0" is inserted as "<C-V>048", "^" as "<C-V>^". if (last) { - stuffReadbuff((last == '0' - ? "\026\060\064\070" - : "\026^")); + stuffReadbuff(last == '0' ? "\026\060\064\070" : "\026^"); } } while (--count > 0); @@ -8292,6 +8297,7 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) int in_indent; int oldState; int cpc[MAX_MCO]; // composing characters + bool call_fix_indent = false; // can't delete anything in an empty file // can't backup past first character in buffer @@ -8435,6 +8441,8 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) beginline(BL_WHITE); if (curwin->w_cursor.col < save_col) { mincol = curwin->w_cursor.col; + // should now fix the indent to match with the previous line + call_fix_indent = true; } curwin->w_cursor.col = save_col; } @@ -8569,6 +8577,11 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) if (curwin->w_cursor.col <= 1) { did_ai = false; } + + if (call_fix_indent) { + fix_indent(); + } + // It's a little strange to put backspaces into the redo // buffer, but it makes auto-indent a lot easier to deal // with. @@ -9183,7 +9196,7 @@ static bool ins_eol(int c) AppendToRedobuff(NL_STR); bool i = open_line(FORWARD, has_format_option(FO_RET_COMS) ? OPENLINE_DO_COM : 0, - old_indent); + old_indent, NULL); old_indent = 0; can_cindent = true; // When inserting a line the cursor line must never be in a closed fold. diff --git a/src/nvim/eval.c b/src/nvim/eval.c index cf3322df1b..926c385892 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -69,6 +69,7 @@ static char *e_nowhitespace static char *e_invalwindow = N_("E957: Invalid window number"); static char *e_lock_unlock = N_("E940: Cannot lock or unlock variable %s"); static char *e_write2 = N_("E80: Error while writing: %s"); +static char *e_string_list_or_blob_required = N_("E1098: String, List or Blob required"); // TODO(ZyX-I): move to eval/executor static char *e_letwrong = N_("E734: Wrong variable type for %s="); @@ -112,9 +113,11 @@ typedef struct { int fi_semicolon; // TRUE if ending in '; var]' int fi_varcount; // nr of variables in the list listwatch_T fi_lw; // keep an eye on the item used. - list_T *fi_list; // list being used + list_T *fi_list; // list being used int fi_bi; // index of blob blob_T *fi_blob; // blob being used + char_u *fi_string; // copy of string being used + int fi_byte_idx; // byte index in fi_string } forinfo_T; // values for vv_flags: @@ -2641,8 +2644,15 @@ void *eval_for_line(const char_u *arg, bool *errp, char_u **nextcmdp, int skip) fi->fi_blob = btv.vval.v_blob; } tv_clear(&tv); + } else if (tv.v_type == VAR_STRING) { + fi->fi_byte_idx = 0; + fi->fi_string = tv.vval.v_string; + tv.vval.v_string = NULL; + if (fi->fi_string == NULL) { + fi->fi_string = vim_strsave((char_u *)""); + } } else { - emsg(_(e_listblobreq)); + emsg(_(e_string_list_or_blob_required)); tv_clear(&tv); } } @@ -2679,6 +2689,22 @@ bool next_for_item(void *fi_void, char_u *arg) fi->fi_semicolon, fi->fi_varcount, false, NULL) == OK; } + if (fi->fi_string != NULL) { + const int len = utfc_ptr2len(fi->fi_string + fi->fi_byte_idx); + if (len == 0) { + return false; + } + typval_T tv; + tv.v_type = VAR_STRING; + tv.v_lock = VAR_FIXED; + tv.vval.v_string = vim_strnsave(fi->fi_string + fi->fi_byte_idx, len); + fi->fi_byte_idx += len; + const int result + = ex_let_vars(arg, &tv, true, fi->fi_semicolon, fi->fi_varcount, false, NULL) == OK; + xfree(tv.vval.v_string); + return result; + } + listitem_T *item = fi->fi_lw.lw_item; if (item == NULL) { return false; @@ -2698,12 +2724,16 @@ void free_for_info(void *fi_void) { forinfo_T *fi = (forinfo_T *)fi_void; - if (fi != NULL && fi->fi_list != NULL) { + if (fi == NULL) { + return; + } + if (fi->fi_list != NULL) { tv_list_watch_remove(fi->fi_list, &fi->fi_lw); tv_list_unref(fi->fi_list); - } - if (fi != NULL && fi->fi_blob != NULL) { + } else if (fi->fi_blob != NULL) { tv_blob_unref(fi->fi_blob); + } else { + xfree(fi->fi_string); } xfree(fi); } @@ -6963,10 +6993,9 @@ win_T *find_tabwin(typval_T *wvp, typval_T *tvp) /// @param off 1 for gettabwinvar() void getwinvar(typval_T *argvars, typval_T *rettv, int off) { - win_T *win, *oldcurwin; + win_T *win; dictitem_T *v; tabpage_T *tp = NULL; - tabpage_T *oldtabpage = NULL; bool done = false; if (off == 1) { @@ -6986,8 +7015,8 @@ void getwinvar(typval_T *argvars, typval_T *rettv, int off) // otherwise the window is not valid. Only do this when needed, // autocommands get blocked. bool need_switch_win = tp != curtab || win != curwin; - if (!need_switch_win - || switch_win(&oldcurwin, &oldtabpage, win, tp, true) == OK) { + switchwin_T switchwin; + if (!need_switch_win || switch_win(&switchwin, win, tp, true) == OK) { if (*varname == '&') { if (varname[1] == NUL) { // get all window-local options in a dict @@ -7015,7 +7044,7 @@ void getwinvar(typval_T *argvars, typval_T *rettv, int off) if (need_switch_win) { // restore previous notion of curwin - restore_win(oldcurwin, oldtabpage, true); + restore_win(&switchwin, true); } } emsg_off--; @@ -7517,11 +7546,9 @@ void setwinvar(typval_T *argvars, typval_T *rettv, int off) typval_T *varp = &argvars[off + 2]; if (win != NULL && varname != NULL && varp != NULL) { - win_T *save_curwin; - tabpage_T *save_curtab; bool need_switch_win = tp != curtab || win != curwin; - if (!need_switch_win - || switch_win(&save_curwin, &save_curtab, win, tp, true) == OK) { + switchwin_T switchwin; + if (!need_switch_win || switch_win(&switchwin, win, tp, true) == OK) { if (*varname == '&') { long numval; bool error = false; @@ -7543,7 +7570,7 @@ void setwinvar(typval_T *argvars, typval_T *rettv, int off) } } if (need_switch_win) { - restore_win(save_curwin, save_curtab, true); + restore_win(&switchwin, true); } } } @@ -8154,6 +8181,52 @@ char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl) return ret; } +/// Convert the specified byte index of line 'lnum' in buffer 'buf' to a +/// character index. Works only for loaded buffers. Returns -1 on failure. +/// The index of the first character is one. +int buf_byteidx_to_charidx(buf_T *buf, int lnum, int byteidx) +{ + if (buf == NULL || buf->b_ml.ml_mfp == NULL) { + return -1; + } + + if (lnum > buf->b_ml.ml_line_count) { + lnum = buf->b_ml.ml_line_count; + } + + char_u *str = ml_get_buf(buf, lnum, false); + + if (*str == NUL) { + return 1; + } + + return mb_charlen_len(str, byteidx + 1); +} + +/// Convert the specified character index of line 'lnum' in buffer 'buf' to a +/// byte index. Works only for loaded buffers. Returns -1 on failure. The index +/// of the first byte and the first character is one. +int buf_charidx_to_byteidx(buf_T *buf, int lnum, int charidx) +{ + if (buf == NULL || buf->b_ml.ml_mfp == NULL) { + return -1; + } + + if (lnum > buf->b_ml.ml_line_count) { + lnum = buf->b_ml.ml_line_count; + } + + char_u *str = ml_get_buf(buf, lnum, false); + + // Convert the character offset to a byte offset + char_u *t = str; + while (*t != NUL && --charidx > 0) { + t += utfc_ptr2len(t); + } + + return t - str + 1; +} + /// Translate a VimL object into a position /// /// Accepts VAR_LIST and VAR_STRING objects. Does not give an error for invalid @@ -8162,9 +8235,11 @@ char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl) /// @param[in] tv Object to translate. /// @param[in] dollar_lnum True when "$" is last line. /// @param[out] ret_fnum Set to fnum for marks. +/// @param[in] charcol True to return character column. /// /// @return Pointer to position or NULL in case of error (e.g. invalid type). -pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret_fnum) +pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret_fnum, + const bool charcol) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { static pos_T pos; @@ -8194,7 +8269,11 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret if (error) { return NULL; } - len = (long)STRLEN(ml_get(pos.lnum)); + if (charcol) { + len = mb_charlen(ml_get(pos.lnum)); + } else { + len = STRLEN(ml_get(pos.lnum)); + } // We accept "$" for the column number: last column. li = tv_list_find(l, 1L); @@ -8225,19 +8304,31 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret return NULL; } if (name[0] == '.') { // Cursor. - return &curwin->w_cursor; + pos = curwin->w_cursor; + if (charcol) { + pos.col = buf_byteidx_to_charidx(curbuf, pos.lnum, pos.col) - 1; + } + return &pos; } if (name[0] == 'v' && name[1] == NUL) { // Visual start. if (VIsual_active) { - return &VIsual; + pos = VIsual; + } else { + pos = curwin->w_cursor; + } + if (charcol) { + pos.col = buf_byteidx_to_charidx(curbuf, pos.lnum, pos.col) - 1; } - return &curwin->w_cursor; + return &pos; } if (name[0] == '\'') { // Mark. pp = getmark_buf_fnum(curbuf, (uint8_t)name[1], false, ret_fnum); if (pp == NULL || pp == (pos_T *)-1 || pp->lnum <= 0) { return NULL; } + if (charcol) { + pp->col = buf_byteidx_to_charidx(curbuf, pp->lnum, pp->col) - 1; + } return pp; } @@ -8263,22 +8354,24 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret pos.col = 0; } else { pos.lnum = curwin->w_cursor.lnum; - pos.col = (colnr_T)STRLEN(get_cursor_line_ptr()); + if (charcol) { + pos.col = (colnr_T)mb_charlen(get_cursor_line_ptr()); + } else { + pos.col = (colnr_T)STRLEN(get_cursor_line_ptr()); + } } return &pos; } return NULL; } -/* - * Convert list in "arg" into a position and optional file number. - * When "fnump" is NULL there is no file number, only 3 items. - * Note that the column is passed on as-is, the caller may want to decrement - * it to use 1 for the first column. - * Return FAIL when conversion is not possible, doesn't check the position for - * validity. - */ -int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp) +/// Convert list in "arg" into a position and optional file number. +/// When "fnump" is NULL there is no file number, only 3 items. +/// Note that the column is passed on as-is, the caller may want to decrement +/// it to use 1 for the first column. +/// Return FAIL when conversion is not possible, doesn't check the position for +/// validity. +int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp, bool charcol) { list_T *l; long i = 0; @@ -8314,6 +8407,15 @@ int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp) if (n < 0) { return FAIL; } + // If character position is specified, then convert to byte position + if (charcol) { + // Get the text for the specified line in a loaded buffer + buf_T *buf = buflist_findnr(fnump == NULL ? curbuf->b_fnum : *fnump); + if (buf == NULL || buf->b_ml.ml_mfp == NULL) { + return FAIL; + } + n = buf_charidx_to_byteidx(buf, posp->lnum, n); + } posp->col = n; n = tv_list_find_nr(l, i, NULL); // off @@ -10522,12 +10624,13 @@ int modify_fname(char_u *src, bool tilde_file, size_t *usedlen, char_u **fnamep, char_u *s, *p, *pbuf; char_u dirname[MAXPATHL]; int c; - int has_fullname = 0; + bool has_fullname = false; + bool has_homerelative = false; repeat: // ":p" - full path/file_name if (src[*usedlen] == ':' && src[*usedlen + 1] == 'p') { - has_fullname = 1; + has_fullname = true; valid |= VALID_PATH; *usedlen += 2; @@ -10596,8 +10699,8 @@ repeat: } pbuf = NULL; // Need full path first (use expand_env() to remove a "~/") - if (!has_fullname) { - if (c == '.' && **fnamep == '~') { + if (!has_fullname && !has_homerelative) { + if ((c == '.' || c == '~') && **fnamep == '~') { p = pbuf = expand_env_save(*fnamep); } else { p = pbuf = (char_u *)FullName_save((char *)*fnamep, FALSE); @@ -10606,18 +10709,33 @@ repeat: p = *fnamep; } - has_fullname = 0; + has_fullname = false; if (p != NULL) { if (c == '.') { os_dirname(dirname, MAXPATHL); - s = path_shorten_fname(p, dirname); - if (s != NULL) { - *fnamep = s; - if (pbuf != NULL) { - xfree(*bufp); // free any allocated file name - *bufp = pbuf; - pbuf = NULL; + if (has_homerelative) { + s = vim_strsave(dirname); + home_replace(NULL, s, dirname, MAXPATHL, true); + xfree(s); + } + size_t namelen = STRLEN(dirname); + + // Do not call shorten_fname() here since it removes the prefix + // even though the path does not have a prefix. + if (fnamencmp(p, dirname, namelen) == 0) { + p += namelen; + if (vim_ispathsep(*p)) { + while (*p && vim_ispathsep(*p)) { + p++; + } + *fnamep = p; + if (pbuf != NULL) { + // free any allocated file name + xfree(*bufp); + *bufp = pbuf; + pbuf = NULL; + } } } } else { @@ -10628,6 +10746,7 @@ repeat: *fnamep = s; xfree(*bufp); *bufp = s; + has_homerelative = true; } } xfree(pbuf); @@ -11004,10 +11123,7 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments, boo bool eval_has_provider(const char *feat) { if (!strequal(feat, "clipboard") - && !strequal(feat, "python") && !strequal(feat, "python3") - && !strequal(feat, "python_compiled") - && !strequal(feat, "python_dynamic") && !strequal(feat, "python3_compiled") && !strequal(feat, "python3_dynamic") && !strequal(feat, "perl") diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index e00a14fca7..1e39854c86 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -71,6 +71,7 @@ return { chanclose={args={1, 2}}, chansend={args=2}, char2nr={args={1, 2}, base=1}, + charcol={args=1, base=1}, charidx={args={2, 3}, base=1}, chdir={args=1, base=1}, cindent={args=1, base=1}, @@ -144,6 +145,7 @@ return { getchangelist={args={0, 1}, base=1}, getchar={args={0, 1}}, getcharmod={}, + getcharpos={args=1, base=1}, getcharsearch={}, getcharstr={args={0, 1}}, getcmdline={}, @@ -151,7 +153,8 @@ return { getcmdtype={}, getcmdwintype={}, getcompletion={args={2, 3}, base=1}, - getcurpos={}, + getcurpos={args={0, 1}, base=1}, + getcursorcharpos={args={0, 1}, base=1}, getcwd={args={0, 2}, base=1}, getenv={args=1, base=1}, getfontname={args={0, 1}}, @@ -246,6 +249,8 @@ return { matcharg={args=1, base=1}, matchdelete={args={1, 2}, base=1}, matchend={args={2, 4}, base=1}, + matchfuzzy={args={2, 3}, base=1}, + matchfuzzypos={args={2, 3}, base=1}, matchlist={args={2, 4}, base=1}, matchstr={args={2, 4}, base=1}, matchstrpos={args={2,4}, base=1}, @@ -259,7 +264,7 @@ return { nextnonblank={args=1, base=1}, nr2char={args={1, 2}, base=1}, ['or']={args=2, base=1}, - pathshorten={args=1, base=1}, + pathshorten={args={1, 2}, base=1}, pow={args=2, base=1}, prevnonblank={args=1, base=1}, printf={args=varargs(1), base=2}, @@ -273,6 +278,7 @@ return { pyeval={args=1, base=1, func="f_py3eval"}, pyxeval={args=1, base=1, func="f_py3eval"}, perleval={args=1, base=1}, + rand={args={0, 1}, base=1}, range={args={1, 3}, base=1}, readdir={args={1, 2}, base=1}, readfile={args={1, 3}, base=1}, @@ -312,8 +318,10 @@ return { serverstop={args=1}, setbufline={args=3, base=3}, setbufvar={args=3, base=3}, + setcharpos={args=2, base=2}, setcharsearch={args=1, base=1}, setcmdpos={args=1, base=1}, + setcursorcharpos={args={1, 3}, base=1}, setenv={args=2, base=2}, setfperm={args=2, base=1}, setline={args=2, base=2}, @@ -349,6 +357,7 @@ return { spellsuggest={args={1, 3}, base=1}, split={args={1, 3}, base=1}, sqrt={args=1, base=1, func="float_op_wrapper", data="&sqrt"}, + srand={args={0, 1}, base=1}, stdpath={args=1}, str2float={args=1, base=1}, str2list={args={1, 2}, base=1}, diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index bd790bfdd3..db4fb06a73 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -22,6 +22,7 @@ #include "nvim/eval/encode.h" #include "nvim/eval/executor.h" #include "nvim/eval/funcs.h" +#include "nvim/eval/typval.h" #include "nvim/eval/userfunc.h" #include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" @@ -97,7 +98,6 @@ PRAGMA_DIAG_POP #endif -static char *e_listarg = N_("E686: Argument of %s must be a List"); 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"); @@ -1020,6 +1020,49 @@ static void f_char2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = utf_ptr2char((const char_u *)tv_get_string(&argvars[0])); } +/// Get the current cursor column and store it in 'rettv'. If 'charcol' is true, +/// returns the character index of the column. Otherwise, returns the byte index +/// of the column. +static void get_col(typval_T *argvars, typval_T *rettv, bool charcol) +{ + colnr_T col = 0; + pos_T *fp; + int fnum = curbuf->b_fnum; + + fp = var2fpos(&argvars[0], false, &fnum, charcol); + if (fp != NULL && fnum == curbuf->b_fnum) { + if (fp->col == MAXCOL) { + // '> can be MAXCOL, get the length of the line then + if (fp->lnum <= curbuf->b_ml.ml_line_count) { + col = (colnr_T)STRLEN(ml_get(fp->lnum)) + 1; + } else { + col = MAXCOL; + } + } else { + col = fp->col + 1; + // col(".") when the cursor is on the NUL at the end of the line + // because of "coladd" can be seen as an extra column. + if (virtual_active() && fp == &curwin->w_cursor) { + char_u *p = get_cursor_pos_ptr(); + if (curwin->w_cursor.coladd >= + (colnr_T)win_chartabsize(curwin, p, curwin->w_virtcol - curwin->w_cursor.coladd)) { + int l; + if (*p != NUL && p[(l = utfc_ptr2len(p))] == NUL) { + col += l; + } + } + } + } + } + rettv->vval.v_number = col; +} + +/// "charcol()" function +static void f_charcol(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + get_col(argvars, rettv, true); +} + // "charidx()" function static void f_charidx(typval_T *argvars, typval_T *rettv, FunPtr fptr) { @@ -1148,45 +1191,10 @@ static void f_clearmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "col(string)" function - */ +/// "col(string)" function static void f_col(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - colnr_T col = 0; - pos_T *fp; - int fnum = curbuf->b_fnum; - - fp = var2fpos(&argvars[0], FALSE, &fnum); - if (fp != NULL && fnum == curbuf->b_fnum) { - if (fp->col == MAXCOL) { - // '> can be MAXCOL, get the length of the line then - if (fp->lnum <= curbuf->b_ml.ml_line_count) { - col = (colnr_T)STRLEN(ml_get(fp->lnum)) + 1; - } else { - col = MAXCOL; - } - } else { - col = fp->col + 1; - // col(".") when the cursor is on the NUL at the end of the line - // because of "coladd" can be seen as an extra column. - if (virtual_active() && fp == &curwin->w_cursor) { - char_u *p = get_cursor_pos_ptr(); - - if (curwin->w_cursor.coladd - >= (colnr_T)win_chartabsize(curwin, p, - (curwin->w_virtcol - - curwin->w_cursor.coladd))) { - int l; - - if (*p != NUL && p[(l = utfc_ptr2len(p))] == NUL) { - col += l; - } - } - } - } - } - rettv->vval.v_number = col; + get_col(argvars, rettv, false); } /* @@ -1549,24 +1557,21 @@ static void f_ctxsize(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = ctx_size(); } -/// "cursor(lnum, col)" function, or -/// "cursor(list)" -/// -/// Moves the cursor to the specified line and column. -/// -/// @returns 0 when the position could be set, -1 otherwise. -static void f_cursor(typval_T *argvars, typval_T *rettv, FunPtr fptr) +/// Set the cursor position. +/// If 'charcol' is true, then use the column number as a character offet. +/// Otherwise use the column number as a byte offset. +static void set_cursorpos(typval_T *argvars, typval_T *rettv, bool charcol) { long line, col; long coladd = 0; bool set_curswant = true; rettv->vval.v_number = -1; - if (argvars[1].v_type == VAR_UNKNOWN) { + if (argvars[0].v_type == VAR_LIST) { pos_T pos; colnr_T curswant = -1; - if (list2fpos(argvars, &pos, NULL, &curswant) == FAIL) { + if (list2fpos(argvars, &pos, NULL, &curswant, charcol) == FAIL) { emsg(_(e_invarg)); return; } @@ -1578,16 +1583,22 @@ static void f_cursor(typval_T *argvars, typval_T *rettv, FunPtr fptr) curwin->w_curswant = curswant - 1; set_curswant = false; } - } else { + } else if ((argvars[0].v_type == VAR_NUMBER || argvars[0].v_type == VAR_STRING) + && (argvars[1].v_type == VAR_NUMBER || argvars[1].v_type == VAR_STRING)) { line = tv_get_lnum(argvars); col = (long)tv_get_number_chk(&argvars[1], NULL); + if (charcol) { + col = buf_charidx_to_byteidx(curbuf, line, col); + } if (argvars[2].v_type != VAR_UNKNOWN) { coladd = (long)tv_get_number_chk(&argvars[2], NULL); } + } else { + emsg(_(e_invarg)); + return; } - if (line < 0 || col < 0 - || coladd < 0) { - return; // type error; errmsg already given + if (line < 0 || col < 0 || coladd < 0) { + return; // type error; errmsg already given } if (line > 0) { curwin->w_cursor.lnum = line; @@ -1606,6 +1617,16 @@ static void f_cursor(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = 0; } +/// "cursor(lnum, col)" function, or +/// "cursor(list)" +/// +/// Moves the cursor to the specified line and column. +/// Returns 0 when the position could be set, -1 otherwise. +static void f_cursor(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + set_cursorpos(argvars, rettv, false); +} + // "debugbreak()" function static void f_debugbreak(typval_T *argvars, typval_T *rettv, FunPtr fptr) { @@ -2177,25 +2198,12 @@ static void f_win_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) { tabpage_T *tp; win_T *wp = win_id2wp_tp(argvars, &tp); - win_T *save_curwin; - tabpage_T *save_curtab; // Return an empty string if something fails. rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; if (wp != NULL && tp != NULL) { - pos_T curpos = wp->w_cursor; - if (switch_win_noblock(&save_curwin, &save_curtab, wp, tp, true) == - OK) { - check_cursor(); - execute_common(argvars, rettv, fptr, 1); - } - restore_win_noblock(save_curwin, save_curtab, true); - - // Update the status line if the cursor moved. - if (win_valid(wp) && !equalpos(curpos, wp->w_cursor)) { - wp->w_redr_status = true; - } + WIN_EXECUTE(wp, tp, execute_common(argvars, rettv, fptr, 1)); } } @@ -3181,7 +3189,7 @@ static void getchar_common(typval_T *argvars, typval_T *rettv) if (argvars[0].v_type == VAR_UNKNOWN) { // getchar(): blocking wait. // TODO(bfredl): deduplicate shared logic with state_enter ? - if (!(char_avail() || using_script() || input_available())) { + if (!char_avail()) { (void)os_inchar(NULL, 0, -1, 0, main_loop.events); if (!multiqueue_empty(main_loop.events)) { state_handle_k_event(); @@ -3301,6 +3309,67 @@ static void f_getcharmod(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = mod_mask; } +static void getpos_both(typval_T *argvars, typval_T *rettv, bool getcurpos, bool charcol) +{ + pos_T *fp = NULL; + pos_T pos; + win_T *wp = curwin; + int fnum = -1; + + if (getcurpos) { + if (argvars[0].v_type != VAR_UNKNOWN) { + wp = find_win_by_nr_or_id(&argvars[0]); + if (wp != NULL) { + fp = &wp->w_cursor; + } + } else { + fp = &curwin->w_cursor; + } + if (fp != NULL && charcol) { + pos = *fp; + pos.col = buf_byteidx_to_charidx(wp->w_buffer, pos.lnum, pos.col) - 1; + fp = &pos; + } + } else { + fp = var2fpos(&argvars[0], true, &fnum, charcol); + } + + list_T *const l = tv_list_alloc_ret(rettv, 4 + getcurpos); + tv_list_append_number(l, (fnum != -1) ? (varnumber_T)fnum : (varnumber_T)0); + tv_list_append_number(l, ((fp != NULL) ? (varnumber_T)fp->lnum : (varnumber_T)0)); + tv_list_append_number(l, ((fp != NULL) + ? (varnumber_T)(fp->col == MAXCOL ? MAXCOL : fp->col + 1) + : (varnumber_T)0)); + tv_list_append_number(l, (fp != NULL) ? (varnumber_T)fp->coladd : (varnumber_T)0); + if (getcurpos) { + const int save_set_curswant = curwin->w_set_curswant; + const colnr_T save_curswant = curwin->w_curswant; + const colnr_T save_virtcol = curwin->w_virtcol; + + if (wp == curwin) { + update_curswant(); + } + tv_list_append_number(l, (wp == NULL) ? 0 : ((wp->w_curswant == MAXCOL) + ? (varnumber_T)MAXCOL + : (varnumber_T)wp->w_curswant + 1)); + + // Do not change "curswant", as it is unexpected that a get + // function has a side effect. + if (wp == curwin && save_set_curswant) { + curwin->w_set_curswant = save_set_curswant; + curwin->w_curswant = save_curswant; + curwin->w_virtcol = save_virtcol; + curwin->w_valid &= ~VALID_VIRTCOL; + } + } +} + +/// "getcharpos()" function +static void f_getcharpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + getpos_both(argvars, rettv, false, true); +} + /* * "getcharsearch()" function */ @@ -3856,61 +3925,21 @@ static void f_getpid(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = os_get_pid(); } -static void getpos_both(typval_T *argvars, typval_T *rettv, bool getcurpos) +/// "getcurpos(string)" function +static void f_getcurpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - pos_T *fp; - int fnum = -1; - - if (getcurpos) { - fp = &curwin->w_cursor; - } else { - fp = var2fpos(&argvars[0], true, &fnum); - } - - list_T *const l = tv_list_alloc_ret(rettv, 4 + (!!getcurpos)); - tv_list_append_number(l, (fnum != -1) ? (varnumber_T)fnum : (varnumber_T)0); - tv_list_append_number(l, ((fp != NULL) - ? (varnumber_T)fp->lnum - : (varnumber_T)0)); - tv_list_append_number(l, ((fp != NULL) - ? (varnumber_T)(fp->col == MAXCOL ? MAXCOL : fp->col + 1) - : (varnumber_T)0)); - tv_list_append_number(l, (fp != NULL) ? (varnumber_T)fp->coladd : (varnumber_T)0); - if (getcurpos) { - const int save_set_curswant = curwin->w_set_curswant; - const colnr_T save_curswant = curwin->w_curswant; - const colnr_T save_virtcol = curwin->w_virtcol; - - update_curswant(); - tv_list_append_number(l, (curwin->w_curswant == MAXCOL - ? (varnumber_T)MAXCOL - : (varnumber_T)curwin->w_curswant + 1)); - - // Do not change "curswant", as it is unexpected that a get - // function has a side effect. - if (save_set_curswant) { - curwin->w_set_curswant = save_set_curswant; - curwin->w_curswant = save_curswant; - curwin->w_virtcol = save_virtcol; - curwin->w_valid &= ~VALID_VIRTCOL; - } - } + getpos_both(argvars, rettv, true, false); } -/* - * "getcurpos(string)" function - */ -static void f_getcurpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getcursorcharpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - getpos_both(argvars, rettv, true); + getpos_both(argvars, rettv, true, true); } -/* - * "getpos(string)" function - */ +/// "getpos(string)" function static void f_getpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - getpos_both(argvars, rettv, false); + getpos_both(argvars, rettv, false, false); } /// "getqflist()" functions @@ -4032,8 +4061,6 @@ static void f_gettabinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_gettabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - win_T *oldcurwin; - tabpage_T *oldtabpage; bool done = false; rettv->v_type = VAR_STRING; @@ -4047,7 +4074,8 @@ static void f_gettabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) win_T *const window = tp == curtab || tp->tp_firstwin == NULL ? firstwin : tp->tp_firstwin; - if (switch_win(&oldcurwin, &oldtabpage, window, tp, true) == OK) { + switchwin_T switchwin; + if (switch_win(&switchwin, window, tp, true) == OK) { // look up the variable // Let gettabvar({nr}, "") return the "t:" dictionary. const dictitem_T *const v = find_var_in_ht(&tp->tp_vars->dv_hashtab, 't', @@ -4060,7 +4088,7 @@ static void f_gettabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) } // restore previous notion of curwin - restore_win(oldcurwin, oldtabpage, true); + restore_win(&switchwin, true); } if (!done && argvars[2].v_type != VAR_UNKNOWN) { @@ -4434,6 +4462,12 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr) #if defined(BSD) && !defined(__APPLE__) "bsd", #endif +#ifdef __linux__ + "linux", +#endif +#ifdef SUN_SYSTEM + "sun", +#endif #ifdef UNIX "unix", #endif @@ -5882,22 +5916,20 @@ static void f_line(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[1].v_type != VAR_UNKNOWN) { tabpage_T *tp; - win_T *save_curwin; - tabpage_T *save_curtab; // use window specified in the second argument win_T *wp = win_id2wp_tp(&argvars[1], &tp); if (wp != NULL && tp != NULL) { - if (switch_win_noblock(&save_curwin, &save_curtab, wp, tp, true) - == OK) { + switchwin_T switchwin; + if (switch_win_noblock(&switchwin, wp, tp, true) == OK) { check_cursor(); - fp = var2fpos(&argvars[0], true, &fnum); + fp = var2fpos(&argvars[0], true, &fnum, false); } - restore_win_noblock(save_curwin, save_curtab, true); + restore_win_noblock(&switchwin, true); } } else { // use current window - fp = var2fpos(&argvars[0], true, &fnum); + fp = var2fpos(&argvars[0], true, &fnum, false); } if (fp != NULL) { @@ -6814,12 +6846,23 @@ static void f_or(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_pathshorten(typval_T *argvars, typval_T *rettv, FunPtr fptr) { + int trim_len = 1; + + if (argvars[1].v_type != VAR_UNKNOWN) { + trim_len = (int)tv_get_number(&argvars[1]); + if (trim_len < 1) { + trim_len = 1; + } + } + rettv->v_type = VAR_STRING; - const char *const s = tv_get_string_chk(&argvars[0]); - if (!s) { - return; + const char_u *p = (char_u *)tv_get_string_chk(&argvars[0]); + if (p == NULL) { + rettv->vval.v_string = NULL; + } else { + rettv->vval.v_string = vim_strsave(p); + shorten_dir_len(rettv->vval.v_string, trim_len); } - rettv->vval.v_string = shorten_dir((char_u *)xstrdup(s)); } /* @@ -6989,6 +7032,162 @@ static void f_py3eval(typval_T *argvars, typval_T *rettv, FunPtr fptr) script_host_eval("python3", argvars, rettv); } +static void init_srand(uint32_t *const x) + FUNC_ATTR_NONNULL_ALL +{ +#ifndef MSWIN + static int dev_urandom_state = NOTDONE; // FAIL or OK once tried + + if (dev_urandom_state != FAIL) { + const int fd = os_open("/dev/urandom", O_RDONLY, 0); + struct { + union { + uint32_t number; + char bytes[sizeof(uint32_t)]; + } contents; + } buf; + + // Attempt reading /dev/urandom. + if (fd == -1) { + dev_urandom_state = FAIL; + } else { + buf.contents.number = 0; + if (read(fd, buf.contents.bytes, sizeof(uint32_t)) != sizeof(uint32_t)) { + dev_urandom_state = FAIL; + } else { + dev_urandom_state = OK; + *x = buf.contents.number; + } + os_close(fd); + } + } + if (dev_urandom_state != OK) { + // Reading /dev/urandom doesn't work, fall back to time(). +#endif + *x = time(NULL); +#ifndef MSWIN + } +#endif +} + +static inline uint32_t splitmix32(uint32_t *const x) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE +{ + uint32_t z = (*x += 0x9e3779b9); + z = (z ^ (z >> 16)) * 0x85ebca6b; + z = (z ^ (z >> 13)) * 0xc2b2ae35; + return z ^ (z >> 16); +} + +static inline uint32_t shuffle_xoshiro128starstar(uint32_t *const x, uint32_t *const y, + uint32_t *const z, uint32_t *const w) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE +{ +#define ROTL(x, k) ((x << k) | (x >> (32 - k))) + const uint32_t result = ROTL(*y * 5, 7) * 9; + const uint32_t t = *y << 9; + *z ^= *x; + *w ^= *y; + *y ^= *z; + *x ^= *w; + *z ^= t; + *w = ROTL(*w, 11); +#undef ROTL + return result; +} + +/// "rand()" function +static void f_rand(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + uint32_t result; + + if (argvars[0].v_type == VAR_UNKNOWN) { + static uint32_t gx, gy, gz, gw; + static bool initialized = false; + + // When no argument is given use the global seed list. + if (!initialized) { + // Initialize the global seed list. + uint32_t x; + init_srand(&x); + + gx = splitmix32(&x); + gy = splitmix32(&x); + gz = splitmix32(&x); + gw = splitmix32(&x); + initialized = true; + } + + result = shuffle_xoshiro128starstar(&gx, &gy, &gz, &gw); + } else if (argvars[0].v_type == VAR_LIST) { + list_T *const l = argvars[0].vval.v_list; + if (tv_list_len(l) != 4) { + goto theend; + } + + typval_T *const tvx = TV_LIST_ITEM_TV(tv_list_find(l, 0L)); + typval_T *const tvy = TV_LIST_ITEM_TV(tv_list_find(l, 1L)); + typval_T *const tvz = TV_LIST_ITEM_TV(tv_list_find(l, 2L)); + typval_T *const tvw = TV_LIST_ITEM_TV(tv_list_find(l, 3L)); + if (tvx->v_type != VAR_NUMBER) { + goto theend; + } + if (tvy->v_type != VAR_NUMBER) { + goto theend; + } + if (tvz->v_type != VAR_NUMBER) { + goto theend; + } + if (tvw->v_type != VAR_NUMBER) { + goto theend; + } + uint32_t x = tvx->vval.v_number; + uint32_t y = tvy->vval.v_number; + uint32_t z = tvz->vval.v_number; + uint32_t w = tvw->vval.v_number; + + result = shuffle_xoshiro128starstar(&x, &y, &z, &w); + + tvx->vval.v_number = (varnumber_T)x; + tvy->vval.v_number = (varnumber_T)y; + tvz->vval.v_number = (varnumber_T)z; + tvw->vval.v_number = (varnumber_T)w; + } else { + goto theend; + } + + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = (varnumber_T)result; + return; + +theend: + semsg(_(e_invarg2), tv_get_string(&argvars[0])); + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = -1; +} + +/// "srand()" function +static void f_srand(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + uint32_t x = 0; + + tv_list_alloc_ret(rettv, 4); + if (argvars[0].v_type == VAR_UNKNOWN) { + init_srand(&x); + } else { + bool error = false; + x = tv_get_number_chk(&argvars[0], &error); + if (error) { + return; + } + } + + tv_list_append_number(rettv->vval.v_list, (varnumber_T)splitmix32(&x)); + tv_list_append_number(rettv->vval.v_list, (varnumber_T)splitmix32(&x)); + tv_list_append_number(rettv->vval.v_list, (varnumber_T)splitmix32(&x)); + tv_list_append_number(rettv->vval.v_list, (varnumber_T)splitmix32(&x)); +} + /// "perleval()" function static void f_perleval(typval_T *argvars, typval_T *rettv, FunPtr fptr) { @@ -8942,6 +9141,49 @@ static void f_setbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } +/// Set the cursor or mark position. +/// If 'charpos' is TRUE, then use the column number as a character offet. +/// Otherwise use the column number as a byte offset. +static void set_position(typval_T *argvars, typval_T *rettv, bool charpos) +{ + pos_T pos; + int fnum; + colnr_T curswant = -1; + + rettv->vval.v_number = -1; + const char *const name = tv_get_string_chk(argvars); + if (name != NULL) { + if (list2fpos(&argvars[1], &pos, &fnum, &curswant, charpos) == OK) { + if (pos.col != MAXCOL && --pos.col < 0) { + pos.col = 0; + } + if (name[0] == '.' && name[1] == NUL) { + // set cursor; "fnum" is ignored + curwin->w_cursor = pos; + if (curswant >= 0) { + curwin->w_curswant = curswant - 1; + curwin->w_set_curswant = false; + } + check_cursor(); + rettv->vval.v_number = 0; + } else if (name[0] == '\'' && name[1] != NUL && name[2] == NUL) { + // set mark + if (setmark_pos((uint8_t)name[1], &pos, fnum) == OK) { + rettv->vval.v_number = 0; + } + } else { + emsg(_(e_invarg)); + } + } + } +} + +/// "setcharpos()" function +static void f_setcharpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + set_position(argvars, rettv, true); +} + static void f_setcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr) { dict_T *d; @@ -8984,6 +9226,12 @@ static void f_setcmdpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } +/// "setcursorcharpos" function +static void f_setcursorcharpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + set_cursorpos(argvars, rettv, true); +} + /// "setenv()" function static void f_setenv(typval_T *argvars, typval_T *rettv, FunPtr fptr) { @@ -9240,41 +9488,10 @@ static void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "setpos()" function - */ +/// "setpos()" function static void f_setpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - pos_T pos; - int fnum; - colnr_T curswant = -1; - - rettv->vval.v_number = -1; - const char *const name = tv_get_string_chk(argvars); - if (name != NULL) { - if (list2fpos(&argvars[1], &pos, &fnum, &curswant) == OK) { - if (pos.col != MAXCOL && --pos.col < 0) { - pos.col = 0; - } - if (name[0] == '.' && name[1] == NUL) { - // set cursor; "fnum" is ignored - curwin->w_cursor = pos; - if (curswant >= 0) { - curwin->w_curswant = curswant - 1; - curwin->w_set_curswant = false; - } - check_cursor(); - rettv->vval.v_number = 0; - } else if (name[0] == '\'' && name[1] != NUL && name[2] == NUL) { - // set mark - if (setmark_pos((uint8_t)name[1], &pos, fnum) == OK) { - rettv->vval.v_number = 0; - } - } else { - emsg(_(e_invarg)); - } - } - } + set_position(argvars, rettv, false); } /* @@ -11957,7 +12174,7 @@ static void f_virtcol(typval_T *argvars, typval_T *rettv, FunPtr fptr) pos_T *fp; int fnum = curbuf->b_fnum; - fp = var2fpos(&argvars[0], FALSE, &fnum); + fp = var2fpos(&argvars[0], false, &fnum, false); if (fp != NULL && fp->lnum <= curbuf->b_ml.ml_line_count && fnum == curbuf->b_fnum) { // Limit the column to a valid value, getvvcol() doesn't check. diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 42ac1839e6..6f8b032d41 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -3091,7 +3091,7 @@ linenr_T tv_get_lnum(const typval_T *const tv) linenr_T lnum = (linenr_T)tv_get_number_chk(tv, NULL); if (lnum == 0) { // No valid number, try using same function as line() does. int fnum; - pos_T *const fp = var2fpos(tv, true, &fnum); + pos_T *const fp = var2fpos(tv, true, &fnum, false); if (fp != NULL) { lnum = fp->lnum; } @@ -3205,8 +3205,9 @@ const char *tv_get_string_buf_chk(const typval_T *const tv, char *const buf) case VAR_BLOB: case VAR_UNKNOWN: emsg(_(str_errors[tv->v_type])); - return false; + return NULL; } + abort(); return NULL; } diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index e6d63d08a7..81fce3565a 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -1366,7 +1366,7 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char_u *cmd, ui_cursor_goto(Rows - 1, 0); if (do_out) { - if (u_save((line2), (linenr_T)(line2 + 1)) == FAIL) { + if (u_save(line2, (linenr_T)(line2 + 1)) == FAIL) { xfree(cmd_buf); goto error; } @@ -3055,7 +3055,7 @@ void ex_append(exarg_T *eap) // it is the same as "start" -- Acevedo if (!cmdmod.lockmarks) { curbuf->b_op_start.lnum - = (eap->line2 < curbuf->b_ml.ml_line_count) ? eap->line2 + 1 : curbuf->b_ml.ml_line_count; + = (eap->line2 < curbuf->b_ml.ml_line_count) ? eap->line2 + 1 : curbuf->b_ml.ml_line_count; if (eap->cmdidx != CMD_append) { curbuf->b_op_start.lnum--; } @@ -3652,8 +3652,14 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, bool do_buf_event, handle // We do it here once to avoid it to be replaced over and over again. // But don't do it when it starts with "\=", then it's an expression. assert(sub != NULL); + + bool sub_needs_free = false; if (!(sub[0] == '\\' && sub[1] == '=')) { + char_u *source = sub; sub = regtilde(sub, p_magic); + // When previewing, the new pattern allocated by regtilde() needs to be freed + // in this function because it will not be used or freed by regtilde() later. + sub_needs_free = preview && sub != source; } // Check for a match on each line. @@ -4450,6 +4456,10 @@ skip: kv_destroy(preview_lines.subresults); + if (sub_needs_free) { + xfree(sub); + } + return preview_buf; #undef ADJUST_SUB_FIRSTLNUM #undef PUSH_PREVIEW_LINES @@ -6131,12 +6141,14 @@ char_u *skip_vimgrep_pat(char_u *p, char_u **s, int *flags) p++; // Find the flags - while (*p == 'g' || *p == 'j') { + while (*p == 'g' || *p == 'j' || *p == 'f') { if (flags != NULL) { if (*p == 'g') { *flags |= VGR_GLOBAL; - } else { + } else if (*p == 'j') { *flags |= VGR_NOJUMP; + } else { + *flags |= VGR_FUZZY; } } p++; diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 07fbf1c61a..d884838136 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -7835,13 +7835,11 @@ void post_chdir(CdScope scope, bool trigger_dirchanged) /// @return true if the directory is successfully changed. bool changedir_func(char_u *new_dir, CdScope scope) { - char_u *pdir = NULL; - bool retval = false; - if (new_dir == NULL || allbuf_locked()) { return false; } + char_u *pdir = NULL; // ":cd -": Change to previous directory if (STRCMP(new_dir, "-") == 0) { pdir = get_prevdir(scope); @@ -7870,32 +7868,30 @@ bool changedir_func(char_u *new_dir, CdScope scope) new_dir = NameBuff; } - bool dir_differs = new_dir == NULL || pdir == NULL - || pathcmp((char *)pdir, (char *)new_dir, -1) != 0; - if (new_dir != NULL && (!dir_differs || vim_chdir(new_dir) == 0)) { - char_u **pp; - - switch (scope) { - case kCdScopeTabpage: - pp = &curtab->tp_prevdir; - break; - case kCdScopeWindow: - pp = &curwin->w_prevdir; - break; - default: - pp = &prev_dir; - } - xfree(*pp); - *pp = pdir; - - post_chdir(scope, dir_differs); - retval = true; - } else { + bool dir_differs = pdir == NULL || pathcmp((char *)pdir, (char *)new_dir, -1) != 0; + if (dir_differs && vim_chdir(new_dir) != 0) { emsg(_(e_failed)); xfree(pdir); + return false; } - return retval; + char_u **pp; + switch (scope) { + case kCdScopeTabpage: + pp = &curtab->tp_prevdir; + break; + case kCdScopeWindow: + pp = &curwin->w_prevdir; + break; + default: + pp = &prev_dir; + } + xfree(*pp); + *pp = pdir; + + post_chdir(scope, dir_differs); + + return true; } /// ":cd", ":tcd", ":lcd", ":chdir", "tchdir" and ":lchdir". @@ -8143,6 +8139,7 @@ static void ex_put(exarg_T *eap) eap->forceit = TRUE; } curwin->w_cursor.lnum = eap->line2; + check_cursor_col(); do_put(eap->regname, NULL, eap->forceit ? BACKWARD : FORWARD, 1, PUT_LINE|PUT_CURSLINE); } diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c index b1c59a607c..851828afcf 100644 --- a/src/nvim/ex_eval.c +++ b/src/nvim/ex_eval.c @@ -599,7 +599,7 @@ static void catch_exception(except_T *excp) { excp->caught = caught_stack; caught_stack = excp; - set_vim_var_string(VV_EXCEPTION, (char *)excp->value, -1); + set_vim_var_string(VV_EXCEPTION, excp->value, -1); if (*excp->throw_name != NUL) { if (excp->throw_lnum != 0) { vim_snprintf((char *)IObuff, IOSIZE, _("%s, line %" PRId64), @@ -650,7 +650,7 @@ static void finish_exception(except_T *excp) } caught_stack = caught_stack->caught; if (caught_stack != NULL) { - set_vim_var_string(VV_EXCEPTION, (char *)caught_stack->value, -1); + set_vim_var_string(VV_EXCEPTION, caught_stack->value, -1); if (*caught_stack->throw_name != NUL) { if (caught_stack->throw_lnum != 0) { vim_snprintf((char *)IObuff, IOSIZE, @@ -733,7 +733,7 @@ static void report_pending(int action, int pending, void *value) vim_snprintf((char *)IObuff, IOSIZE, mesg, _("Exception")); mesg = (char *)concat_str(IObuff, (char_u *)": %s"); - s = (char *)((except_T *)value)->value; + s = ((except_T *)value)->value; } else if ((pending & CSTP_ERROR) && (pending & CSTP_INTERRUPT)) { s = _("Error and interrupt"); } else if (pending & CSTP_ERROR) { @@ -1620,7 +1620,7 @@ void ex_endtry(exarg_T *eap) // the finally clause. The latter case need not be tested since then // anything pending has already been discarded. bool skip = did_emsg || got_int || current_exception - || !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE); + || !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE); if (!(cstack->cs_flags[cstack->cs_idx] & CSF_TRY)) { eap->errmsg = get_end_emsg(cstack); diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 3bb5d37212..f81f49a174 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -311,7 +311,7 @@ static char_u *get_healthcheck_names(expand_T *xp, int idx) healthchecks.last_gen = last_prompt_id; } return idx < - (int)healthchecks.names.ga_len ? ((char_u **)(healthchecks.names.ga_data))[idx] : NULL; + healthchecks.names.ga_len ? ((char_u **)(healthchecks.names.ga_data))[idx] : NULL; } /// Transform healthcheck file path into it's name. diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c index b4becb3066..25dbf680de 100644 --- a/src/nvim/file_search.c +++ b/src/nvim/file_search.c @@ -483,8 +483,14 @@ void *vim_findfile_init(char_u *path, char_u *filename, char_u *stopdirs, int le int len = 0; if (p > search_ctx->ffsc_fix_path) { + // do not add '..' to the path and start upwards searching len = (int)(p - search_ctx->ffsc_fix_path) - 1; - STRNCAT(ff_expand_buffer, search_ctx->ffsc_fix_path, len); + if ((len >= 2 && STRNCMP(search_ctx->ffsc_fix_path, "..", 2) == 0) + && (len == 2 || search_ctx->ffsc_fix_path[2] == PATHSEP)) { + xfree(buf); + goto error_return; + } + STRLCAT(ff_expand_buffer, search_ctx->ffsc_fix_path, eb_len + (size_t)len + 1); add_pathsep((char *)ff_expand_buffer); } else { len = (int)STRLEN(search_ctx->ffsc_fix_path); diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index f6d37adf89..f28ee1bfcb 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -3863,7 +3863,7 @@ void msg_add_lines(int insert_space, long lnum, off_T nchars) *p++ = ' '; } if (shortmess(SHM_LINES)) { - vim_snprintf((char *)p, IOSIZE - (p - IObuff), "%" PRId64 "L, %" PRId64 "C", + vim_snprintf((char *)p, IOSIZE - (p - IObuff), "%" PRId64 "L, %" PRId64 "B", (int64_t)lnum, (int64_t)nchars); } else { vim_snprintf((char *)p, IOSIZE - (p - IObuff), @@ -3871,7 +3871,7 @@ void msg_add_lines(int insert_space, long lnum, off_T nchars) (int64_t)lnum); p += STRLEN(p); vim_snprintf((char *)p, IOSIZE - (p - IObuff), - NGETTEXT("%" PRId64 " character", "%" PRId64 " characters", nchars), + NGETTEXT("%" PRId64 " byte", "%" PRId64 " bytes", nchars), (int64_t)nchars); } } diff --git a/src/nvim/generators/gen_keysets.lua b/src/nvim/generators/gen_keysets.lua index 01d8c1d357..633c5da184 100644 --- a/src/nvim/generators/gen_keysets.lua +++ b/src/nvim/generators/gen_keysets.lua @@ -27,7 +27,8 @@ local defspipe = io.open(defs_file, 'wb') local keysets = require'api.keysets' local keywords = { - register = true, + register = true; + default = true; } local function sanitize(key) diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index d3641032ab..5d8a8ddbfe 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -2304,6 +2304,10 @@ static int vgetorpeek(bool advance) c = ESC; } tc = c; + + // no chars to block abbreviations for + typebuf.tb_no_abbr_cnt = 0; + break; } diff --git a/src/nvim/getchar.h b/src/nvim/getchar.h index be10e150e5..f24a4e7c7c 100644 --- a/src/nvim/getchar.h +++ b/src/nvim/getchar.h @@ -55,7 +55,7 @@ struct map_arguments { char_u *orig_rhs; /// The original text of the {rhs}. size_t orig_rhs_len; - char *desc; /// map escription + char *desc; /// map description }; typedef struct map_arguments MapArguments; #define MAP_ARGUMENTS_INIT { false, false, false, false, false, false, false, \ diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 041b60d838..f6fbe98ff0 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -979,6 +979,7 @@ EXTERN char e_invalidreg[] INIT(= N_("E850: Invalid register name")); EXTERN char e_dirnotf[] INIT(= N_("E919: Directory not found in '%s': \"%s\"")); EXTERN char e_au_recursive[] INIT(= N_("E952: Autocommand caused recursive behavior")); EXTERN char e_autocmd_close[] INIT(= N_("E813: Cannot close autocmd window")); +EXTERN char e_listarg[] INIT(= N_("E686: Argument of %s must be a List")); EXTERN char e_unsupportedoption[] INIT(= N_("E519: Option not supported")); EXTERN char e_fnametoolong[] INIT(= N_("E856: Filename too long")); EXTERN char e_float_as_string[] INIT(= N_("E806: using Float as a String")); diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c index 3050ca02de..87c090e594 100644 --- a/src/nvim/highlight.c +++ b/src/nvim/highlight.c @@ -144,13 +144,19 @@ int hl_get_syn_attr(int ns_id, int idx, HlAttrs at_en) } } -void ns_hl_def(NS ns_id, int hl_id, HlAttrs attrs, int link_id) +void ns_hl_def(NS ns_id, int hl_id, HlAttrs attrs, int link_id, Dict(highlight) *dict) { - DecorProvider *p = get_decor_provider(ns_id, true); if ((attrs.rgb_ae_attr & HL_DEFAULT) && map_has(ColorKey, ColorItem)(&ns_hl, ColorKey(ns_id, hl_id))) { return; } + if (ns_id == 0) { + assert(dict); + // set in global (':highlight') namespace + set_hl_group(hl_id, attrs, dict, link_id); + return; + } + DecorProvider *p = get_decor_provider(ns_id, true); int attr_id = link_id > 0 ? -1 : hl_get_syn_attr(ns_id, hl_id, attrs); ColorItem it = { .attr_id = attr_id, .link_id = link_id, @@ -192,23 +198,17 @@ int ns_get_hl(NS ns_id, int hl_id, bool link, bool nodefault) int tmp = false; HlAttrs attrs = HLATTRS_INIT; if (ret.type == kObjectTypeDictionary) { - Dictionary dict = ret.data.dictionary; fallback = false; - attrs = dict2hlattrs(dict, true, &it.link_id, &err); - for (size_t i = 0; i < dict.size; i++) { - char *key = dict.items[i].key.data; - Object val = dict.items[i].value; - bool truthy = api_object_to_bool(val, key, false, &err); - - if (strequal(key, "fallback")) { - fallback = truthy; - } else if (strequal(key, "temp")) { - tmp = truthy; + Dict(highlight) dict = { 0 }; + if (api_dict_to_keydict(&dict, KeyDict_highlight_get_field, + ret.data.dictionary, &err)) { + attrs = dict2hlattrs(&dict, true, &it.link_id, &err); + fallback = api_object_to_bool(dict.fallback, "fallback", true, &err); + tmp = api_object_to_bool(dict.fallback, "tmp", false, &err); + if (it.link_id >= 0) { + fallback = true; } } - if (it.link_id >= 0) { - fallback = true; - } } it.attr_id = fallback ? -1 : hl_get_syn_attr((int)ns_id, hl_id, attrs); @@ -796,112 +796,98 @@ Dictionary hlattrs2dict(HlAttrs ae, bool use_rgb) return hl; } -HlAttrs dict2hlattrs(Dictionary dict, bool use_rgb, int *link_id, Error *err) +HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *err) { HlAttrs hlattrs = HLATTRS_INIT; - int32_t fg = -1, bg = -1, ctermfg = -1, ctermbg = -1, sp = -1; int16_t mask = 0; int16_t cterm_mask = 0; bool cterm_mask_provided = false; - for (size_t i = 0; i < dict.size; i++) { - char *key = dict.items[i].key.data; - Object val = dict.items[i].value; - - struct { - const char *name; - int16_t flag; - } flags[] = { - { "bold", HL_BOLD }, - { "standout", HL_STANDOUT }, - { "underline", HL_UNDERLINE }, - { "undercurl", HL_UNDERCURL }, - { "italic", HL_ITALIC }, - { "reverse", HL_INVERSE }, - { "default", HL_DEFAULT }, - { "global", HL_GLOBAL }, - { NULL, 0 }, - }; - - int j; - for (j = 0; flags[j].name; j++) { - if (strequal(flags[j].name, key)) { - if (api_object_to_bool(val, key, false, err)) { - mask = mask | flags[j].flag; - } - break; - } +#define CHECK_FLAG(d, m, name, extra, flag) \ + if (api_object_to_bool(d->name ## extra, #name, false, err)) { \ + m = m | flag; \ } - // Handle cterm attrs - if (strequal(key, "cterm") && val.type == kObjectTypeDictionary) { - cterm_mask_provided = true; - Dictionary cterm_dict = val.data.dictionary; - for (size_t l = 0; l < cterm_dict.size; l++) { - char *cterm_dict_key = cterm_dict.items[l].key.data; - Object cterm_dict_val = cterm_dict.items[l].value; - for (int m = 0; flags[m].name; m++) { - if (strequal(flags[m].name, cterm_dict_key)) { - if (api_object_to_bool(cterm_dict_val, cterm_dict_key, false, - err)) { - cterm_mask |= flags[m].flag; - } - break; - } - } + CHECK_FLAG(dict, mask, bold, , HL_BOLD); + CHECK_FLAG(dict, mask, standout, , HL_STANDOUT); + CHECK_FLAG(dict, mask, underline, , HL_UNDERLINE); + CHECK_FLAG(dict, mask, undercurl, , HL_UNDERCURL); + CHECK_FLAG(dict, mask, italic, , HL_ITALIC); + CHECK_FLAG(dict, mask, reverse, , HL_INVERSE); + CHECK_FLAG(dict, mask, default, _, HL_DEFAULT); + CHECK_FLAG(dict, mask, global, , HL_GLOBAL); + + if (HAS_KEY(dict->fg)) { + fg = object_to_color(dict->fg, "fg", err); + } else if (HAS_KEY(dict->foreground)) { + fg = object_to_color(dict->foreground, "foreground", err); + } + if (ERROR_SET(err)) { + return hlattrs; + } + + if (HAS_KEY(dict->bg)) { + bg = object_to_color(dict->bg, "bg", err); + } else if (HAS_KEY(dict->background)) { + bg = object_to_color(dict->background, "background", err); + } + if (ERROR_SET(err)) { + return hlattrs; + } + + if (HAS_KEY(dict->sp)) { + sp = object_to_color(dict->sp, "sp", err); + } else if (HAS_KEY(dict->special)) { + sp = object_to_color(dict->special, "special", err); + } + if (ERROR_SET(err)) { + return hlattrs; + } + + if (HAS_KEY(dict->link)) { + if (link_id) { + *link_id = object_to_hl_id(dict->link, "link", err); + if (ERROR_SET(err)) { + return hlattrs; } + } else { + api_set_error(err, kErrorTypeValidation, "Invalid Key: 'link'"); } + } - struct { - const char *name; - const char *shortname; - int *dest; - } colors[] = { - { "foreground", "fg", &fg }, - { "background", "bg", &bg }, - { "ctermfg", NULL, &ctermfg }, - { "ctermbg", NULL, &ctermbg }, - { "special", "sp", &sp }, - { NULL, NULL, NULL }, - }; - - int k; - for (k = 0; (!flags[j].name) && colors[k].name; k++) { - if (strequal(colors[k].name, key) || strequal(colors[k].shortname, key)) { - if (val.type == kObjectTypeInteger) { - *colors[k].dest = (int)val.data.integer; - } else if (val.type == kObjectTypeString) { - String str = val.data.string; - // TODO(bfredl): be more fancy with "bg", "fg" etc - if (str.size) { - *colors[k].dest = name_to_color(str.data); - } - } else { - api_set_error(err, kErrorTypeValidation, - "'%s' must be string or integer", key); - } - break; - } + // Handle cterm attrs + if (dict->cterm.type == kObjectTypeDictionary) { + Dict(highlight_cterm) cterm[1] = { 0 }; + if (!api_dict_to_keydict(cterm, KeyDict_highlight_cterm_get_field, + dict->cterm.data.dictionary, err)) { + return hlattrs; } - if (flags[j].name || colors[k].name) { - // handled above - } else if (link_id && strequal(key, "link")) { - if (val.type == kObjectTypeString) { - String str = val.data.string; - *link_id = syn_check_group(str.data, (int)str.size); - } else if (val.type == kObjectTypeInteger) { - // TODO(bfredl): validate range? - *link_id = (int)val.data.integer; - } else { - api_set_error(err, kErrorTypeValidation, - "'link' must be string or integer"); - } + cterm_mask_provided = true; + CHECK_FLAG(cterm, cterm_mask, bold, , HL_BOLD); + CHECK_FLAG(cterm, cterm_mask, standout, , HL_STANDOUT); + CHECK_FLAG(cterm, cterm_mask, underline, , HL_UNDERLINE); + CHECK_FLAG(cterm, cterm_mask, undercurl, , HL_UNDERCURL); + CHECK_FLAG(cterm, cterm_mask, italic, , HL_ITALIC); + CHECK_FLAG(cterm, cterm_mask, reverse, , HL_INVERSE); + + } else if (HAS_KEY(dict->cterm)) { + api_set_error(err, kErrorTypeValidation, "'cterm' must be a Dictionary."); + } +#undef CHECK_FLAG + + if (HAS_KEY(dict->ctermfg)) { + ctermfg = object_to_color(dict->ctermfg, "ctermfg", err); + if (ERROR_SET(err)) { + return hlattrs; } + } + if (HAS_KEY(dict->ctermbg)) { + ctermbg = object_to_color(dict->ctermbg, "ctermbg", err); if (ERROR_SET(err)) { - return hlattrs; // error set, caller should not use retval + return hlattrs; } } @@ -927,6 +913,21 @@ HlAttrs dict2hlattrs(Dictionary dict, bool use_rgb, int *link_id, Error *err) return hlattrs; } + +int object_to_color(Object val, char *key, Error *err) +{ + if (val.type == kObjectTypeInteger) { + return (int)val.data.integer; + } else if (val.type == kObjectTypeString) { + String str = val.data.string; + // TODO(bfredl): be more fancy with "bg", "fg" etc + return str.size ? name_to_color(str.data) : 0; + } else { + api_set_error(err, kErrorTypeValidation, "'%s' must be string or integer", key); + return 0; + } +} + Array hl_inspect(int attr) { Array ret = ARRAY_DICT_INIT; diff --git a/src/nvim/if_cscope.c b/src/nvim/if_cscope.c index daef8db267..9ca01137cf 100644 --- a/src/nvim/if_cscope.c +++ b/src/nvim/if_cscope.c @@ -919,8 +919,8 @@ static int cs_find(exarg_T *eap) /// Common code for cscope find, shared by cs_find() and ex_cstag(). -static bool cs_find_common(char *opt, char *pat, int forceit, int verbose, - bool use_ll, char_u *cmdline) +static bool cs_find_common(char *opt, char *pat, int forceit, int verbose, bool use_ll, + char_u *cmdline) { char *cmd; int *nummatches; @@ -1594,7 +1594,6 @@ static char *cs_pathcomponents(char *path) char *s = path + strlen(path) - 1; for (int i = 0; i < p_cspc; i++) { while (s > path && *--s != '/') { - continue; } } if ((s > path && *s == '/')) { @@ -1813,7 +1812,6 @@ static int cs_read_prompt(size_t i) static void sig_handler(int s) { // do nothing - return; } #endif diff --git a/src/nvim/indent_c.c b/src/nvim/indent_c.c index eb24e13d24..7f483d02ab 100644 --- a/src/nvim/indent_c.c +++ b/src/nvim/indent_c.c @@ -41,9 +41,7 @@ static pos_T *ind_find_start_comment(void) // XXX pos_T *find_start_comment(int ind_maxcomment) // XXX { - pos_T *pos; - char_u *line; - char_u *p; + pos_T *pos; int64_t cur_maxcomment = ind_maxcomment; for (;; ) { @@ -55,11 +53,9 @@ pos_T *find_start_comment(int ind_maxcomment) // XXX * Check if the comment start we found is inside a string. * If it is then restrict the search to below this line and try again. */ - line = ml_get(pos->lnum); - for (p = line; *p && (colnr_T)(p - line) < pos->col; ++p) - p = skip_string(p); - if ((colnr_T)(p - line) <= pos->col) + if (!is_pos_in_string(ml_get(pos->lnum), pos->col)) { break; + } cur_maxcomment = curwin->w_cursor.lnum - pos->lnum - 1; if (cur_maxcomment <= 0) { pos = NULL; @@ -110,8 +106,6 @@ static pos_T *ind_find_start_CORS(linenr_T *is_raw) static pos_T *find_start_rawstring(int ind_maxcomment) // XXX { pos_T *pos; - char_u *line; - char_u *p; long cur_maxcomment = ind_maxcomment; for (;;) @@ -124,11 +118,9 @@ static pos_T *find_start_rawstring(int ind_maxcomment) // XXX * Check if the raw string start we found is inside a string. * If it is then restrict the search to below this line and try again. */ - line = ml_get(pos->lnum); - for (p = line; *p && (colnr_T)(p - line) < pos->col; ++p) - p = skip_string(p); - if ((colnr_T)(p - line) <= pos->col) - break; + if (!is_pos_in_string(ml_get(pos->lnum), pos->col)) { + break; + } cur_maxcomment = curwin->w_cursor.lnum - pos->lnum - 1; if (cur_maxcomment <= 0) { @@ -143,7 +135,7 @@ static pos_T *find_start_rawstring(int ind_maxcomment) // XXX * Skip to the end of a "string" and a 'c' character. * If there is no string or character, return argument unmodified. */ -static char_u *skip_string(char_u *p) +static const char_u *skip_string(const char_u *p) { int i; @@ -178,24 +170,24 @@ static char_u *skip_string(char_u *p) continue; // continue for another string } } else if (p[0] == 'R' && p[1] == '"') { - // Raw string: R"[delim](...)[delim]" - char_u *delim = p + 2; - char_u *paren = vim_strchr(delim, '('); - - if (paren != NULL) { - const ptrdiff_t delim_len = paren - delim; - - for (p += 3; *p; ++p) - if (p[0] == ')' && STRNCMP(p + 1, delim, delim_len) == 0 - && p[delim_len + 1] == '"') - { - p += delim_len + 1; - break; - } - if (p[0] == '"') { - continue; // continue for another string - } + // Raw string: R"[delim](...)[delim]" + const char_u *delim = p + 2; + const char_u *paren = vim_strchr(delim, '('); + + if (paren != NULL) { + const ptrdiff_t delim_len = paren - delim; + + for (p += 3; *p; p++) { + if (p[0] == ')' && STRNCMP(p + 1, delim, delim_len) == 0 + && p[delim_len + 1] == '"') { + p += delim_len + 1; + break; + } + } + if (p[0] == '"') { + continue; // continue for another string } + } } break; // no string found } @@ -205,6 +197,16 @@ static char_u *skip_string(char_u *p) return p; } +/// @returns true if "line[col]" is inside a C string. +int is_pos_in_string(const char_u *line, colnr_T col) +{ + const char_u *p; + + for (p = line; *p && (colnr_T)(p - line) < col; p++) { + p = skip_string(p); + } + return !((colnr_T)(p - line) <= col); +} /* * Functions for C-indenting. @@ -218,7 +220,7 @@ static char_u *skip_string(char_u *p) /* * Return true if the string "line" starts with a word from 'cinwords'. */ -bool cin_is_cinword(char_u *line) +bool cin_is_cinword(const char_u *line) { bool retval = false; @@ -246,10 +248,10 @@ bool cin_is_cinword(char_u *line) * Skip over white space and C comments within the line. * Also skip over Perl/shell comments if desired. */ -static char_u *cin_skipcomment(char_u *s) +static const char_u *cin_skipcomment(const char_u *s) { while (*s) { - char_u *prev_s = s; + const char_u *prev_s = s; s = skipwhite(s); @@ -283,7 +285,7 @@ static char_u *cin_skipcomment(char_u *s) * Return TRUE if there is no code at *s. White space and comments are * not considered code. */ -static int cin_nocode(char_u *s) +static int cin_nocode(const char_u *s) { return *cin_skipcomment(s) == NUL; } @@ -312,9 +314,9 @@ static pos_T *find_line_comment(void) // XXX } /// Checks if `text` starts with "key:". -static bool cin_has_js_key(char_u *text) +static bool cin_has_js_key(const char_u *text) { - char_u *s = skipwhite(text); + const char_u *s = skipwhite(text); char_u quote = 0; if (*s == '\'' || *s == '"') { @@ -341,7 +343,7 @@ static bool cin_has_js_key(char_u *text) /// Checks if string matches "label:"; move to character after ':' if true. /// "*s" must point to the start of the label, if there is one. -static bool cin_islabel_skip(char_u **s) +static bool cin_islabel_skip(const char_u **s) FUNC_ATTR_NONNULL_ALL { if (!vim_isIDc(**s)) { // need at least one ID character @@ -361,7 +363,7 @@ static bool cin_islabel_skip(char_u **s) // Note: curwin->w_cursor must be where we are looking for the label. bool cin_islabel(void) // XXX { - char_u *s = cin_skipcomment(get_cursor_line_ptr()); + const char_u *s = cin_skipcomment(get_cursor_line_ptr()); // Exclude "default" from labels, since it should be indented // like a switch label. Same for C++ scope declarations. @@ -380,8 +382,8 @@ bool cin_islabel(void) // XXX * label. */ pos_T cursor_save; - pos_T *trypos; - char_u *line; + pos_T *trypos; + const char_u *line; cursor_save = curwin->w_cursor; while (curwin->w_cursor.lnum > 1) { @@ -424,8 +426,8 @@ bool cin_islabel(void) // XXX */ static int cin_isinit(void) { - char_u *s; - static char *skip[] = {"static", "public", "protected", "private"}; + const char_u *s; + static char *skip[] = { "static", "public", "protected", "private" }; s = cin_skipcomment(get_cursor_line_ptr()); @@ -460,7 +462,7 @@ static int cin_isinit(void) * Recognize a switch label: "case .*:" or "default:". */ bool cin_iscase( - char_u *s, + const char_u *s, bool strict // Allow relaxed check of case statement for JS ) { @@ -503,7 +505,7 @@ bool cin_iscase( /* * Recognize a "default" switch label. */ -static int cin_isdefault(char_u *s) +static int cin_isdefault(const char_u *s) { return STRNCMP(s, "default", 7) == 0 && *(s = cin_skipcomment(s + 7)) == ':' @@ -513,7 +515,7 @@ static int cin_isdefault(char_u *s) /* * Recognize a "public/private/protected" scope declaration label. */ -bool cin_isscopedecl(char_u *s) +bool cin_isscopedecl(const char_u *s) { int i; @@ -534,9 +536,9 @@ bool cin_isscopedecl(char_u *s) #define FIND_NAMESPACE_LIM 20 // Recognize a "namespace" scope declaration. -static bool cin_is_cpp_namespace(char_u *s) +static bool cin_is_cpp_namespace(const char_u *s) { - char_u *p; + const char_u *p; bool has_name = false; bool has_name_start = false; @@ -581,7 +583,7 @@ static bool cin_is_cpp_namespace(char_u *s) * case 234: a = b; * ^ */ -static char_u *after_label(char_u *l) +static const char_u *after_label(const char_u *l) { for (; *l; ++l) { if (*l == ':') { @@ -608,10 +610,10 @@ static char_u *after_label(char_u *l) */ static int get_indent_nolabel(linenr_T lnum) // XXX { - char_u *l; + const char_u *l; pos_T fp; colnr_T col; - char_u *p; + const char_u *p; l = ml_get(lnum); p = after_label(l); @@ -630,9 +632,9 @@ static int get_indent_nolabel(linenr_T lnum) // XXX * label: if (asdf && asdfasdf) * ^ */ -static int skip_label(linenr_T lnum, char_u **pp) +static int skip_label(linenr_T lnum, const char_u **pp) { - char_u *l; + const char_u *l; int amount; pos_T cursor_save; @@ -713,8 +715,8 @@ static int cin_first_id_amount(void) */ static int cin_get_equal_amount(linenr_T lnum) { - char_u *line; - char_u *s; + const char_u *line; + const char_u *s; colnr_T col; pos_T fp; @@ -752,7 +754,7 @@ static int cin_get_equal_amount(linenr_T lnum) /* * Recognize a preprocessor statement: Any line that starts with '#'. */ -static int cin_ispreproc(char_u *s) +static int cin_ispreproc(const char_u *s) { if (*skipwhite(s) == '#') return TRUE; @@ -763,9 +765,9 @@ static int cin_ispreproc(char_u *s) /// continuation line of a preprocessor statement. Decrease "*lnump" to the /// start and return the line in "*pp". /// Put the amount of indent in "*amount". -static int cin_ispreproc_cont(char_u **pp, linenr_T *lnump, int *amount) +static int cin_ispreproc_cont(const char_u **pp, linenr_T *lnump, int *amount) { - char_u *line = *pp; + const char_u *line = *pp; linenr_T lnum = *lnump; int retval = false; int candidate_amount = *amount; @@ -799,7 +801,7 @@ static int cin_ispreproc_cont(char_u **pp, linenr_T *lnump, int *amount) /* * Recognize the start of a C or C++ comment. */ -static int cin_iscomment(char_u *p) +static int cin_iscomment(const char_u *p) { return p[0] == '/' && (p[1] == '*' || p[1] == '/'); } @@ -807,7 +809,7 @@ static int cin_iscomment(char_u *p) /* * Recognize the start of a "//" comment. */ -static int cin_islinecomment(char_u *p) +static int cin_islinecomment(const char_u *p) { return p[0] == '/' && p[1] == '/'; } @@ -822,8 +824,8 @@ static int cin_islinecomment(char_u *p) * both apply in order to determine initializations). */ static char_u -cin_isterminated ( - char_u *s, +cin_isterminated( + const char_u *s, int incl_open, // include '{' at the end as terminator int incl_comma // recognize a trailing comma ) @@ -872,9 +874,9 @@ cin_isterminated ( /// lines here. /// @param[in] first_lnum Where to start looking. /// @param[in] min_lnum The line before which we will not be looking. -static int cin_isfuncdecl(char_u **sp, linenr_T first_lnum, linenr_T min_lnum) +static int cin_isfuncdecl(const char_u **sp, linenr_T first_lnum, linenr_T min_lnum) { - char_u *s; + const char_u *s; linenr_T lnum = first_lnum; linenr_T save_lnum = curwin->w_cursor.lnum; int retval = false; @@ -975,12 +977,12 @@ done: return retval; } -static int cin_isif(char_u *p) +static int cin_isif(const char_u *p) { return STRNCMP(p, "if", 2) == 0 && !vim_isIDc(p[2]); } -static int cin_iselse(char_u *p) +static int cin_iselse(const char_u *p) { if (*p == '}') { // accept "} else" p = cin_skipcomment(p + 1); @@ -988,7 +990,7 @@ static int cin_iselse(char_u *p) return STRNCMP(p, "else", 4) == 0 && !vim_isIDc(p[4]); } -static int cin_isdo(char_u *p) +static int cin_isdo(const char_u *p) { return STRNCMP(p, "do", 2) == 0 && !vim_isIDc(p[2]); } @@ -998,7 +1000,7 @@ static int cin_isdo(char_u *p) * We only accept a "while (condition) ;", with only white space between the * ')' and ';'. The condition may be spread over several lines. */ -static int cin_iswhileofdo(char_u *p, linenr_T lnum) // XXX +static int cin_iswhileofdo(const char_u *p, linenr_T lnum) // XXX { pos_T cursor_save; pos_T *trypos; @@ -1032,7 +1034,7 @@ static int cin_iswhileofdo(char_u *p, linenr_T lnum) // XXX * Otherwise return !0 and update "*poffset" to point to the place where the * string was found. */ -static int cin_is_if_for_while_before_offset(char_u *line, int *poffset) +static int cin_is_if_for_while_before_offset(const char_u *line, int *poffset) { int offset = *poffset; @@ -1076,10 +1078,10 @@ probablyFound: */ static int cin_iswhileofdo_end(int terminated) { - char_u *line; - char_u *p; - char_u *s; - pos_T *trypos; + const char_u *line; + const char_u *p; + const char_u *s; + pos_T *trypos; int i; if (terminated != ';') { // there must be a ';' at the end @@ -1119,7 +1121,7 @@ static int cin_iswhileofdo_end(int terminated) return FALSE; } -static int cin_isbreak(char_u *p) +static int cin_isbreak(const char_u *p) { return STRNCMP(p, "break", 5) == 0 && !vim_isIDc(p[5]); } @@ -1139,10 +1141,10 @@ static int cin_isbreak(char_u *p) */ static int cin_is_cpp_baseclass(cpp_baseclass_cache_T *cached) { lpos_T *pos = &cached->lpos; // find position - char_u *s; + const char_u *s; int class_or_struct, lookfor_ctor_init, cpp_base_class; linenr_T lnum = curwin->w_cursor.lnum; - char_u *line = get_cursor_line_ptr(); + const char_u *line = get_cursor_line_ptr(); if (pos->lnum <= lnum) { return cached->found; // Use the cached result @@ -1310,10 +1312,10 @@ static int get_baseclass_amount(int col) * white space and comments. Skip strings and comments. * Ignore "ignore" after "find" if it's not NULL. */ -static int cin_ends_in(char_u *s, char_u *find, char_u *ignore) +static int cin_ends_in(const char_u *s, const char_u *find, const char_u *ignore) { - char_u *p = s; - char_u *r; + const char_u *p = s; + const char_u *r; int len = (int)STRLEN(find); while (*p != NUL) { @@ -1334,7 +1336,7 @@ static int cin_ends_in(char_u *s, char_u *find, char_u *ignore) /* * Return TRUE when "s" starts with "word" and then a non-ID character. */ -static int cin_starts_with(char_u *s, char *word) +static int cin_starts_with(const char_u *s, const char *word) { int l = (int)STRLEN(word); @@ -1342,10 +1344,10 @@ static int cin_starts_with(char_u *s, char *word) } /// Recognize a `extern "C"` or `extern "C++"` linkage specifications. -static int cin_is_cpp_extern_c(char_u *s) +static int cin_is_cpp_extern_c(const char_u *s) { - char_u *p; - int has_string_literal = false; + const char_u *p; + int has_string_literal = false; s = cin_skipcomment(s); if (STRNCMP(s, "extern", 6) == 0 && (s[6] == NUL || !vim_iswordc(s[6]))) { @@ -1384,9 +1386,9 @@ static int cin_is_cpp_extern_c(char_u *s) */ static int cin_skip2pos(pos_T *trypos) { - char_u *line; - char_u *p; - char_u *new_p; + const char_u *line; + const char_u *p; + const char_u *new_p; p = line = ml_get(trypos->lnum); while (*p && (colnr_T)(p - line) < trypos->col) { @@ -1529,7 +1531,7 @@ static int corr_ind_maxparen(pos_T *startpos) * Set w_cursor.col to the column number of the last unmatched ')' or '{' in * line "l". "l" must point to the start of the line. */ -static int find_last_paren(char_u *l, int start, int end) +static int find_last_paren(const char_u *l, int start, int end) { int i; int retval = FALSE; @@ -1801,8 +1803,8 @@ int get_c_indent(void) #define BRACE_AT_START 2 // '{' is at start of line #define BRACE_AT_END 3 // '{' is at end of line linenr_T ourscope; - char_u *l; - char_u *look; + const char_u *l; + const char_u *look; char_u terminated; int lookfor; #define LOOKFOR_INITIAL 0 @@ -1906,12 +1908,25 @@ int get_c_indent(void) * If we're inside a "//" comment and there is a "//" comment in a * previous line, lineup with that one. */ - if (cin_islinecomment(theline) - && (trypos = find_line_comment()) != NULL) { // XXX - // find how indented the line beginning the comment is - getvcol(curwin, trypos, &col, NULL, NULL); - amount = col; - goto theend; + if (cin_islinecomment(theline)) { + pos_T linecomment_pos; + + trypos = find_line_comment(); // XXX + if (trypos == NULL && curwin->w_cursor.lnum > 1) { + // There may be a statement before the comment, search from the end + // of the line for a comment start. + linecomment_pos.col = check_linecomment(ml_get(curwin->w_cursor.lnum - 1)); + if (linecomment_pos.col != MAXCOL) { + trypos = &linecomment_pos; + trypos->lnum = curwin->w_cursor.lnum - 1; + } + } + if (trypos != NULL) { + // find how indented the line beginning the comment is + getvcol(curwin, trypos, &col, NULL, NULL); + amount = col; + goto theend; + } } /* * If we're inside a comment and not looking at the start of the @@ -3602,9 +3617,9 @@ laterend: static int find_match(int lookfor, linenr_T ourscope) { - char_u *look; - pos_T *theirscope; - char_u *mightbeif; + const char_u *look; + pos_T *theirscope; + const char_u *mightbeif; int elselevel; int whilelevel; diff --git a/src/nvim/keymap.h b/src/nvim/keymap.h index 42cae0c35e..ae2ec7835e 100644 --- a/src/nvim/keymap.h +++ b/src/nvim/keymap.h @@ -122,8 +122,6 @@ // // Entries must be in the range 0x02-0x7f (see comment at K_SPECIAL). enum key_extra { - KE_NAME = 3, // name of this terminal entry - KE_S_UP = 4, // shift-up KE_S_DOWN = 5, // shift-down diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 5c4d7e3c91..cbfb8364f6 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -682,7 +682,7 @@ int nlua_call(lua_State *lstate) typval_T vim_args[MAX_FUNC_ARGS + 1]; int i = 0; // also used for freeing the variables for (; i < nargs; i++) { - lua_pushvalue(lstate, (int)i+2); + lua_pushvalue(lstate, i+2); if (!nlua_pop_typval(lstate, &vim_args[i])) { api_set_error(&err, kErrorTypeException, "error converting argument %d", i+1); @@ -747,7 +747,7 @@ static int nlua_rpc(lua_State *lstate, bool request) Array args = ARRAY_DICT_INIT; for (int i = 0; i < nargs; i++) { - lua_pushvalue(lstate, (int)i+3); + lua_pushvalue(lstate, i+3); ADD(args, nlua_pop_Object(lstate, false, &err)); if (ERROR_SET(&err)) { api_free_array(args); diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index 731e7d8d36..f5993c3f55 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -87,7 +87,8 @@ function vim._load_package(name) return nil end -table.insert(package.loaders, 1, vim._load_package) +-- Insert vim._load_package after the preloader at position 2 +table.insert(package.loaders, 2, vim._load_package) -- These are for loading runtime modules lazily since they aren't available in -- the nvim binary as specified in executor.c diff --git a/src/nvim/lua/xdiff.c b/src/nvim/lua/xdiff.c index b2e971f9f3..ea7a700e1e 100644 --- a/src/nvim/lua/xdiff.c +++ b/src/nvim/lua/xdiff.c @@ -184,11 +184,11 @@ static NluaXdiffMode process_xdl_diff_opts(lua_State *lstate, xdemitconf_t *cfg, if (strequal("myers", v->data.string.data)) { // default } else if (strequal("minimal", v->data.string.data)) { - cfg->flags |= XDF_NEED_MINIMAL; + params->flags |= XDF_NEED_MINIMAL; } else if (strequal("patience", v->data.string.data)) { - cfg->flags |= XDF_PATIENCE_DIFF; + params->flags |= XDF_PATIENCE_DIFF; } else if (strequal("histogram", v->data.string.data)) { - cfg->flags |= XDF_HISTOGRAM_DIFF; + params->flags |= XDF_HISTOGRAM_DIFF; } else { api_set_error(err, kErrorTypeValidation, "not a valid algorithm"); goto exit_1; diff --git a/src/nvim/macros.h b/src/nvim/macros.h index c2b2c89abf..d5611f587b 100644 --- a/src/nvim/macros.h +++ b/src/nvim/macros.h @@ -105,6 +105,9 @@ #define MB_PTR_BACK(s, p) \ (p -= utf_head_off((char_u *)s, (char_u *)p - 1) + 1) +// MB_CHAR2BYTES(): convert character to bytes and advance pointer to bytes +#define MB_CHAR2BYTES(c, b) ((b) += utf_char2bytes((c), (b))) + #define RESET_BINDING(wp) \ do { \ (wp)->w_p_scb = false; \ diff --git a/src/nvim/message.c b/src/nvim/message.c index e1e253cd2e..93742ccbdb 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -262,7 +262,6 @@ void msg_multiline_attr(const char *s, int attr, bool check_int, bool *need_clea if (*s != NUL) { msg_outtrans_attr((char_u *)s, attr); } - return; } @@ -329,7 +328,7 @@ bool msg_attr_keep(const char *s, int attr, bool keep, bool multiline) } retval = msg_end(); - if (keep && retval && vim_strsize((char_u *)s) < (int)(Rows - cmdline_row - 1) + if (keep && retval && vim_strsize((char_u *)s) < (Rows - cmdline_row - 1) * Columns + sc_col) { set_keep_msg((char *)s, 0); } @@ -356,10 +355,10 @@ char_u *msg_strtrunc(char_u *s, int force) len = vim_strsize(s); if (msg_scrolled != 0) { // Use all the columns. - room = (int)(Rows - msg_row) * Columns - 1; + room = (Rows - msg_row) * Columns - 1; } else { // Use up to 'showcmd' column. - room = (int)(Rows - msg_row - 1) * Columns + sc_col - 1; + room = (Rows - msg_row - 1) * Columns + sc_col - 1; } if (len > room && room > 0) { // may have up to 18 bytes per cell (6 per char, up to two @@ -873,7 +872,7 @@ char_u *msg_may_trunc(bool force, char_u *s) { int room; - room = (int)(Rows - cmdline_row - 1) * Columns + sc_col - 1; + room = (Rows - cmdline_row - 1) * Columns + sc_col - 1; if ((force || (shortmess(SHM_TRUNC) && !exmode_active)) && (int)STRLEN(s) - room > 0) { int size = vim_strsize(s); @@ -1095,6 +1094,10 @@ void wait_return(int redraw) return; } + if (headless_mode && !ui_active()) { + return; + } + /* * When inside vgetc(), we can't wait for a typed character at all. * With the global command (and some others) we only need one return at diff --git a/src/nvim/move.c b/src/nvim/move.c index 67ec19903f..27cc2b341c 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -346,10 +346,10 @@ void update_topline(win_T *wp) */ void update_topline_win(win_T *win) { - win_T *save_curwin; - switch_win(&save_curwin, NULL, win, NULL, true); + switchwin_T switchwin; + switch_win(&switchwin, win, NULL, true); update_topline(curwin); - restore_win(save_curwin, NULL, true); + restore_win(&switchwin, true); } /* diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 28e5d47dbc..225c66aae1 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -1030,7 +1030,7 @@ static int normal_execute(VimState *state, int key) s->need_flushbuf = add_to_showcmd(s->c); - while (normal_get_command_count(s)) { continue; } + while (normal_get_command_count(s)) { } if (s->c == K_EVENT) { // Save the count values so that ca.opcount and ca.count0 are exactly @@ -3098,8 +3098,14 @@ static void nv_gd(oparg_T *oap, int nchar, int thisblock) if ((len = find_ident_under_cursor(&ptr, FIND_IDENT)) == 0 || !find_decl(ptr, len, nchar == 'd', thisblock, SEARCH_START)) { clearopbeep(oap); - } else if ((fdo_flags & FDO_SEARCH) && KeyTyped && oap->op_type == OP_NOP) { - foldOpenCursor(); + } else { + if ((fdo_flags & FDO_SEARCH) && KeyTyped && oap->op_type == OP_NOP) { + foldOpenCursor(); + } + // clear any search statistics + if (messaging() && !msg_silent && !shortmess(SHM_SEARCHCOUNT)) { + clear_cmdline = true; + } } } @@ -4093,7 +4099,7 @@ static void nv_colon(cmdarg_T *cap) if (is_lua) { cmd_result = map_execute_lua(); } else { - // get a command line and execute it + // get a command line and execute it cmd_result = do_cmdline(NULL, is_cmdkey ? getcmdkeycmd : getexline, NULL, cap->oap->op_type != OP_NOP ? DOCMD_KEEPLINE : 0); } @@ -5027,9 +5033,7 @@ static void nv_brackets(cmdarg_T *cap) * identifier "]i" "[i" "]I" "[I" "]^I" "[^I" * define "]d" "[d" "]D" "[D" "]^D" "[^D" */ - if (vim_strchr((char_u *) - "iI\011dD\004", - cap->nchar) != NULL) { + if (vim_strchr((char_u *)"iI\011dD\004", cap->nchar) != NULL) { char_u *ptr; size_t len; @@ -6330,7 +6334,7 @@ static void nv_g_cmd(cmdarg_T *cap) curwin->w_set_curswant = true; break; - case 'M': { + case 'M': oap->motion_type = kMTCharWise; oap->inclusive = false; i = linetabsize(get_cursor_line_ptr()); @@ -6340,8 +6344,7 @@ static void nv_g_cmd(cmdarg_T *cap) coladvance((colnr_T)(i / 2)); } curwin->w_set_curswant = true; - } - break; + break; case '_': /* "g_": to the last non-blank character in the line or <count> lines @@ -6685,9 +6688,8 @@ static void n_opencmd(cmdarg_T *cap) (cap->cmdchar == 'o' ? 1 : 0)) ) && open_line(cap->cmdchar == 'O' ? BACKWARD : FORWARD, - has_format_option(FO_OPEN_COMS) - ? OPENLINE_DO_COM : 0, - 0)) { + has_format_option(FO_OPEN_COMS) ? OPENLINE_DO_COM : 0, + 0, NULL)) { if (win_cursorline_standout(curwin)) { // force redraw of cursorline curwin->w_valid &= ~VALID_CROW; diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 83a7c31991..b8b639265c 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -381,8 +381,8 @@ static void shift_block(oparg_T *oap, int amount) } } for (; ascii_iswhite(*bd.textstart);) { - // TODO: is passing bd.textstart for start of the line OK? - incr = lbr_chartabsize_adv(bd.textstart, &bd.textstart, (bd.start_vcol)); + // TODO(fmoralesc): is passing bd.textstart for start of the line OK? + incr = lbr_chartabsize_adv(bd.textstart, &bd.textstart, bd.start_vcol); total += incr; bd.start_vcol += incr; } @@ -2789,8 +2789,6 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append) curbuf->b_op_end.col = MAXCOL; } } - - return; } // Copy a block range into a register. @@ -3472,6 +3470,9 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) curwin->w_cursor.col -= first_byte_off; } } else { + linenr_T new_lnum = new_cursor.lnum; + size_t len; + // Insert at least one line. When y_type is kMTCharWise, break the first // line in two. for (cnt = 1; cnt <= count; cnt++) { @@ -3488,6 +3489,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) STRCAT(newp, ptr); // insert second line ml_append(lnum, newp, (colnr_T)0, false); + new_lnum++; xfree(newp); oldp = ml_get(lnum); @@ -3503,10 +3505,11 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) } for (; i < y_size; i++) { - if ((y_type != kMTCharWise || i < y_size - 1) - && ml_append(lnum, y_array[i], (colnr_T)0, false) - == FAIL) { - goto error; + if ((y_type != kMTCharWise || i < y_size - 1)) { + if (ml_append(lnum, y_array[i], (colnr_T)0, false) == FAIL) { + goto error; + } + new_lnum++; } lnum++; ++nr_lines; @@ -3556,6 +3559,10 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) extmark_splice(curbuf, (int)new_cursor.lnum-1, col + 1, 0, 0, 0, (int)y_size+1, 0, totsize+2, kExtmarkUndo); } + + if (cnt == 1) { + new_lnum = lnum; + } } error: @@ -3583,11 +3590,12 @@ error: // Put the '] mark on the first byte of the last inserted character. // Correct the length for change in indent. - curbuf->b_op_end.lnum = lnum; - col = (colnr_T)STRLEN(y_array[y_size - 1]) - lendiff; + curbuf->b_op_end.lnum = new_lnum; + len = STRLEN(y_array[y_size - 1]); + col = (colnr_T)len - lendiff; if (col > 1) { curbuf->b_op_end.col = col - 1 - utf_head_off(y_array[y_size - 1], - y_array[y_size - 1] + col - 1); + y_array[y_size - 1] + len - 1); } else { curbuf->b_op_end.col = 0; } @@ -3606,8 +3614,12 @@ error: } curwin->w_cursor.col = 0; } else { - curwin->w_cursor.lnum = lnum; + curwin->w_cursor.lnum = new_lnum; curwin->w_cursor.col = col; + curbuf->b_op_end = curwin->w_cursor; + if (col > 1) { + curbuf->b_op_end.col = col - 1; + } } } else if (y_type == kMTLineWise) { // put cursor on first non-blank in first inserted line @@ -4350,7 +4362,7 @@ void format_lines(linenr_T line_count, int avoid_fex) int leader_len = 0; // leader len of current line int next_leader_len; // leader len of next line char_u *leader_flags = NULL; // flags for leader of current line - char_u *next_leader_flags; // flags for leader of next line + char_u *next_leader_flags = NULL; // flags for leader of next line bool advance = true; int second_indent = -1; // indent for second line (comment aware) bool first_par_line = true; @@ -4467,7 +4479,14 @@ void format_lines(linenr_T line_count, int avoid_fex) leader_len, leader_flags, next_leader_len, next_leader_flags)) { - is_end_par = true; + // Special case: If the next line starts with a line comment + // and this line has a line comment after some text, the + // paragraph doesn't really end. + if (next_leader_flags == NULL + || STRNCMP(next_leader_flags, "://", 3) != 0 + || check_linecomment(get_cursor_line_ptr()) == MAXCOL) { + is_end_par = true; + } } /* diff --git a/src/nvim/option.c b/src/nvim/option.c index 2fb1966cda..b9fed8b378 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -1781,7 +1781,7 @@ static char *illegal_char(char *errbuf, size_t errbuflen, int c) if (errbuf == NULL) { return ""; } - vim_snprintf((char *)errbuf, errbuflen, _("E539: Illegal character <%s>"), + vim_snprintf(errbuf, errbuflen, _("E539: Illegal character <%s>"), (char *)transchar(c)); return errbuf; } @@ -2284,12 +2284,12 @@ static char *set_string_option(const int opt_idx, const char *const value, const *varp = s; char *const saved_oldval = xstrdup(oldval); - char *const saved_oldval_l = (oldval_l != NULL) ? xstrdup((char *)oldval_l) : 0; - char *const saved_oldval_g = (oldval_g != NULL) ? xstrdup((char *)oldval_g) : 0; + char *const saved_oldval_l = (oldval_l != NULL) ? xstrdup(oldval_l) : 0; + char *const saved_oldval_g = (oldval_g != NULL) ? xstrdup(oldval_g) : 0; char *const saved_newval = xstrdup(s); int value_checked = false; - char *const r = did_set_string_option(opt_idx, (char_u **)varp, (int)true, + char *const r = did_set_string_option(opt_idx, (char_u **)varp, true, (char_u *)oldval, NULL, 0, opt_flags, &value_checked); if (r == NULL) { @@ -2777,7 +2777,7 @@ ambw_end: if (!ascii_isdigit(*(s - 1))) { if (errbuf != NULL) { - vim_snprintf((char *)errbuf, errbuflen, + vim_snprintf(errbuf, errbuflen, _("E526: Missing number after <%s>"), transchar_byte(*(s - 1))); errmsg = errbuf; @@ -2968,7 +2968,7 @@ ambw_end: } } else { if (errbuf != NULL) { - vim_snprintf((char *)errbuf, errbuflen, + vim_snprintf(errbuf, errbuflen, _("E535: Illegal character after <%c>"), *--s); errmsg = errbuf; @@ -3178,10 +3178,7 @@ ambw_end: char_u *cp; if (!(*varp)[0] || ((*varp)[0] == '0' && !(*varp)[1])) { - if (curbuf->b_p_vsts_array) { - xfree(curbuf->b_p_vsts_array); - curbuf->b_p_vsts_array = 0; - } + XFREE_CLEAR(curbuf->b_p_vsts_array); } else { for (cp = *varp; *cp; cp++) { if (ascii_isdigit(*cp)) { @@ -3206,10 +3203,7 @@ ambw_end: char_u *cp; if (!(*varp)[0] || ((*varp)[0] == '0' && !(*varp)[1])) { - if (curbuf->b_p_vts_array) { - xfree(curbuf->b_p_vts_array); - curbuf->b_p_vts_array = NULL; - } + XFREE_CLEAR(curbuf->b_p_vts_array); } else { for (cp = *varp; *cp; cp++) { if (ascii_isdigit(*cp)) { @@ -4420,6 +4414,8 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf, } else if (pp == &curbuf->b_p_ts || pp == &p_ts) { if (value < 1) { errmsg = e_positive; + } else if (value > TABSTOP_MAX) { + errmsg = e_invarg; } } else if (pp == &curbuf->b_p_tw || pp == &p_tw) { if (value < 0) { @@ -4433,7 +4429,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf, // Don't change the value and return early if validation failed. if (errmsg != NULL) { - return (char *)errmsg; + return errmsg; } *pp = value; @@ -4557,7 +4553,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf, // Check the (new) bounds for Rows and Columns here. if (p_lines < min_rows() && full_screen) { if (errbuf != NULL) { - vim_snprintf((char *)errbuf, errbuflen, + vim_snprintf(errbuf, errbuflen, _("E593: Need at least %d lines"), min_rows()); errmsg = errbuf; } @@ -4565,7 +4561,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf, } if (p_columns < MIN_COLUMNS && full_screen) { if (errbuf != NULL) { - vim_snprintf((char *)errbuf, errbuflen, + vim_snprintf(errbuf, errbuflen, _("E594: Need at least %d columns"), MIN_COLUMNS); errmsg = errbuf; } @@ -4681,7 +4677,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf, } check_redraw(options[opt_idx].flags); - return (char *)errmsg; + return errmsg; } /// Trigger the OptionSet autocommand. @@ -6416,7 +6412,7 @@ void buf_copy_options(buf_T *buf, int flags) if (p_vsts && p_vsts != empty_option) { (void)tabstop_set(p_vsts, &buf->b_p_vsts_array); } else { - buf->b_p_vsts_array = 0; + buf->b_p_vsts_array = NULL; } buf->b_p_vsts_nopaste = p_vsts_nopaste ? vim_strsave(p_vsts_nopaste) @@ -7153,10 +7149,7 @@ static void paste_option_changed(void) free_string_option(buf->b_p_vsts); } buf->b_p_vsts = empty_option; - if (buf->b_p_vsts_array) { - xfree(buf->b_p_vsts_array); - } - buf->b_p_vsts_array = 0; + XFREE_CLEAR(buf->b_p_vsts_array); } // set global options @@ -7193,13 +7186,11 @@ static void paste_option_changed(void) buf->b_p_vsts = buf->b_p_vsts_nopaste ? vim_strsave(buf->b_p_vsts_nopaste) : empty_option; - if (buf->b_p_vsts_array) { - xfree(buf->b_p_vsts_array); - } + xfree(buf->b_p_vsts_array); if (buf->b_p_vsts && buf->b_p_vsts != empty_option) { (void)tabstop_set(buf->b_p_vsts, &buf->b_p_vsts_array); } else { - buf->b_p_vsts_array = 0; + buf->b_p_vsts_array = NULL; } } @@ -7560,7 +7551,7 @@ bool tabstop_set(char_u *var, long **array) int n = atoi((char *)cp); // Catch negative values, overflow and ridiculous big values. - if (n < 0 || n > 9999) { + if (n <= 0 || n > TABSTOP_MAX) { semsg(_(e_invarg2), cp); XFREE_CLEAR(*array); return false; diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 5d6aca9574..d88cd6b9b9 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -903,6 +903,8 @@ enum { #define SB_MAX 100000 // Maximum 'scrollback' value. +#define TABSTOP_MAX 9999 + /// Stores an identifier of a script or channel that last set an option. typedef struct { sctx_T script_ctx; /// script context where the option was last set diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index e9f44d2775..e9868d6b61 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -1111,10 +1111,9 @@ size_t home_replace(const buf_T *const buf, const char_u *src, char_u *const dst *dst_p++ = '~'; } - // If it's just the home directory, add "/". - if (!vim_ispathsep(src[0]) && --dstlen > 0) { - *dst_p++ = '/'; - } + // Do not add directory separator into dst, because dst is + // expected to just return the directory name without the + // directory separator '/'. break; } if (p == homedir_env_mod) { diff --git a/src/nvim/path.c b/src/nvim/path.c index 39e276e0a5..7f7f941e26 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -269,16 +269,17 @@ int vim_ispathlistsep(int c) #endif } -/* - * Shorten the path of a file from "~/foo/../.bar/fname" to "~/f/../.b/fname" - * It's done in-place. - */ -char_u *shorten_dir(char_u *str) +/// Shorten the path of a file from "~/foo/../.bar/fname" to "~/f/../.b/fname" +/// "trim_len" specifies how many characters to keep for each directory. +/// Must be 1 or more. +/// It's done in-place. +void shorten_dir_len(char_u *str, int trim_len) { char_u *tail = path_tail(str); char_u *d = str; bool skip = false; - for (char_u *s = str;; ++s) { + int dirchunk_len = 0; + for (char_u *s = str;; s++) { if (s >= tail) { // copy the whole tail *d++ = *s; if (*s == NUL) { @@ -287,10 +288,16 @@ char_u *shorten_dir(char_u *str) } else if (vim_ispathsep(*s)) { // copy '/' and next char *d++ = *s; skip = false; + dirchunk_len = 0; } else if (!skip) { *d++ = *s; // copy next char if (*s != '~' && *s != '.') { // and leading "~" and "." - skip = true; + dirchunk_len++; // only count word chars for the size + // keep copying chars until we have our preferred length (or + // until the above if/else branches move us along) + if (dirchunk_len >= trim_len) { + skip = true; + } } int l = utfc_ptr2len(s); while (--l > 0) { @@ -298,7 +305,13 @@ char_u *shorten_dir(char_u *str) } } } - return str; +} + +/// Shorten the path of a file from "~/foo/../.bar/fname" to "~/f/../.b/fname" +/// It's done in-place. +void shorten_dir(char_u *str) +{ + shorten_dir_len(str, 1); } /* @@ -1508,7 +1521,7 @@ void simplify_filename(char_u *filename) p = filename; #ifdef BACKSLASH_IN_FILENAME - if (p[1] == ':') { // skip "x:" + if (p[0] != NUL && p[1] == ':') { // skip "x:" p += 2; } #endif @@ -2389,9 +2402,11 @@ static int path_to_absolute(const char_u *fname, char_u *buf, size_t len, int fo int path_is_absolute(const char_u *fname) { #ifdef WIN32 + if (*fname == NUL) { + return false; + } // A name like "d:/foo" and "//server/share" is absolute - return ((isalpha(fname[0]) && fname[1] == ':' - && vim_ispathsep_nocolon(fname[2])) + return ((isalpha(fname[0]) && fname[1] == ':' && vim_ispathsep_nocolon(fname[2])) || (vim_ispathsep_nocolon(fname[0]) && fname[0] == fname[1])); #else // UNIX: This just checks if the file name starts with '/' or '~'. diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c index da2ada791f..d7726409b5 100644 --- a/src/nvim/popupmnu.c +++ b/src/nvim/popupmnu.c @@ -291,7 +291,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i } else { assert(Columns - pum_col - pum_scrollbar >= INT_MIN && Columns - pum_col - pum_scrollbar <= INT_MAX); - pum_width = (int)(Columns - pum_col - pum_scrollbar); + pum_width = Columns - pum_col - pum_scrollbar; } if ((pum_width > max_width + pum_kind_width + pum_extra_width + 1) @@ -352,12 +352,12 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i // not enough room, will use what we have if (pum_rl) { assert(Columns - 1 >= INT_MIN); - pum_col = (int)(Columns - 1); + pum_col = Columns - 1; } else { pum_col = 0; } assert(Columns - 1 >= INT_MIN); - pum_width = (int)(Columns - 1); + pum_width = Columns - 1; } else { if (max_width > p_pw) { // truncate @@ -369,7 +369,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i } else { assert(Columns - max_width >= INT_MIN && Columns - max_width <= INT_MAX); - pum_col = (int)(Columns - max_width); + pum_col = Columns - max_width; } pum_width = max_width - pum_scrollbar; } diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 0196e05455..c609daa55c 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -5194,49 +5194,93 @@ static bool vgr_qflist_valid(win_T *wp, qf_info_T *qi, unsigned qfid, char_u *ti /// Search for a pattern in all the lines in a buffer and add the matching lines /// to a quickfix list. -static bool vgr_match_buflines(qf_list_T *qfl, char_u *fname, buf_T *buf, regmmatch_T *regmatch, - long *tomatch, int duplicate_name, int flags) - FUNC_ATTR_NONNULL_ARG(1, 3, 4, 5) +static bool vgr_match_buflines(qf_list_T *qfl, char_u *fname, buf_T *buf, char_u *spat, + regmmatch_T *regmatch, long *tomatch, int duplicate_name, int flags) + FUNC_ATTR_NONNULL_ARG(1, 3, 4, 5, 6) { bool found_match = false; for (long lnum = 1; lnum <= buf->b_ml.ml_line_count && *tomatch > 0; lnum++) { colnr_T col = 0; - while (vim_regexec_multi(regmatch, curwin, buf, lnum, col, NULL, - NULL) > 0) { - // Pass the buffer number so that it gets used even for a - // dummy buffer, unless duplicate_name is set, then the - // buffer will be wiped out below. - if (qf_add_entry(qfl, - NULL, // dir - fname, - NULL, - duplicate_name ? 0 : buf->b_fnum, - ml_get_buf(buf, regmatch->startpos[0].lnum + lnum, - false), - regmatch->startpos[0].lnum + lnum, - regmatch->endpos[0].lnum + lnum, - regmatch->startpos[0].col + 1, - regmatch->endpos[0].col + 1, - false, // vis_col - NULL, // search pattern - 0, // nr - 0, // type - true) // valid - == QF_FAIL) { - got_int = true; - break; - } - found_match = true; - if (--*tomatch == 0) { - break; - } - if ((flags & VGR_GLOBAL) == 0 || regmatch->endpos[0].lnum > 0) { - break; + if (!(flags & VGR_FUZZY)) { + // Regular expression match + while (vim_regexec_multi(regmatch, curwin, buf, lnum, col, NULL, NULL) > 0) { + // Pass the buffer number so that it gets used even for a + // dummy buffer, unless duplicate_name is set, then the + // buffer will be wiped out below. + if (qf_add_entry(qfl, + NULL, // dir + fname, + NULL, + duplicate_name ? 0 : buf->b_fnum, + ml_get_buf(buf, regmatch->startpos[0].lnum + lnum, false), + regmatch->startpos[0].lnum + lnum, + regmatch->endpos[0].lnum + lnum, + regmatch->startpos[0].col + 1, + regmatch->endpos[0].col + 1, + false, // vis_col + NULL, // search pattern + 0, // nr + 0, // type + true) // valid + == QF_FAIL) { + got_int = true; + break; + } + found_match = true; + if (--*tomatch == 0) { + break; + } + if ((flags & VGR_GLOBAL) == 0 || regmatch->endpos[0].lnum > 0) { + break; + } + col = regmatch->endpos[0].col + (col == regmatch->endpos[0].col); + if (col > (colnr_T)STRLEN(ml_get_buf(buf, lnum, false))) { + break; + } } - col = regmatch->endpos[0].col + (col == regmatch->endpos[0].col); - if (col > (colnr_T)STRLEN(ml_get_buf(buf, lnum, false))) { - break; + } else { + const size_t pat_len = STRLEN(spat); + char_u *const str = ml_get_buf(buf, lnum, false); + int score; + uint32_t matches[MAX_FUZZY_MATCHES]; + const size_t sz = sizeof(matches) / sizeof(matches[0]); + + // Fuzzy string match + while (fuzzy_match(str + col, spat, false, &score, matches, (int)sz) > 0) { + // Pass the buffer number so that it gets used even for a + // dummy buffer, unless duplicate_name is set, then the + // buffer will be wiped out below. + if (qf_add_entry(qfl, + NULL, // dir + fname, + NULL, + duplicate_name ? 0 : buf->b_fnum, + str, + lnum, + 0, + (colnr_T)matches[0] + col + 1, + 0, + false, // vis_col + NULL, // search pattern + 0, // nr + 0, // type + true) // valid + == QF_FAIL) { + got_int = true; + break; + } + found_match = true; + if (--*tomatch == 0) { + break; + } + if ((flags & VGR_GLOBAL) == 0) { + break; + } + col = (colnr_T)matches[pat_len - 1] + col + 1; + if (col > (colnr_T)STRLEN(str)) { + break; + } } } line_breakcheck(); @@ -5418,8 +5462,7 @@ 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, ®match, &tomatch, + found_match = vgr_match_buflines(qf_get_curlist(qi), fname, buf, s, ®match, &tomatch, duplicate_name, flags); if (using_dummy) { diff --git a/src/nvim/quickfix.h b/src/nvim/quickfix.h index f5178e332a..0da43e436c 100644 --- a/src/nvim/quickfix.h +++ b/src/nvim/quickfix.h @@ -7,6 +7,7 @@ // flags for skip_vimgrep_pat() #define VGR_GLOBAL 1 #define VGR_NOJUMP 2 +#define VGR_FUZZY 4 #ifdef INCLUDE_GENERATED_DECLARATIONS # include "quickfix.h.generated.h" diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index c8508179a1..6a6c915094 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -2901,18 +2901,14 @@ static int peekchr(void) { int c = regparse[1]; - if (c == NUL) - curchr = '\\'; /* trailing '\' */ - else if ( - c <= '~' && META_flags[c] - ) { - /* - * META contains everything that may be magic sometimes, - * except ^ and $ ("\^" and "\$" are only magic after - * "\V"). We now fetch the next character and toggle its - * magicness. Therefore, \ is so meta-magic that it is - * not in META. - */ + if (c == NUL) { + curchr = '\\'; // trailing '\' + } else if (c <= '~' && META_flags[c]) { + // META contains everything that may be magic sometimes, + // except ^ and $ ("\^" and "\$" are only magic after + // "\V"). We now fetch the next character and toggle its + // magicness. Therefore, \ is so meta-magic that it is + // not in META. curchr = -1; prev_at_start = at_start; at_start = false; // be able to say "/\*ptr" @@ -6538,11 +6534,16 @@ char_u *regtilde(char_u *source, int magic) } } - xfree(reg_prev_sub); - if (newsub != source) /* newsub was allocated, just keep it */ - reg_prev_sub = newsub; - else /* no ~ found, need to save newsub */ - reg_prev_sub = vim_strsave(newsub); + // Only change reg_prev_sub when not previewing. + if (!(State & CMDPREVIEW)) { + xfree(reg_prev_sub); + if (newsub != source) { // newsub was allocated, just keep it + reg_prev_sub = newsub; + } else { // no ~ found, need to save newsub + reg_prev_sub = vim_strsave(newsub); + } + } + return newsub; } diff --git a/src/nvim/screen.c b/src/nvim/screen.c index af023d6785..6b2a2afa41 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -1981,7 +1981,7 @@ static size_t fill_foldcolumn(char_u *p, win_T *wp, foldinfo_T foldinfo, linenr_ level = foldinfo.fi_level; // If the column is too narrow, we start at the lowest level that - // fits and use numbers to indicated the depth. + // fits and use numbers to indicate the depth. first_level = level - fdc - closed + 1; if (first_level < 1) { first_level = 1; @@ -6919,8 +6919,6 @@ void grid_ins_lines(ScreenGrid *grid, int row, int line_count, int end, int col, if (!grid->throttled) { ui_call_grid_scroll(grid->handle, row, end, col, col+width, -line_count, 0); } - - return; } /// delete lines on the screen and move lines up. @@ -6971,8 +6969,6 @@ void grid_del_lines(ScreenGrid *grid, int row, int line_count, int end, int col, if (!grid->throttled) { ui_call_grid_scroll(grid->handle, row, end, col, col+width, line_count, 0); } - - return; } @@ -7354,7 +7350,7 @@ void draw_tabline(void) if (room > 0) { // Get buffer name in NameBuff[] get_trans_bufname(cwp->w_buffer); - (void)shorten_dir(NameBuff); + shorten_dir(NameBuff); len = vim_strsize(NameBuff); p = NameBuff; while (len > room) { diff --git a/src/nvim/search.c b/src/nvim/search.c index 906c9a6f47..3a2435e07a 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -26,6 +26,7 @@ #include "nvim/func_attr.h" #include "nvim/getchar.h" #include "nvim/indent.h" +#include "nvim/indent_c.h" #include "nvim/main.h" #include "nvim/mark.h" #include "nvim/mbyte.h" @@ -2313,12 +2314,9 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) return (pos_T *)NULL; // never found it } -/* - * Check if line[] contains a / / comment. - * Return MAXCOL if not, otherwise return the column. - * TODO: skip strings. - */ -static int check_linecomment(const char_u *line) +/// Check if line[] contains a / / comment. +/// @returns MAXCOL if not, otherwise return the column. +int check_linecomment(const char_u *line) { const char_u *p = line; // scan from start // skip Lispish one-line comments @@ -2338,7 +2336,8 @@ static int check_linecomment(const char_u *line) in_str = true; } } else if (!in_str && ((p - line) < 2 - || (*(p - 1) != '\\' && *(p - 2) != '#'))) { + || (*(p - 1) != '\\' && *(p - 2) != '#')) + && !is_pos_in_string(line, (colnr_T)(p - line))) { break; // found! } p++; @@ -2348,9 +2347,11 @@ static int check_linecomment(const char_u *line) } } else { while ((p = vim_strchr(p, '/')) != NULL) { - // accept a double /, unless it's preceded with * and followed by *, - // because * / / * is an end and start of a C comment - if (p[1] == '/' && (p == line || p[-1] != '*' || p[2] != '*')) { + // Accept a double /, unless it's preceded with * and followed by *, + // because * / / * is an end and start of a C comment. + // Only accept the position if it is not inside a string. + if (p[1] == '/' && (p == line || p[-1] != '*' || p[2] != '*') + && !is_pos_in_string(line, (colnr_T)(p - line))) { break; } ++p; @@ -4763,6 +4764,535 @@ the_end: restore_last_search_pattern(); } +/// Fuzzy string matching +/// +/// Ported from the lib_fts library authored by Forrest Smith. +/// https://github.com/forrestthewoods/lib_fts/tree/master/code +/// +/// The following blog describes the fuzzy matching algorithm: +/// https://www.forrestthewoods.com/blog/reverse_engineering_sublime_texts_fuzzy_match/ +/// +/// Each matching string is assigned a score. The following factors are checked: +/// - Matched letter +/// - Unmatched letter +/// - Consecutively matched letters +/// - Proximity to start +/// - Letter following a separator (space, underscore) +/// - Uppercase letter following lowercase (aka CamelCase) +/// +/// Matched letters are good. Unmatched letters are bad. Matching near the start +/// is good. Matching the first letter in the middle of a phrase is good. +/// Matching the uppercase letters in camel case entries is good. +/// +/// The score assigned for each factor is explained below. +/// File paths are different from file names. File extensions may be ignorable. +/// Single words care about consecutive matches but not separators or camel +/// case. +/// Score starts at 100 +/// Matched letter: +0 points +/// Unmatched letter: -1 point +/// Consecutive match bonus: +15 points +/// First letter bonus: +15 points +/// Separator bonus: +30 points +/// Camel case bonus: +30 points +/// Unmatched leading letter: -5 points (max: -15) +/// +/// There is some nuance to this. Scores don’t have an intrinsic meaning. The +/// score range isn’t 0 to 100. It’s roughly [50, 150]. Longer words have a +/// lower minimum score due to unmatched letter penalty. Longer search patterns +/// have a higher maximum score due to match bonuses. +/// +/// Separator and camel case bonus is worth a LOT. Consecutive matches are worth +/// quite a bit. +/// +/// There is a penalty if you DON’T match the first three letters. Which +/// effectively rewards matching near the start. However there’s no difference +/// in matching between the middle and end. +/// +/// There is not an explicit bonus for an exact match. Unmatched letters receive +/// a penalty. So shorter strings and closer matches are worth more. +typedef struct { + int idx; ///< used for stable sort + listitem_T *item; + int score; + list_T *lmatchpos; +} fuzzyItem_T; + +/// bonus for adjacent matches; this is higher than SEPARATOR_BONUS so that +/// matching a whole word is preferred. +#define SEQUENTIAL_BONUS 40 +/// bonus if match occurs after a path separator +#define PATH_SEPARATOR_BONUS 30 +/// bonus if match occurs after a word separator +#define WORD_SEPARATOR_BONUS 25 +/// bonus if match is uppercase and prev is lower +#define CAMEL_BONUS 30 +/// bonus if the first letter is matched +#define FIRST_LETTER_BONUS 15 +/// penalty applied for every letter in str before the first match +#define LEADING_LETTER_PENALTY -5 +/// maximum penalty for leading letters +#define MAX_LEADING_LETTER_PENALTY -15 +/// penalty for every letter that doesn't match +#define UNMATCHED_LETTER_PENALTY -1 +/// penalty for gap in matching positions (-2 * k) +#define GAP_PENALTY -2 +/// Score for a string that doesn't fuzzy match the pattern +#define SCORE_NONE -9999 + +#define FUZZY_MATCH_RECURSION_LIMIT 10 + +/// Compute a score for a fuzzy matched string. The matching character locations +/// are in 'matches'. +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 +{ + // Initialize score + int score = 100; + + // Apply leading letter penalty + int penalty = LEADING_LETTER_PENALTY * matches[0]; + if (penalty < MAX_LEADING_LETTER_PENALTY) { + penalty = MAX_LEADING_LETTER_PENALTY; + } + score += penalty; + + // Apply unmatched penalty + const int unmatched = strSz - numMatches; + score += UNMATCHED_LETTER_PENALTY * unmatched; + + // Apply ordering bonuses + for (int i = 0; i < numMatches; i++) { + const uint32_t currIdx = matches[i]; + + if (i > 0) { + const uint32_t prevIdx = matches[i - 1]; + + // Sequential + if (currIdx == prevIdx + 1) { + score += SEQUENTIAL_BONUS; + } else { + score += GAP_PENALTY * (currIdx - prevIdx); + } + } + + // Check for bonuses based on neighbor character value + if (currIdx > 0) { + // Camel case + const char_u *p = str; + int neighbor; + + for (uint32_t sidx = 0; sidx < currIdx; sidx++) { + neighbor = utf_ptr2char(p); + MB_PTR_ADV(p); + } + const int curr = utf_ptr2char(p); + + if (mb_islower(neighbor) && mb_isupper(curr)) { + score += CAMEL_BONUS; + } + + // Bonus if the match follows a separator character + if (neighbor == '/' || neighbor == '\\') { + score += PATH_SEPARATOR_BONUS; + } else if (neighbor == ' ' || neighbor == '_') { + score += WORD_SEPARATOR_BONUS; + } + } else { + // First letter + score += FIRST_LETTER_BONUS; + } + } + return score; +} + +/// Perform a recursive search for fuzzy matching 'fuzpat' in 'str'. +/// @return the number of matching characters. +static int fuzzy_match_recursive(const char_u *fuzpat, const char_u *str, uint32_t strIdx, + int *const outScore, const char_u *const strBegin, + const int strLen, const uint32_t *const srcMatches, + uint32_t *const matches, const int maxMatches, int nextMatch, + int *const recursionCount) + FUNC_ATTR_NONNULL_ARG(1, 2, 4, 5, 8, 11) FUNC_ATTR_WARN_UNUSED_RESULT +{ + // Recursion params + bool recursiveMatch = false; + uint32_t bestRecursiveMatches[MAX_FUZZY_MATCHES]; + int bestRecursiveScore = 0; + + // Count recursions + (*recursionCount)++; + if (*recursionCount >= FUZZY_MATCH_RECURSION_LIMIT) { + return 0; + } + + // Detect end of strings + if (*fuzpat == NUL || *str == NUL) { + return 0; + } + + // Loop through fuzpat and str looking for a match + bool first_match = true; + while (*fuzpat != NUL && *str != NUL) { + const int c1 = utf_ptr2char(fuzpat); + const int c2 = utf_ptr2char(str); + + // Found match + if (mb_tolower(c1) == mb_tolower(c2)) { + // Supplied matches buffer was too short + if (nextMatch >= maxMatches) { + return 0; + } + + // "Copy-on-Write" srcMatches into matches + if (first_match && srcMatches != NULL) { + memcpy(matches, srcMatches, nextMatch * sizeof(srcMatches[0])); + first_match = false; + } + + // Recursive call that "skips" this match + uint32_t recursiveMatches[MAX_FUZZY_MATCHES]; + int recursiveScore = 0; + const char_u *const next_char = str + utfc_ptr2len(str); + if (fuzzy_match_recursive(fuzpat, next_char, strIdx + 1, &recursiveScore, strBegin, strLen, + matches, recursiveMatches, + sizeof(recursiveMatches) / sizeof(recursiveMatches[0]), nextMatch, + recursionCount)) { + // Pick best recursive score + if (!recursiveMatch || recursiveScore > bestRecursiveScore) { + memcpy(bestRecursiveMatches, recursiveMatches, + MAX_FUZZY_MATCHES * sizeof(recursiveMatches[0])); + bestRecursiveScore = recursiveScore; + } + recursiveMatch = true; + } + + // Advance + matches[nextMatch++] = strIdx; + MB_PTR_ADV(fuzpat); + } + MB_PTR_ADV(str); + strIdx++; + } + + // Determine if full fuzpat was matched + const bool matched = *fuzpat == NUL; + + // Calculate score + if (matched) { + *outScore = fuzzy_match_compute_score(strBegin, strLen, matches, nextMatch); + } + + // Return best result + if (recursiveMatch && (!matched || bestRecursiveScore > *outScore)) { + // Recursive score is better than "this" + memcpy(matches, bestRecursiveMatches, maxMatches * sizeof(matches[0])); + *outScore = bestRecursiveScore; + return nextMatch; + } else if (matched) { + return nextMatch; // "this" score is better than recursive + } + + return 0; // no match +} + +/// fuzzy_match() +/// +/// Performs exhaustive search via recursion to find all possible matches and +/// match with highest score. +/// Scores values have no intrinsic meaning. Possible score range is not +/// normalized and varies with pattern. +/// Recursion is limited internally (default=10) to prevent degenerate cases +/// (pat_arg="aaaaaa" str="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"). +/// Uses char_u for match indices. Therefore patterns are limited to +/// MAX_FUZZY_MATCHES characters. +/// +/// @return true if 'pat_arg' matches 'str'. Also returns the match score in +/// 'outScore' and the matching character positions in 'matches'. +bool fuzzy_match(char_u *const str, const char_u *const pat_arg, const bool matchseq, + int *const outScore, uint32_t *const matches, const int maxMatches) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + const int len = mb_charlen(str); + bool complete = false; + int numMatches = 0; + + *outScore = 0; + + char_u *const save_pat = vim_strsave(pat_arg); + char_u *pat = save_pat; + char_u *p = pat; + + // Try matching each word in 'pat_arg' in 'str' + while (true) { + if (matchseq) { + complete = true; + } else { + // Extract one word from the pattern (separated by space) + p = skipwhite(p); + if (*p == NUL) { + break; + } + pat = p; + while (*p != NUL && !ascii_iswhite(utf_ptr2char(p))) { + MB_PTR_ADV(p); + } + if (*p == NUL) { // processed all the words + complete = true; + } + *p = NUL; + } + + int score = 0; + int recursionCount = 0; + const int matchCount + = fuzzy_match_recursive(pat, str, 0, &score, str, len, NULL, matches + numMatches, + maxMatches - numMatches, 0, &recursionCount); + if (matchCount == 0) { + numMatches = 0; + break; + } + + // Accumulate the match score and the number of matches + *outScore += score; + numMatches += matchCount; + + if (complete) { + break; + } + + // try matching the next word + p++; + } + + xfree(save_pat); + return numMatches != 0; +} + +/// Sort the fuzzy matches in the descending order of the match score. +/// For items with same score, retain the order using the index (stable sort) +static int fuzzy_match_item_compare(const void *const s1, const void *const s2) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE +{ + const int v1 = ((const fuzzyItem_T *)s1)->score; + const int v2 = ((const fuzzyItem_T *)s2)->score; + const int idx1 = ((const fuzzyItem_T *)s1)->idx; + const int idx2 = ((const fuzzyItem_T *)s2)->idx; + + return v1 == v2 ? (idx1 - idx2) : v1 > v2 ? -1 : 1; +} + +/// Fuzzy search the string 'str' in a list of 'items' and return the matching +/// strings in 'fmatchlist'. +/// If 'matchseq' is true, then for multi-word search strings, match all the +/// words in sequence. +/// If 'items' is a list of strings, then search for 'str' in the list. +/// If 'items' is a list of dicts, then either use 'key' to lookup the string +/// for each item or use 'item_cb' Funcref function to get the string. +/// If 'retmatchpos' is true, then return a list of positions where 'str' +/// matches for each item. +static void fuzzy_match_in_list(list_T *const items, char_u *const str, const bool matchseq, + const char_u *const key, Callback *const item_cb, + const bool retmatchpos, list_T *const fmatchlist) + FUNC_ATTR_NONNULL_ARG(2, 5, 7) +{ + const long len = tv_list_len(items); + if (len == 0) { + return; + } + + fuzzyItem_T *const ptrs = xcalloc(len, sizeof(fuzzyItem_T)); + long i = 0; + bool found_match = false; + uint32_t matches[MAX_FUZZY_MATCHES]; + + // For all the string items in items, get the fuzzy matching score + TV_LIST_ITER(items, li, { + ptrs[i].idx = i; + ptrs[i].item = li; + ptrs[i].score = SCORE_NONE; + char_u *itemstr = NULL; + typval_T rettv; + rettv.v_type = VAR_UNKNOWN; + const typval_T *const tv = TV_LIST_ITEM_TV(li); + if (tv->v_type == VAR_STRING) { // list of strings + itemstr = tv->vval.v_string; + } else if (tv->v_type == VAR_DICT && (key != NULL || item_cb->type != kCallbackNone)) { + // For a dict, either use the specified key to lookup the string or + // use the specified callback function to get the string. + if (key != NULL) { + itemstr = (char_u *)tv_dict_get_string(tv->vval.v_dict, (const char *)key, false); + } else { + typval_T argv[2]; + + // Invoke the supplied callback (if any) to get the dict item + tv->vval.v_dict->dv_refcount++; + argv[0].v_type = VAR_DICT; + argv[0].vval.v_dict = tv->vval.v_dict; + argv[1].v_type = VAR_UNKNOWN; + if (callback_call(item_cb, 1, argv, &rettv)) { + if (rettv.v_type == VAR_STRING) { + itemstr = rettv.vval.v_string; + } + } + tv_dict_unref(tv->vval.v_dict); + } + } + + int score; + if (itemstr != NULL && fuzzy_match(itemstr, str, matchseq, &score, matches, + sizeof(matches) / sizeof(matches[0]))) { + // Copy the list of matching positions in itemstr to a list, if + // 'retmatchpos' is set. + if (retmatchpos) { + ptrs[i].lmatchpos = tv_list_alloc(kListLenMayKnow); + int j = 0; + const char_u *p = str; + while (*p != NUL) { + if (!ascii_iswhite(utf_ptr2char(p))) { + tv_list_append_number(ptrs[i].lmatchpos, matches[j]); + j++; + } + MB_PTR_ADV(p); + } + } + ptrs[i].score = score; + found_match = true; + } + i++; + tv_clear(&rettv); + }); + + if (found_match) { + // Sort the list by the descending order of the match score + qsort(ptrs, len, sizeof(fuzzyItem_T), fuzzy_match_item_compare); + + // For matchfuzzy(), return a list of matched strings. + // ['str1', 'str2', 'str3'] + // For matchfuzzypos(), return a list with three items. + // The first item is a list of matched strings. The second item + // is a list of lists where each list item is a list of matched + // character positions. The third item is a list of matching scores. + // [['str1', 'str2', 'str3'], [[1, 3], [1, 3], [1, 3]]] + list_T *l; + if (retmatchpos) { + const listitem_T *const li = tv_list_find(fmatchlist, 0); + assert(li != NULL && TV_LIST_ITEM_TV(li)->vval.v_list != NULL); + l = TV_LIST_ITEM_TV(li)->vval.v_list; + } else { + l = fmatchlist; + } + + // Copy the matching strings with a valid score to the return list + for (i = 0; i < len; i++) { + if (ptrs[i].score == SCORE_NONE) { + break; + } + tv_list_append_tv(l, TV_LIST_ITEM_TV(ptrs[i].item)); + } + + // next copy the list of matching positions + if (retmatchpos) { + const listitem_T *li = tv_list_find(fmatchlist, -2); + assert(li != NULL && TV_LIST_ITEM_TV(li)->vval.v_list != NULL); + l = TV_LIST_ITEM_TV(li)->vval.v_list; + for (i = 0; i < len; i++) { + if (ptrs[i].score == SCORE_NONE) { + break; + } + tv_list_append_list(l, ptrs[i].lmatchpos); + } + + // copy the matching scores + li = tv_list_find(fmatchlist, -1); + assert(li != NULL && TV_LIST_ITEM_TV(li)->vval.v_list != NULL); + l = TV_LIST_ITEM_TV(li)->vval.v_list; + for (i = 0; i < len; i++) { + if (ptrs[i].score == SCORE_NONE) { + break; + } + tv_list_append_number(l, ptrs[i].score); + } + } + } + xfree(ptrs); +} + +/// Do fuzzy matching. Returns the list of matched strings in 'rettv'. +/// If 'retmatchpos' is true, also returns the matching character positions. +static void do_fuzzymatch(const typval_T *const argvars, typval_T *const rettv, + const bool retmatchpos) + FUNC_ATTR_NONNULL_ALL +{ + // validate and get the arguments + if (argvars[0].v_type != VAR_LIST || argvars[0].vval.v_list == NULL) { + semsg(_(e_listarg), retmatchpos ? "matchfuzzypos()" : "matchfuzzy()"); + return; + } + if (argvars[1].v_type != VAR_STRING || argvars[1].vval.v_string == NULL) { + semsg(_(e_invarg2), tv_get_string(&argvars[1])); + return; + } + + Callback cb = CALLBACK_NONE; + const char_u *key = NULL; + bool matchseq = false; + if (argvars[2].v_type != VAR_UNKNOWN) { + if (argvars[2].v_type != VAR_DICT || argvars[2].vval.v_dict == NULL) { + emsg(_(e_dictreq)); + return; + } + + // To search a dict, either a callback function or a key can be + // specified. + dict_T *const d = argvars[2].vval.v_dict; + const dictitem_T *const di = tv_dict_find(d, "key", -1); + if (di != NULL) { + if (di->di_tv.v_type != VAR_STRING || di->di_tv.vval.v_string == NULL + || *di->di_tv.vval.v_string == NUL) { + semsg(_(e_invarg2), tv_get_string(&di->di_tv)); + return; + } + key = (const char_u *)tv_get_string(&di->di_tv); + } else if (!tv_dict_get_callback(d, "text_cb", -1, &cb)) { + semsg(_(e_invargval), "text_cb"); + return; + } + if (tv_dict_find(d, "matchseq", -1) != NULL) { + matchseq = true; + } + } + + // get the fuzzy matches + tv_list_alloc_ret(rettv, retmatchpos ? 3 : kListLenUnknown); + if (retmatchpos) { + // For matchfuzzypos(), a list with three items are returned. First + // item is a list of matching strings, the second item is a list of + // lists with matching positions within each string and the third item + // is the list of scores of the matches. + tv_list_append_list(rettv->vval.v_list, tv_list_alloc(kListLenUnknown)); + tv_list_append_list(rettv->vval.v_list, tv_list_alloc(kListLenUnknown)); + tv_list_append_list(rettv->vval.v_list, tv_list_alloc(kListLenUnknown)); + } + + fuzzy_match_in_list(argvars[0].vval.v_list, (char_u *)tv_get_string(&argvars[1]), matchseq, key, + &cb, retmatchpos, rettv->vval.v_list); + callback_free(&cb); +} + +/// "matchfuzzy()" function +void f_matchfuzzy(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + do_fuzzymatch(argvars, rettv, false); +} + +/// "matchfuzzypos()" function +void f_matchfuzzypos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + do_fuzzymatch(argvars, rettv, true); +} + /// Find identifiers or defines in included files. /// If p_ic && (compl_cont_status & CONT_SOL) then ptr must be in lowercase. /// diff --git a/src/nvim/search.h b/src/nvim/search.h index 15b8d41f39..53059cc1ea 100644 --- a/src/nvim/search.h +++ b/src/nvim/search.h @@ -55,6 +55,9 @@ #define SEARCH_STAT_DEF_MAX_COUNT 99 #define SEARCH_STAT_BUF_LEN 12 +/// Maximum number of characters that can be fuzzy matched +#define MAX_FUZZY_MATCHES 256 + /// Structure containing offset definition for the last search pattern /// /// @note Only offset for the last search pattern is used, not for the last diff --git a/src/nvim/sign.c b/src/nvim/sign.c index a308df07d1..8b41781c98 100644 --- a/src/nvim/sign.c +++ b/src/nvim/sign.c @@ -1501,28 +1501,28 @@ static void sign_getinfo(sign_T *sp, dict_T *retdict) if (p == NULL) { p = "NONE"; } - tv_dict_add_str(retdict, S_LEN("linehl"), (char *)p); + tv_dict_add_str(retdict, S_LEN("linehl"), p); } if (sp->sn_text_hl > 0) { p = get_highlight_name_ext(NULL, sp->sn_text_hl - 1, false); if (p == NULL) { p = "NONE"; } - tv_dict_add_str(retdict, S_LEN("texthl"), (char *)p); + tv_dict_add_str(retdict, S_LEN("texthl"), p); } if (sp->sn_cul_hl > 0) { p = get_highlight_name_ext(NULL, sp->sn_cul_hl - 1, false); if (p == NULL) { p = "NONE"; } - tv_dict_add_str(retdict, S_LEN("culhl"), (char *)p); + tv_dict_add_str(retdict, S_LEN("culhl"), p); } if (sp->sn_num_hl > 0) { p = get_highlight_name_ext(NULL, sp->sn_num_hl - 1, false); if (p == NULL) { p = "NONE"; } - tv_dict_add_str(retdict, S_LEN("numhl"), (char *)p); + tv_dict_add_str(retdict, S_LEN("numhl"), p); } } diff --git a/src/nvim/state.c b/src/nvim/state.c index 9e4c9b2bad..f9a3aaab7f 100644 --- a/src/nvim/state.c +++ b/src/nvim/state.c @@ -39,10 +39,16 @@ void state_enter(VimState *s) int key; getkey: - if (char_avail() || using_script() || input_available()) { - // Don't block for events if there's a character already available for - // processing. Characters can come from mappings, scripts and other - // sources, so this scenario is very common. + // Expand mappings first by calling vpeekc() directly. + // - If vpeekc() returns non-NUL, there is a character already available for processing, so + // don't block for events. vgetc() may still block, in case of an incomplete UTF-8 sequence. + // - If vpeekc() returns NUL, vgetc() will block, and there are three cases: + // - There is no input available. + // - All of available input maps to an empty string. + // - There is an incomplete mapping. + // A blocking wait for a character should only be done in the third case, which is the only + // case of the three where typebuf.tb_len > 0 after vpeekc() returns NUL. + if (vpeekc() != NUL || typebuf.tb_len > 0) { key = safe_vgetc(); } else if (!multiqueue_empty(main_loop.events)) { // Event was made available after the last multiqueue_process_events call @@ -55,9 +61,11 @@ getkey: // mapping engine. (void)os_inchar(NULL, 0, -1, 0, main_loop.events); // If an event was put into the queue, we send K_EVENT directly. - key = !multiqueue_empty(main_loop.events) - ? K_EVENT - : safe_vgetc(); + if (!multiqueue_empty(main_loop.events)) { + key = K_EVENT; + } else { + goto getkey; + } } if (key == K_EVENT) { diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index a9447165c2..119f6e811f 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -3112,9 +3112,9 @@ static void syn_cmd_conceal(exarg_T *eap, int syncing) next = skiptowhite(arg); if (*arg == NUL) { if (curwin->w_s->b_syn_conceal) { - msg(_("syntax conceal on")); + msg("syntax conceal on"); } else { - msg(_("syntax conceal off")); + msg("syntax conceal off"); } } else if (STRNICMP(arg, "on", 2) == 0 && next - arg == 2) { curwin->w_s->b_syn_conceal = true; @@ -3141,9 +3141,9 @@ static void syn_cmd_case(exarg_T *eap, int syncing) next = skiptowhite(arg); if (*arg == NUL) { if (curwin->w_s->b_syn_ic) { - msg(_("syntax case ignore")); + msg("syntax case ignore"); } else { - msg(_("syntax case match")); + msg("syntax case match"); } } else if (STRNICMP(arg, "match", 5) == 0 && next - arg == 5) { curwin->w_s->b_syn_ic = false; @@ -3168,9 +3168,9 @@ static void syn_cmd_foldlevel(exarg_T *eap, int syncing) if (*arg == NUL) { switch (curwin->w_s->b_syn_foldlevel) { case SYNFLD_START: - msg(_("syntax foldlevel start")); break; + msg("syntax foldlevel start"); break; case SYNFLD_MINIMUM: - msg(_("syntax foldlevel minimum")); break; + msg("syntax foldlevel minimum"); break; default: break; } @@ -3209,11 +3209,11 @@ static void syn_cmd_spell(exarg_T *eap, int syncing) next = skiptowhite(arg); if (*arg == NUL) { if (curwin->w_s->b_syn_spell == SYNSPL_TOP) { - msg(_("syntax spell toplevel")); + msg("syntax spell toplevel"); } else if (curwin->w_s->b_syn_spell == SYNSPL_NOTOP) { - msg(_("syntax spell notoplevel")); + msg("syntax spell notoplevel"); } else { - msg(_("syntax spell default")); + msg("syntax spell default"); } } else if (STRNICMP(arg, "toplevel", 8) == 0 && next - arg == 8) { curwin->w_s->b_syn_spell = SYNSPL_TOP; @@ -3245,7 +3245,7 @@ static void syn_cmd_iskeyword(exarg_T *eap, int syncing) if (*arg == NUL) { msg_puts("\n"); if (curwin->w_s->b_syn_isk != empty_option) { - msg_puts(_("syntax iskeyword ")); + msg_puts("syntax iskeyword "); msg_outtrans(curwin->w_s->b_syn_isk); } else { msg_outtrans((char_u *)_("syntax iskeyword not set")); @@ -6714,6 +6714,90 @@ int lookup_color(const int idx, const bool foreground, TriState *const boldp) return color; } +void set_hl_group(int id, HlAttrs attrs, Dict(highlight) *dict, int link_id) +{ + int idx = id - 1; // Index is ID minus one. + + bool is_default = attrs.rgb_ae_attr & HL_DEFAULT; + + // Return if "default" was used and the group already has settings + if (is_default && hl_has_settings(idx, true)) { + return; + } + + HlGroup *g = &HL_TABLE()[idx]; + + if (link_id > 0) { + g->sg_cleared = false; + g->sg_link = link_id; + g->sg_script_ctx = current_sctx; + g->sg_script_ctx.sc_lnum += sourcing_lnum; + g->sg_set |= SG_LINK; + if (is_default) { + g->sg_deflink = link_id; + g->sg_deflink_sctx = current_sctx; + g->sg_deflink_sctx.sc_lnum += sourcing_lnum; + } + return; + } + + g->sg_cleared = false; + g->sg_link = 0; + g->sg_gui = attrs.rgb_ae_attr; + + g->sg_rgb_fg = attrs.rgb_fg_color; + g->sg_rgb_bg = attrs.rgb_bg_color; + g->sg_rgb_sp = attrs.rgb_sp_color; + + struct { + char **dest; RgbValue val; Object name; + } cattrs[] = { + { &g->sg_rgb_fg_name, g->sg_rgb_fg, HAS_KEY(dict->fg) ? dict->fg : dict->foreground }, + { &g->sg_rgb_bg_name, g->sg_rgb_bg, HAS_KEY(dict->bg) ? dict->bg : dict->background }, + { &g->sg_rgb_sp_name, g->sg_rgb_sp, HAS_KEY(dict->sp) ? dict->sp : dict->special }, + { NULL, -1, NIL }, + }; + + for (int j = 0; cattrs[j].dest; j++) { + if (cattrs[j].val != -1) { + xfree(*cattrs[j].dest); + if (cattrs[j].name.type == kObjectTypeString && cattrs[j].name.data.string.size) { + *cattrs[j].dest = xstrdup(cattrs[j].name.data.string.data); + } else { + char hex_name[8]; + snprintf(hex_name, sizeof(hex_name), "#%06x", cattrs[j].val); + *cattrs[j].dest = xstrdup(hex_name); + } + } + } + + g->sg_cterm = attrs.cterm_ae_attr; + g->sg_cterm_bg = attrs.cterm_bg_color; + g->sg_cterm_fg = attrs.cterm_fg_color; + g->sg_cterm_bold = g->sg_cterm & HL_BOLD; + g->sg_blend = attrs.hl_blend; + + g->sg_script_ctx = current_sctx; + g->sg_script_ctx.sc_lnum += sourcing_lnum; + + // 'Normal' is special + if (STRCMP(g->sg_name_u, "NORMAL") == 0) { + cterm_normal_fg_color = g->sg_cterm_fg; + cterm_normal_bg_color = g->sg_cterm_bg; + normal_fg = g->sg_rgb_fg; + normal_bg = g->sg_rgb_bg; + normal_sp = g->sg_rgb_sp; + ui_default_colors_set(); + } else { + g->sg_attr = hl_get_syn_attr(0, id, attrs); + + // a cursor style uses this syn_id, make sure its attribute is updated. + if (cursor_mode_uses_syn_id(id)) { + ui_mode_info_set(); + } + } +} + /// Handle ":highlight" command /// diff --git a/src/nvim/tag.c b/src/nvim/tag.c index a10a2a0c32..54d7e54fb4 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -3408,7 +3408,7 @@ static void tagstack_push_items(win_T *wp, list_T *l) if ((di = tv_dict_find(itemdict, "from", -1)) == NULL) { continue; } - if (list2fpos(&di->di_tv, &mark, &fnum, NULL) != OK) { + if (list2fpos(&di->di_tv, &mark, &fnum, NULL, false) != OK) { continue; } if ((tagname = (char_u *)tv_dict_get_string(itemdict, "tagname", true)) diff --git a/src/nvim/testdir/check.vim b/src/nvim/testdir/check.vim index ab26dddbe0..883f036fe1 100644 --- a/src/nvim/testdir/check.vim +++ b/src/nvim/testdir/check.vim @@ -63,6 +63,15 @@ func CheckUnix() endif endfunc +" Command to check for not running on a BSD system. +" TODO: using this checks should not be needed +command CheckNotBSD call CheckNotBSD() +func CheckNotBSD() + if has('bsd') + throw 'Skipped: does not work on BSD' + endif +endfunc + " Command to check that making screendumps is supported. " Caller must source screendump.vim command CheckScreendump call CheckScreendump() @@ -104,6 +113,14 @@ func CheckNotGui() endif endfunc +" Command to check that test is not running as root +command CheckNotRoot call CheckNotRoot() +func CheckNotRoot() + if IsRoot() + throw 'Skipped: cannot run test as root' + endif +endfunc + " Command to check that the current language is English command CheckEnglish call CheckEnglish() func CheckEnglish() diff --git a/src/nvim/testdir/shared.vim b/src/nvim/testdir/shared.vim index f456ff4250..c2809844ac 100644 --- a/src/nvim/testdir/shared.vim +++ b/src/nvim/testdir/shared.vim @@ -343,6 +343,15 @@ func RunVimPiped(before, after, arguments, pipecmd) return 1 endfunc +func IsRoot() + if !has('unix') + return v:false + elseif $USER == 'root' || system('id -un') =~ '\<root\>' + return v:true + endif + return v:false +endfunc + " Get all messages but drop the maintainer entry. func GetMessages() redir => result diff --git a/src/nvim/testdir/test_alot_utf8.vim b/src/nvim/testdir/test_alot_utf8.vim index 70f14320a6..77f5ede4c8 100644 --- a/src/nvim/testdir/test_alot_utf8.vim +++ b/src/nvim/testdir/test_alot_utf8.vim @@ -6,7 +6,6 @@ source test_charsearch_utf8.vim source test_expr_utf8.vim -source test_matchadd_conceal_utf8.vim source test_mksession_utf8.vim source test_regexp_utf8.vim source test_source_utf8.vim diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index 231ab2acf1..a8d51ef598 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -4,7 +4,7 @@ source shared.vim source check.vim source term_util.vim -func! s:cleanup_buffers() abort +func s:cleanup_buffers() abort for bnr in range(1, bufnr('$')) if bufloaded(bnr) && bufnr('%') != bnr execute 'bd! ' . bnr @@ -2543,6 +2543,16 @@ func Test_close_autocmd_tab() %bwipe! endfunc +func Test_Visual_doautoall_redraw() + call setline(1, ['a', 'b']) + new + wincmd p + call feedkeys("G\<C-V>", 'txn') + autocmd User Explode ++once redraw + doautoall User Explode + %bwipe! +endfunc + func Test_autocmd_closes_window() au BufNew,BufWinLeave * e %e file yyy diff --git a/src/nvim/testdir/test_breakindent.vim b/src/nvim/testdir/test_breakindent.vim index 8d592f21ea..b619f2adb6 100644 --- a/src/nvim/testdir/test_breakindent.vim +++ b/src/nvim/testdir/test_breakindent.vim @@ -20,7 +20,7 @@ func s:screen_lines2(lnums, lnume, width) abort return ScreenLines([a:lnums, a:lnume], a:width) endfunc -func! s:compare_lines(expect, actual) +func s:compare_lines(expect, actual) call assert_equal(join(a:expect, "\n"), join(a:actual, "\n")) endfunc diff --git a/src/nvim/testdir/test_charsearch_utf8.vim b/src/nvim/testdir/test_charsearch_utf8.vim index 09341a90b0..82a807ac5b 100644 --- a/src/nvim/testdir/test_charsearch_utf8.vim +++ b/src/nvim/testdir/test_charsearch_utf8.vim @@ -1,7 +1,7 @@ " Tests for related f{char} and t{char} using utf-8. " Test for t,f,F,T movement commands -function! Test_search_cmds() +func Test_search_cmds() new! call setline(1, "・最初から最後まで最強のVimは最高") 1 diff --git a/src/nvim/testdir/test_cindent.vim b/src/nvim/testdir/test_cindent.vim index 6554d034d3..5dc54111e7 100644 --- a/src/nvim/testdir/test_cindent.vim +++ b/src/nvim/testdir/test_cindent.vim @@ -1707,9 +1707,9 @@ func Test_cindent_1() #endif int y; // comment - // comment + // comment - // comment + // comment { Constructor(int a, diff --git a/src/nvim/testdir/test_cscope.vim b/src/nvim/testdir/test_cscope.vim index cc6154af69..faf37485cd 100644 --- a/src/nvim/testdir/test_cscope.vim +++ b/src/nvim/testdir/test_cscope.vim @@ -102,7 +102,7 @@ func Test_cscopeWithCscopeConnections() for cmd in ['cs find f Xmemfile_test.c', 'cs find 7 Xmemfile_test.c'] enew let a = execute(cmd) - call assert_true(a =~ '"Xmemfile_test.c" \d\+L, \d\+C') + call assert_true(a =~ '"Xmemfile_test.c" \d\+L, \d\+B') call assert_equal('Xmemfile_test.c', @%) endfor @@ -112,7 +112,7 @@ func Test_cscopeWithCscopeConnections() let a = execute(cmd) let alines = split(a, '\n', 1) call assert_equal('', alines[0]) - call assert_true(alines[1] =~ '"Xmemfile_test.c" \d\+L, \d\+C') + call assert_true(alines[1] =~ '"Xmemfile_test.c" \d\+L, \d\+B') call assert_equal('(1 of 1): <<global>> #include <assert.h>', alines[2]) call assert_equal('#include <assert.h>', getline('.')) endfor diff --git a/src/nvim/testdir/test_cursor_func.vim b/src/nvim/testdir/test_cursor_func.vim index e8c4a952ee..f2ffd50726 100644 --- a/src/nvim/testdir/test_cursor_func.vim +++ b/src/nvim/testdir/test_cursor_func.vim @@ -1,4 +1,4 @@ -" Tests for cursor(). +" Tests for cursor() and other functions that get/set the cursor position func Test_wrong_arguments() call assert_fails('call cursor(1. 3)', 'E474:') @@ -24,6 +24,9 @@ func Test_move_cursor() " below last line goes to last line call cursor(9, 1) call assert_equal([4, 1, 0, 1], getcurpos()[1:]) + " pass string arguments + call cursor('3', '3') + call assert_equal([3, 3, 0, 3], getcurpos()[1:]) call setline(1, ["\<TAB>"]) call cursor(1, 1, 1) @@ -119,3 +122,188 @@ func Test_screenpos_number() close bwipe! endfunc + +func SaveVisualStartCharPos() + call add(g:VisualStartPos, getcharpos('v')) + return '' +endfunc + +" Test for the getcharpos() function +func Test_getcharpos() + call assert_fails('call getcharpos({})', 'E731:') + call assert_equal([0, 0, 0, 0], getcharpos(0)) + new + call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678']) + + " Test for '.' and '$' + normal 1G + call assert_equal([0, 1, 1, 0], getcharpos('.')) + call assert_equal([0, 4, 1, 0], getcharpos('$')) + normal 2G6l + call assert_equal([0, 2, 7, 0], getcharpos('.')) + normal 3G$ + call assert_equal([0, 3, 1, 0], getcharpos('.')) + normal 4G$ + call assert_equal([0, 4, 9, 0], getcharpos('.')) + + " Test for a mark + normal 2G7lmmgg + call assert_equal([0, 2, 8, 0], getcharpos("'m")) + delmarks m + call assert_equal([0, 0, 0, 0], getcharpos("'m")) + + " Test for the visual start column + vnoremap <expr> <F3> SaveVisualStartCharPos() + let g:VisualStartPos = [] + exe "normal 2G6lv$\<F3>ohh\<F3>o\<F3>" + call assert_equal([[0, 2, 7, 0], [0, 2, 9, 0], [0, 2, 5, 0]], g:VisualStartPos) + call assert_equal([0, 2, 9, 0], getcharpos('v')) + let g:VisualStartPos = [] + exe "normal 3Gv$\<F3>o\<F3>" + call assert_equal([[0, 3, 1, 0], [0, 3, 1, 0]], g:VisualStartPos) + let g:VisualStartPos = [] + exe "normal 1Gv$\<F3>o\<F3>" + call assert_equal([[0, 1, 1, 0], [0, 1, 1, 0]], g:VisualStartPos) + vunmap <F3> + + %bw! +endfunc + +" Test for the setcharpos() function +func Test_setcharpos() + call assert_equal(-1, setcharpos('.', v:_null_list)) + new + call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678']) + call setcharpos('.', [0, 1, 1, 0]) + call assert_equal([1, 1], [line('.'), col('.')]) + call setcharpos('.', [0, 2, 7, 0]) + call assert_equal([2, 9], [line('.'), col('.')]) + call setcharpos('.', [0, 3, 4, 0]) + call assert_equal([3, 1], [line('.'), col('.')]) + call setcharpos('.', [0, 3, 1, 0]) + call assert_equal([3, 1], [line('.'), col('.')]) + call setcharpos('.', [0, 4, 0, 0]) + call assert_equal([4, 1], [line('.'), col('.')]) + call setcharpos('.', [0, 4, 20, 0]) + call assert_equal([4, 9], [line('.'), col('.')]) + + " Test for mark + delmarks m + call setcharpos("'m", [0, 2, 9, 0]) + normal `m + call assert_equal([2, 11], [line('.'), col('.')]) + + %bw! + call assert_equal(-1, setcharpos('.', [10, 3, 1, 0])) +endfunc + +func SaveVisualStartCharCol() + call add(g:VisualStartCol, charcol('v')) + return '' +endfunc + +" Test for the charcol() function +func Test_charcol() + call assert_fails('call charcol({})', 'E731:') + call assert_equal(0, charcol(0)) + new + call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678']) + + " Test for '.' and '$' + normal 1G + call assert_equal(1, charcol('.')) + call assert_equal(1, charcol('$')) + normal 2G6l + call assert_equal(7, charcol('.')) + call assert_equal(10, charcol('$')) + normal 3G$ + call assert_equal(1, charcol('.')) + call assert_equal(2, charcol('$')) + normal 4G$ + call assert_equal(9, charcol('.')) + call assert_equal(10, charcol('$')) + + " Test for [lnum, '$'] + call assert_equal(1, charcol([1, '$'])) + call assert_equal(10, charcol([2, '$'])) + call assert_equal(2, charcol([3, '$'])) + call assert_equal(0, charcol([5, '$'])) + + " Test for a mark + normal 2G7lmmgg + call assert_equal(8, charcol("'m")) + delmarks m + call assert_equal(0, charcol("'m")) + + " Test for the visual start column + vnoremap <expr> <F3> SaveVisualStartCharCol() + let g:VisualStartCol = [] + exe "normal 2G6lv$\<F3>ohh\<F3>o\<F3>" + call assert_equal([7, 9, 5], g:VisualStartCol) + call assert_equal(9, charcol('v')) + let g:VisualStartCol = [] + exe "normal 3Gv$\<F3>o\<F3>" + call assert_equal([1, 1], g:VisualStartCol) + let g:VisualStartCol = [] + exe "normal 1Gv$\<F3>o\<F3>" + call assert_equal([1, 1], g:VisualStartCol) + vunmap <F3> + + %bw! +endfunc + +" Test for getcursorcharpos() +func Test_getcursorcharpos() + call assert_equal(getcursorcharpos(), getcursorcharpos(0)) + call assert_equal([0, 0, 0, 0, 0], getcursorcharpos(-1)) + call assert_equal([0, 0, 0, 0, 0], getcursorcharpos(1999)) + + new + call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678']) + normal 1G9l + call assert_equal([0, 1, 1, 0, 1], getcursorcharpos()) + normal 2G9l + call assert_equal([0, 2, 9, 0, 14], getcursorcharpos()) + normal 3G9l + call assert_equal([0, 3, 1, 0, 1], getcursorcharpos()) + normal 4G9l + call assert_equal([0, 4, 9, 0, 9], getcursorcharpos()) + + let winid = win_getid() + normal 2G5l + wincmd w + call assert_equal([0, 2, 6, 0, 11], getcursorcharpos(winid)) + %bw! +endfunc + +" Test for setcursorcharpos() +func Test_setcursorcharpos() + call assert_fails('call setcursorcharpos(v:_null_list)', 'E474:') + call assert_fails('call setcursorcharpos([1])', 'E474:') + call assert_fails('call setcursorcharpos([1, 1, 1, 1, 1])', 'E474:') + new + call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678']) + normal G + call setcursorcharpos([1, 1]) + call assert_equal([1, 1], [line('.'), col('.')]) + call setcursorcharpos([2, 7, 0]) + call assert_equal([2, 9], [line('.'), col('.')]) + call setcursorcharpos(3, 4) + call assert_equal([3, 1], [line('.'), col('.')]) + call setcursorcharpos([3, 1]) + call assert_equal([3, 1], [line('.'), col('.')]) + call setcursorcharpos([4, 0, 0, 0]) + call assert_equal([4, 1], [line('.'), col('.')]) + call setcursorcharpos([4, 20]) + call assert_equal([4, 9], [line('.'), col('.')]) + normal 1G + call setcursorcharpos([100, 100, 100, 100]) + call assert_equal([4, 9], [line('.'), col('.')]) + normal 1G + call setcursorcharpos('$', 1) + call assert_equal([4, 1], [line('.'), col('.')]) + + %bw! +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_display.vim b/src/nvim/testdir/test_display.vim index c2a9683f7c..9f74d0a38a 100644 --- a/src/nvim/testdir/test_display.vim +++ b/src/nvim/testdir/test_display.vim @@ -41,7 +41,7 @@ func Test_display_foldcolumn() quit! endfunc -func! Test_display_foldtext_mbyte() +func Test_display_foldtext_mbyte() CheckFeature folding call NewWindow(10, 40) diff --git a/src/nvim/testdir/test_edit.vim b/src/nvim/testdir/test_edit.vim index c1f74e7675..a1f6a84a99 100644 --- a/src/nvim/testdir/test_edit.vim +++ b/src/nvim/testdir/test_edit.vim @@ -213,7 +213,7 @@ func Test_edit_07() bw! endfunc -func! Test_edit_08() +func Test_edit_08() throw 'skipped: moved to test/functional/legacy/edit_spec.lua' " reset insertmode from i_ctrl-r_= let g:bufnr = bufnr('%') @@ -417,7 +417,7 @@ func Test_edit_13() bwipe! endfunc -func! Test_edit_CR() +func Test_edit_CR() " Test for <CR> in insert mode " basically only in quickfix mode ist tested, the rest " has been taken care of by other tests @@ -450,7 +450,7 @@ func! Test_edit_CR() call delete('Xqflist.txt') endfunc -func! Test_edit_CTRL_() +func Test_edit_CTRL_() " disabled for Windows builds, why? if !has("rightleft") || has("win32") return @@ -734,7 +734,7 @@ func Test_edit_CTRL_O() bw! endfunc -func! Test_edit_CTRL_R() +func Test_edit_CTRL_R() " Insert Register new " call test_override("ALL", 1) @@ -1006,8 +1006,6 @@ func Test_edit_DROP() endfunc func Test_edit_CTRL_V() - CheckNotFeature ebcdic - new call setline(1, ['abc']) call cursor(2, 1) @@ -1561,11 +1559,7 @@ endfunc func Test_edit_special_chars() new - if has("ebcdic") - let t = "o\<C-V>193\<C-V>xc2\<C-V>o303 \<C-V>90a\<C-V>xfg\<C-V>o578\<Esc>" - else - let t = "o\<C-V>65\<C-V>x42\<C-V>o103 \<C-V>33a\<C-V>xfg\<C-V>o78\<Esc>" - endif + let t = "o\<C-V>65\<C-V>x42\<C-V>o103 \<C-V>33a\<C-V>xfg\<C-V>o78\<Esc>" exe "normal " . t call assert_equal("ABC !a\<C-O>g\<C-G>8", getline(2)) diff --git a/src/nvim/testdir/test_eval_stuff.vim b/src/nvim/testdir/test_eval_stuff.vim index 883ba5de3d..95eccde35c 100644 --- a/src/nvim/testdir/test_eval_stuff.vim +++ b/src/nvim/testdir/test_eval_stuff.vim @@ -65,11 +65,9 @@ func Test_E963() endfunc func Test_for_invalid() - " Vim gives incorrect emsg here until v8.2.3284, but the exact emsg from that - " patch cannot be used until v8.2.2658 is ported (for loop over Strings) - call assert_fails("for x in 99", 'E897:') - call assert_fails("for x in function('winnr')", 'E897:') - call assert_fails("for x in {'a': 9}", 'E897:') + call assert_fails("for x in 99", 'E1098:') + call assert_fails("for x in function('winnr')", 'E1098:') + call assert_fails("for x in {'a': 9}", 'E1098:') if 0 /1/5/2/s/\n 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_exec_while_if.vim b/src/nvim/testdir/test_exec_while_if.vim index 3da2784d77..3f13b09945 100644 --- a/src/nvim/testdir/test_exec_while_if.vim +++ b/src/nvim/testdir/test_exec_while_if.vim @@ -6,11 +6,7 @@ func Test_exec_while_if() let i = 0 while i < 12 let i = i + 1 - if has("ebcdic") - execute "normal o" . i . "\047" - else - execute "normal o" . i . "\033" - endif + execute "normal o" . i . "\033" if i % 2 normal Ax if i == 9 @@ -21,21 +17,13 @@ func Test_exec_while_if() else let j = 9 while j > 0 - if has("ebcdic") - execute "normal" j . "a" . j . "\x27" - else - execute "normal" j . "a" . j . "\x1b" - endif + execute "normal" j . "a" . j . "\x1b" let j = j - 1 endwhile endif endif if i == 9 - if has("ebcdic") - execute "normal Az\047" - else - execute "normal Az\033" - endif + execute "normal Az\033" endif endwhile unlet i j diff --git a/src/nvim/testdir/test_execute_func.vim b/src/nvim/testdir/test_execute_func.vim index 2cb6d73407..16cc20e9a7 100644 --- a/src/nvim/testdir/test_execute_func.vim +++ b/src/nvim/testdir/test_execute_func.vim @@ -147,3 +147,30 @@ func Test_win_execute_other_tab() tabclose unlet xyz endfunc + +func Test_win_execute_visual_redraw() + call setline(1, ['a', 'b', 'c']) + new + wincmd p + " start Visual in current window, redraw in other window with fewer lines + call feedkeys("G\<C-V>", 'txn') + call win_execute(winnr('#')->win_getid(), 'redraw') + call feedkeys("\<Esc>", 'txn') + bwipe! + bwipe! + + enew + new + call setline(1, ['a', 'b', 'c']) + let winid = win_getid() + wincmd p + " start Visual in current window, extend it in other window with more lines + call feedkeys("\<C-V>", 'txn') + call win_execute(winid, 'call feedkeys("G\<C-V>", ''txn'')') + redraw + + bwipe! + bwipe! +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_expand.vim b/src/nvim/testdir/test_expand.vim new file mode 100644 index 0000000000..48dce25bb3 --- /dev/null +++ b/src/nvim/testdir/test_expand.vim @@ -0,0 +1,83 @@ +" Test for expanding file names + +func Test_with_directories() + call mkdir('Xdir1') + call mkdir('Xdir2') + call mkdir('Xdir3') + cd Xdir3 + call mkdir('Xdir4') + cd .. + + split Xdir1/file + call setline(1, ['a', 'b']) + w + w Xdir3/Xdir4/file + close + + next Xdir?/*/file + call assert_equal('Xdir3/Xdir4/file', expand('%')) + if has('unix') + next! Xdir?/*/nofile + call assert_equal('Xdir?/*/nofile', expand('%')) + endif + " Edit another file, on MS-Windows the swap file would be in use and can't + " be deleted. + edit foo + + call assert_equal(0, delete('Xdir1', 'rf')) + call assert_equal(0, delete('Xdir2', 'rf')) + call assert_equal(0, delete('Xdir3', 'rf')) +endfunc + +func Test_with_tilde() + let dir = getcwd() + call mkdir('Xdir ~ dir') + call assert_true(isdirectory('Xdir ~ dir')) + cd Xdir\ ~\ dir + call assert_true(getcwd() =~ 'Xdir \~ dir') + call chdir(dir) + call delete('Xdir ~ dir', 'd') + call assert_false(isdirectory('Xdir ~ dir')) +endfunc + +func Test_expand_tilde_filename() + split ~ + call assert_equal('~', expand('%')) + call assert_notequal(expand('%:p'), expand('~/')) + call assert_match('\~', expand('%:p')) + bwipe! +endfunc + +func Test_expandcmd() + let $FOO = 'Test' + call assert_equal('e x/Test/y', expandcmd('e x/$FOO/y')) + unlet $FOO + + new + edit Xfile1 + call assert_equal('e Xfile1', expandcmd('e %')) + edit Xfile2 + edit Xfile1 + call assert_equal('e Xfile2', 'e #'->expandcmd()) + edit Xfile2 + edit Xfile3 + edit Xfile4 + let bnum = bufnr('Xfile2') + call assert_equal('e Xfile2', expandcmd('e #' . bnum)) + call setline('.', 'Vim!@#') + call assert_equal('e Vim', expandcmd('e <cword>')) + call assert_equal('e Vim!@#', expandcmd('e <cWORD>')) + enew! + edit Xfile.java + call assert_equal('e Xfile.py', expandcmd('e %:r.py')) + call assert_equal('make abc.java', expandcmd('make abc.%:e')) + call assert_equal('make Xabc.java', expandcmd('make %:s?file?abc?')) + edit a1a2a3.rb + call assert_equal('make b1b2b3.rb a1a2a3 Xfile.o', expandcmd('make %:gs?a?b? %< #<.o')) + + call assert_fails('call expandcmd("make <afile>")', 'E495:') + call assert_fails('call expandcmd("make <afile>")', 'E495:') + enew + call assert_fails('call expandcmd("make %")', 'E499:') + close +endfunc diff --git a/src/nvim/testdir/test_feedkeys.vim b/src/nvim/testdir/test_feedkeys.vim index 70500f2bb5..f343b0174c 100644 --- a/src/nvim/testdir/test_feedkeys.vim +++ b/src/nvim/testdir/test_feedkeys.vim @@ -12,3 +12,15 @@ func Test_feedkeys_x_with_empty_string() call assert_equal('foo', getline('.')) quit! endfunc + +func Test_feedkeys_with_abbreviation() + new + inoreabbrev trigger value + call feedkeys("atrigger ", 'x') + call feedkeys("atrigger ", 'x') + call assert_equal('value value ', getline(1)) + bwipe! + iunabbrev trigger +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index dcf20e2c1b..4ef35b3a46 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -183,6 +183,7 @@ let s:filename_checks = { \ 'fgl': ['file.4gl', 'file.4gh', 'file.m4gl'], \ 'fish': ['file.fish'], \ 'focexec': ['file.fex', 'file.focexec'], + \ 'form': ['file.frm'], \ 'forth': ['file.ft', 'file.fth'], \ 'fortran': ['file.f', 'file.for', 'file.fortran', 'file.fpp', 'file.ftn', 'file.f77', 'file.f90', 'file.f95', 'file.f03', 'file.f08'], \ 'fpcmake': ['file.fpc'], @@ -730,101 +731,126 @@ func Test_setfiletype_completion() call assert_equal('"setfiletype java javacc javascript javascriptreact', @:) endfunc -func Test_hook_file() +""""""""""""""""""""""""""""""""""""""""""""""""" +" Tests for specific extentions and filetypes. +" Keep sorted. +""""""""""""""""""""""""""""""""""""""""""""""""" + +func Test_bas_file() filetype on - call writefile(['[Trigger]', 'this is pacman config'], 'Xfile.hook') - split Xfile.hook - call assert_equal('dosini', &filetype) + call writefile(['looks like BASIC'], 'Xfile.bas') + split Xfile.bas + call assert_equal('basic', &filetype) bwipe! - call writefile(['not pacman'], 'Xfile.hook') - split Xfile.hook - call assert_notequal('dosini', &filetype) + " Test dist#ft#FTbas() + + let g:filetype_bas = 'freebasic' + split Xfile.bas + call assert_equal('freebasic', &filetype) bwipe! + unlet g:filetype_bas - call delete('Xfile.hook') - filetype off -endfunc + " FreeBASIC -func Test_tf_file() - filetype on + call writefile(["/' FreeBASIC multiline comment '/"], 'Xfile.bas') + split Xfile.bas + call assert_equal('freebasic', &filetype) + bwipe! - call writefile([';;; TF MUD client is super duper cool'], 'Xfile.tf') - split Xfile.tf - call assert_equal('tf', &filetype) + call writefile(['#define TESTING'], 'Xfile.bas') + split Xfile.bas + call assert_equal('freebasic', &filetype) bwipe! - call writefile(['provider "azurerm" {'], 'Xfile.tf') - split Xfile.tf - call assert_equal('terraform', &filetype) + call writefile(['option byval'], 'Xfile.bas') + split Xfile.bas + call assert_equal('freebasic', &filetype) bwipe! - call delete('Xfile.tf') - filetype off -endfunc + call writefile(['extern "C"'], 'Xfile.bas') + split Xfile.bas + call assert_equal('freebasic', &filetype) + bwipe! + " QB64 -func Test_ts_file() - filetype on + call writefile(['$LET TESTING = 1'], 'Xfile.bas') + split Xfile.bas + call assert_equal('qb64', &filetype) + bwipe! - call writefile(['<?xml version="1.0" encoding="utf-8"?>'], 'Xfile.ts') - split Xfile.ts - call assert_equal('xml', &filetype) + call writefile(['OPTION _EXPLICIT'], 'Xfile.bas') + split Xfile.bas + call assert_equal('qb64', &filetype) bwipe! - call writefile(['// looks like Typescript'], 'Xfile.ts') - split Xfile.ts - call assert_equal('typescript', &filetype) + " Visual Basic + + call writefile(['Attribute VB_NAME = "Testing"'], 'Xfile.bas') + split Xfile.bas + call assert_equal('vb', &filetype) bwipe! - call delete('Xfile.hook') + call delete('Xfile.bas') filetype off endfunc -func Test_ttl_file() +func Test_dep3patch_file() filetype on - call writefile(['@base <http://example.org/> .'], 'Xfile.ttl') - split Xfile.ttl - call assert_equal('turtle', &filetype) - bwipe! + call assert_true(mkdir('debian/patches', 'p')) - call writefile(['looks like Tera Term Language'], 'Xfile.ttl') - split Xfile.ttl - call assert_equal('teraterm', &filetype) + " series files are not patches + call writefile(['Description: some awesome patch'], 'debian/patches/series') + split debian/patches/series + call assert_notequal('dep3patch', &filetype) bwipe! - call delete('Xfile.ttl') - filetype off -endfunc + " diff/patch files without the right headers should still show up as ft=diff + call writefile([], 'debian/patches/foo.diff') + split debian/patches/foo.diff + call assert_equal('diff', &filetype) + bwipe! -func Test_pp_file() - filetype on + " Files with the right headers are detected as dep3patch, even if they don't + " have a diff/patch extension + call writefile(['Subject: dep3patches'], 'debian/patches/bar') + split debian/patches/bar + call assert_equal('dep3patch', &filetype) + bwipe! - call writefile(['looks like puppet'], 'Xfile.pp') - split Xfile.pp - call assert_equal('puppet', &filetype) + " Files in sub-directories are detected + call assert_true(mkdir('debian/patches/s390x', 'p')) + call writefile(['Subject: dep3patches'], 'debian/patches/s390x/bar') + split debian/patches/s390x/bar + call assert_equal('dep3patch', &filetype) bwipe! - let g:filetype_pp = 'pascal' - split Xfile.pp - call assert_equal('pascal', &filetype) + " The detection stops when seeing the "header end" marker + call writefile(['---', 'Origin: the cloud'], 'debian/patches/baz') + split debian/patches/baz + call assert_notequal('dep3patch', &filetype) bwipe! - unlet g:filetype_pp - " Test dist#ft#FTpp() - call writefile(['{ pascal comment'], 'Xfile.pp') - split Xfile.pp - call assert_equal('pascal', &filetype) + call delete('debian', 'rf') +endfunc + +func Test_dsl_file() + filetype on + + call writefile([' <!doctype dsssl-spec ['], 'dslfile.dsl') + split dslfile.dsl + call assert_equal('dsl', &filetype) bwipe! - call writefile(['procedure pascal'], 'Xfile.pp') - split Xfile.pp - call assert_equal('pascal', &filetype) + call writefile(['workspace {'], 'dslfile.dsl') + split dslfile.dsl + call assert_equal('structurizr', &filetype) bwipe! - call delete('Xfile.pp') + call delete('dslfile.dsl') filetype off endfunc @@ -865,20 +891,183 @@ func Test_ex_file() filetype off endfunc -func Test_dsl_file() +func Test_foam_file() filetype on + call assert_true(mkdir('0', 'p')) + call assert_true(mkdir('0.orig', 'p')) - call writefile([' <!doctype dsssl-spec ['], 'dslfile.dsl') - split dslfile.dsl - call assert_equal('dsl', &filetype) + call writefile(['FoamFile {', ' object something;'], 'Xfile1Dict') + split Xfile1Dict + call assert_equal('foam', &filetype) bwipe! - call writefile(['workspace {'], 'dslfile.dsl') - split dslfile.dsl - call assert_equal('structurizr', &filetype) + call writefile(['FoamFile {', ' object something;'], 'Xfile1Dict.something') + split Xfile1Dict.something + call assert_equal('foam', &filetype) bwipe! - call delete('dslfile.dsl') + call writefile(['FoamFile {', ' object something;'], 'XfileProperties') + split XfileProperties + call assert_equal('foam', &filetype) + bwipe! + + call writefile(['FoamFile {', ' object something;'], 'XfileProperties.something') + split XfileProperties.something + call assert_equal('foam', &filetype) + bwipe! + + call writefile(['FoamFile {', ' object something;'], 'XfileProperties') + split XfileProperties + call assert_equal('foam', &filetype) + bwipe! + + call writefile(['FoamFile {', ' object something;'], 'XfileProperties.something') + split XfileProperties.something + call assert_equal('foam', &filetype) + bwipe! + + call writefile(['FoamFile {', ' object something;'], '0/Xfile') + split 0/Xfile + call assert_equal('foam', &filetype) + bwipe! + + call writefile(['FoamFile {', ' object something;'], '0.orig/Xfile') + split 0.orig/Xfile + call assert_equal('foam', &filetype) + bwipe! + + call delete('0', 'rf') + call delete('0.orig', 'rf') + call delete('Xfile1Dict') + call delete('Xfile1Dict.something') + call delete('XfileProperties') + call delete('XfileProperties.something') + filetype off +endfunc + +func Test_frm_file() + filetype on + + call writefile(['looks like FORM'], 'Xfile.frm') + split Xfile.frm + call assert_equal('form', &filetype) + bwipe! + + " Test dist#ft#FTfrm() + + let g:filetype_frm = 'form' + split Xfile.frm + call assert_equal('form', &filetype) + bwipe! + unlet g:filetype_frm + + " Visual Basic + + call writefile(['Begin VB.Form Form1'], 'Xfile.frm') + split Xfile.frm + call assert_equal('vb', &filetype) + bwipe! + + call delete('Xfile.frm') + filetype off +endfunc + +func Test_fs_file() + filetype on + + call writefile(['looks like F#'], 'Xfile.fs') + split Xfile.fs + call assert_equal('fsharp', &filetype) + bwipe! + + let g:filetype_fs = 'forth' + split Xfile.fs + call assert_equal('forth', &filetype) + bwipe! + unlet g:filetype_fs + + " Test dist#ft#FTfs() + + " Forth (Gforth) + + call writefile(['( Forth inline comment )'], 'Xfile.fs') + split Xfile.fs + call assert_equal('forth', &filetype) + bwipe! + + call writefile(['.( Forth displayed inline comment )'], 'Xfile.fs') + split Xfile.fs + call assert_equal('forth', &filetype) + bwipe! + + call writefile(['\ Forth line comment'], 'Xfile.fs') + split Xfile.fs + call assert_equal('forth', &filetype) + bwipe! + + " empty line comment - no space required + call writefile(['\'], 'Xfile.fs') + split Xfile.fs + call assert_equal('forth', &filetype) + bwipe! + + call writefile(['\G Forth documentation comment '], 'Xfile.fs') + split Xfile.fs + call assert_equal('forth', &filetype) + bwipe! + + call writefile([': squared ( n -- n^2 )', 'dup * ;'], 'Xfile.fs') + split Xfile.fs + call assert_equal('forth', &filetype) + bwipe! + + call delete('Xfile.fs') + filetype off +endfunc + +func Test_git_file() + filetype on + + call assert_true(mkdir('Xrepo.git', 'p')) + + call writefile([], 'Xrepo.git/HEAD') + split Xrepo.git/HEAD + call assert_equal('', &filetype) + bwipe! + + call writefile(['0000000000000000000000000000000000000000'], 'Xrepo.git/HEAD') + split Xrepo.git/HEAD + call assert_equal('git', &filetype) + bwipe! + + call writefile(['0000000000000000000000000000000000000000000000000000000000000000'], 'Xrepo.git/HEAD') + split Xrepo.git/HEAD + call assert_equal('git', &filetype) + bwipe! + + call writefile(['ref: refs/heads/master'], 'Xrepo.git/HEAD') + split Xrepo.git/HEAD + call assert_equal('git', &filetype) + bwipe! + + call delete('Xrepo.git', 'rf') + filetype off +endfunc + +func Test_hook_file() + filetype on + + call writefile(['[Trigger]', 'this is pacman config'], 'Xfile.hook') + split Xfile.hook + call assert_equal('dosini', &filetype) + bwipe! + + call writefile(['not pacman'], 'Xfile.hook') + split Xfile.hook + call assert_notequal('dosini', &filetype) + bwipe! + + call delete('Xfile.hook') filetype off endfunc @@ -982,111 +1171,6 @@ func Test_m_file() filetype off endfunc -func Test_xpm_file() - filetype on - - call writefile(['this is XPM2'], 'file.xpm') - split file.xpm - call assert_equal('xpm2', &filetype) - bwipe! - - call delete('file.xpm') - filetype off -endfunc - -func Test_fs_file() - filetype on - - call writefile(['looks like F#'], 'Xfile.fs') - split Xfile.fs - call assert_equal('fsharp', &filetype) - bwipe! - - let g:filetype_fs = 'forth' - split Xfile.fs - call assert_equal('forth', &filetype) - bwipe! - unlet g:filetype_fs - - " Test dist#ft#FTfs() - - " Forth (Gforth) - - call writefile(['( Forth inline comment )'], 'Xfile.fs') - split Xfile.fs - call assert_equal('forth', &filetype) - bwipe! - - call writefile(['.( Forth displayed inline comment )'], 'Xfile.fs') - split Xfile.fs - call assert_equal('forth', &filetype) - bwipe! - - call writefile(['\ Forth line comment'], 'Xfile.fs') - split Xfile.fs - call assert_equal('forth', &filetype) - bwipe! - - " empty line comment - no space required - call writefile(['\'], 'Xfile.fs') - split Xfile.fs - call assert_equal('forth', &filetype) - bwipe! - - call writefile(['\G Forth documentation comment '], 'Xfile.fs') - split Xfile.fs - call assert_equal('forth', &filetype) - bwipe! - - call writefile([': squared ( n -- n^2 )', 'dup * ;'], 'Xfile.fs') - split Xfile.fs - call assert_equal('forth', &filetype) - bwipe! - - call delete('Xfile.fs') - filetype off -endfunc - -func Test_dep3patch_file() - filetype on - - call assert_true(mkdir('debian/patches', 'p')) - - " series files are not patches - call writefile(['Description: some awesome patch'], 'debian/patches/series') - split debian/patches/series - call assert_notequal('dep3patch', &filetype) - bwipe! - - " diff/patch files without the right headers should still show up as ft=diff - call writefile([], 'debian/patches/foo.diff') - split debian/patches/foo.diff - call assert_equal('diff', &filetype) - bwipe! - - " Files with the right headers are detected as dep3patch, even if they don't - " have a diff/patch extension - call writefile(['Subject: dep3patches'], 'debian/patches/bar') - split debian/patches/bar - call assert_equal('dep3patch', &filetype) - bwipe! - - " Files in sub-directories are detected - call assert_true(mkdir('debian/patches/s390x', 'p')) - call writefile(['Subject: dep3patches'], 'debian/patches/s390x/bar') - split debian/patches/s390x/bar - call assert_equal('dep3patch', &filetype) - bwipe! - - " The detection stops when seeing the "header end" marker - call writefile(['---', 'Origin: the cloud'], 'debian/patches/baz') - split debian/patches/baz - call assert_notequal('dep3patch', &filetype) - bwipe! - - call delete('debian', 'rf') -endfunc - func Test_patch_file() filetype on @@ -1109,144 +1193,132 @@ func Test_patch_file() filetype off endfunc -func Test_git_file() +func Test_perl_file() filetype on - call assert_true(mkdir('Xrepo.git', 'p')) + " only tests one case, should do more + let lines =<< trim END - call writefile([], 'Xrepo.git/HEAD') - split Xrepo.git/HEAD - call assert_equal('', &filetype) - bwipe! - - call writefile(['0000000000000000000000000000000000000000'], 'Xrepo.git/HEAD') - split Xrepo.git/HEAD - call assert_equal('git', &filetype) - bwipe! + use a + END + call writefile(lines, "Xfile.t") + split Xfile.t + call assert_equal('perl', &filetype) + bwipe - call writefile(['0000000000000000000000000000000000000000000000000000000000000000'], 'Xrepo.git/HEAD') - split Xrepo.git/HEAD - call assert_equal('git', &filetype) - bwipe! - - call writefile(['ref: refs/heads/master'], 'Xrepo.git/HEAD') - split Xrepo.git/HEAD - call assert_equal('git', &filetype) - bwipe! - - call delete('Xrepo.git', 'rf') + call delete('Xfile.t') filetype off endfunc -func Test_foam_file() +func Test_pp_file() filetype on - call assert_true(mkdir('0', 'p')) - call assert_true(mkdir('0.orig', 'p')) - call writefile(['FoamFile {', ' object something;'], 'Xfile1Dict') - split Xfile1Dict - call assert_equal('foam', &filetype) + call writefile(['looks like puppet'], 'Xfile.pp') + split Xfile.pp + call assert_equal('puppet', &filetype) bwipe! - call writefile(['FoamFile {', ' object something;'], 'Xfile1Dict.something') - split Xfile1Dict.something - call assert_equal('foam', &filetype) + let g:filetype_pp = 'pascal' + split Xfile.pp + call assert_equal('pascal', &filetype) bwipe! + unlet g:filetype_pp - call writefile(['FoamFile {', ' object something;'], 'XfileProperties') - split XfileProperties - call assert_equal('foam', &filetype) + " Test dist#ft#FTpp() + call writefile(['{ pascal comment'], 'Xfile.pp') + split Xfile.pp + call assert_equal('pascal', &filetype) bwipe! - call writefile(['FoamFile {', ' object something;'], 'XfileProperties.something') - split XfileProperties.something - call assert_equal('foam', &filetype) + call writefile(['procedure pascal'], 'Xfile.pp') + split Xfile.pp + call assert_equal('pascal', &filetype) bwipe! - call writefile(['FoamFile {', ' object something;'], 'XfileProperties') - split XfileProperties - call assert_equal('foam', &filetype) - bwipe! + call delete('Xfile.pp') + filetype off +endfunc - call writefile(['FoamFile {', ' object something;'], 'XfileProperties.something') - split XfileProperties.something - call assert_equal('foam', &filetype) - bwipe! +func Test_tex_file() + filetype on - call writefile(['FoamFile {', ' object something;'], '0/Xfile') - split 0/Xfile - call assert_equal('foam', &filetype) - bwipe! + " only tests one case, should do more + let lines =<< trim END + % This is a sentence. - call writefile(['FoamFile {', ' object something;'], '0.orig/Xfile') - split 0.orig/Xfile - call assert_equal('foam', &filetype) - bwipe! + This is a sentence. + END + call writefile(lines, "Xfile.tex") + split Xfile.tex + call assert_equal('plaintex', &filetype) + bwipe - call delete('0', 'rf') - call delete('0.orig', 'rf') + call delete('Xfile.tex') filetype off endfunc -func Test_bas_file() +func Test_tf_file() filetype on - call writefile(['looks like BASIC'], 'Xfile.bas') - split Xfile.bas - call assert_equal('basic', &filetype) + call writefile([';;; TF MUD client is super duper cool'], 'Xfile.tf') + split Xfile.tf + call assert_equal('tf', &filetype) bwipe! - " Test dist#ft#FTbas() - - let g:filetype_bas = 'freebasic' - split Xfile.bas - call assert_equal('freebasic', &filetype) + call writefile(['provider "azurerm" {'], 'Xfile.tf') + split Xfile.tf + call assert_equal('terraform', &filetype) bwipe! - unlet g:filetype_bas - " FreeBASIC + call delete('Xfile.tf') + filetype off +endfunc - call writefile(["/' FreeBASIC multiline comment '/"], 'Xfile.bas') - split Xfile.bas - call assert_equal('freebasic', &filetype) - bwipe! +func Test_ts_file() + filetype on - call writefile(['#define TESTING'], 'Xfile.bas') - split Xfile.bas - call assert_equal('freebasic', &filetype) + call writefile(['<?xml version="1.0" encoding="utf-8"?>'], 'Xfile.ts') + split Xfile.ts + call assert_equal('xml', &filetype) bwipe! - call writefile(['option byval'], 'Xfile.bas') - split Xfile.bas - call assert_equal('freebasic', &filetype) + call writefile(['// looks like Typescript'], 'Xfile.ts') + split Xfile.ts + call assert_equal('typescript', &filetype) bwipe! - call writefile(['extern "C"'], 'Xfile.bas') - split Xfile.bas - call assert_equal('freebasic', &filetype) - bwipe! + call delete('Xfile.ts') + filetype off +endfunc - " QB64 +func Test_ttl_file() + filetype on - call writefile(['$LET TESTING = 1'], 'Xfile.bas') - split Xfile.bas - call assert_equal('qb64', &filetype) + call writefile(['@base <http://example.org/> .'], 'Xfile.ttl') + split Xfile.ttl + call assert_equal('turtle', &filetype) bwipe! - call writefile(['OPTION _EXPLICIT'], 'Xfile.bas') - split Xfile.bas - call assert_equal('qb64', &filetype) + call writefile(['looks like Tera Term Language'], 'Xfile.ttl') + split Xfile.ttl + call assert_equal('teraterm', &filetype) bwipe! - " Visual Basic + call delete('Xfile.ttl') + filetype off +endfunc - call writefile(['Attribute VB_NAME = "Testing"'], 'Xfile.bas') - split Xfile.bas - call assert_equal('vb', &filetype) +func Test_xpm_file() + filetype on + + call writefile(['this is XPM2'], 'file.xpm') + split file.xpm + call assert_equal('xpm2', &filetype) bwipe! - call delete('Xfile.bas') + call delete('file.xpm') filetype off endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_findfile.vim b/src/nvim/testdir/test_findfile.vim index 5a20475d3d..1684c5d30a 100644 --- a/src/nvim/testdir/test_findfile.vim +++ b/src/nvim/testdir/test_findfile.vim @@ -226,4 +226,26 @@ func Test_find_cmd() call assert_fails('tabfind', 'E471:') endfunc +func Test_find_non_existing_path() + new + let save_path = &path + let save_dir = getcwd() + call mkdir('dir1/dir2', 'p') + call writefile([], 'dir1/file.txt') + call writefile([], 'dir1/dir2/base.txt') + call chdir('dir1/dir2') + e base.txt + set path=../include + + call assert_fails(':find file.txt', 'E345:') + + call chdir(save_dir) + bw! + call delete('dir1/dir2/base.txt', 'rf') + call delete('dir1/dir2', 'rf') + call delete('dir1/file.txt', 'rf') + call delete('dir1', 'rf') + let &path = save_path +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_fnamemodify.vim b/src/nvim/testdir/test_fnamemodify.vim index 411f7ebbb3..5ae2a5ee17 100644 --- a/src/nvim/testdir/test_fnamemodify.vim +++ b/src/nvim/testdir/test_fnamemodify.vim @@ -3,8 +3,10 @@ func Test_fnamemodify() let save_home = $HOME let save_shell = &shell + let save_shellslash = &shellslash let $HOME = fnamemodify('.', ':p:h:h') set shell=sh + set shellslash call assert_equal('/', fnamemodify('.', ':p')[-1:]) call assert_equal('r', fnamemodify('.', ':p:h')[-1:]) @@ -27,6 +29,21 @@ func Test_fnamemodify() call assert_equal('fb2.tar.gz', fnamemodify('abc.fb2.tar.gz', ':e:e:e')) call assert_equal('fb2.tar.gz', fnamemodify('abc.fb2.tar.gz', ':e:e:e:e')) call assert_equal('tar', fnamemodify('abc.fb2.tar.gz', ':e:e:r')) + call assert_equal(getcwd(), fnamemodify('', ':p:h')) + + let cwd = getcwd() + call chdir($HOME) + call assert_equal('foobar', fnamemodify('~/foobar', ':~:.')) + call chdir(cwd) + call mkdir($HOME . '/XXXXXXXX/a', 'p') + call mkdir($HOME . '/XXXXXXXX/b', 'p') + call chdir($HOME . '/XXXXXXXX/a/') + call assert_equal('foo', fnamemodify($HOME . '/XXXXXXXX/a/foo', ':p:~:.')) + call assert_equal('~/XXXXXXXX/b/foo', fnamemodify($HOME . '/XXXXXXXX/b/foo', ':p:~:.')) + call mkdir($HOME . '/XXXXXXXX/a.ext', 'p') + call assert_equal('~/XXXXXXXX/a.ext/foo', fnamemodify($HOME . '/XXXXXXXX/a.ext/foo', ':p:~:.')) + call chdir(cwd) + call delete($HOME . '/XXXXXXXX', 'rf') call assert_equal('''abc def''', fnamemodify('abc def', ':S')) call assert_equal('''abc" "def''', fnamemodify('abc" "def', ':S')) @@ -44,6 +61,7 @@ func Test_fnamemodify() let $HOME = save_home let &shell = save_shell + let &shellslash = save_shellslash endfunc func Test_fnamemodify_er() @@ -73,6 +91,7 @@ func Test_fnamemodify_er() call assert_equal('b.c', fnamemodify('a.b.c.d.e', ':r:r:e:e:e')) call assert_equal('b.c', fnamemodify('a.b.c.d.e', ':r:r:e:e:e:e')) + call assert_equal('', fnamemodify('', ':p:t')) call assert_equal('', fnamemodify(v:_null_string, v:_null_string)) endfunc diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim index 0edbeb420a..438bed51c6 100644 --- a/src/nvim/testdir/test_functions.vim +++ b/src/nvim/testdir/test_functions.vim @@ -376,6 +376,25 @@ func Test_pathshorten() call assert_equal('~.f/bar', pathshorten('~.foo/bar')) call assert_equal('.~f/bar', pathshorten('.~foo/bar')) call assert_equal('~/f/bar', pathshorten('~/foo/bar')) + call assert_fails('call pathshorten([])', 'E730:') + + " test pathshorten with optional variable to set preferred size of shortening + call assert_equal('', pathshorten('', 2)) + call assert_equal('foo', pathshorten('foo', 2)) + call assert_equal('/foo', pathshorten('/foo', 2)) + call assert_equal('fo/', pathshorten('foo/', 2)) + call assert_equal('fo/bar', pathshorten('foo/bar', 2)) + call assert_equal('fo/ba/foobar', pathshorten('foo/bar/foobar', 2)) + call assert_equal('/fo/ba/foobar', pathshorten('/foo/bar/foobar', 2)) + call assert_equal('.fo/bar', pathshorten('.foo/bar', 2)) + call assert_equal('~fo/bar', pathshorten('~foo/bar', 2)) + call assert_equal('~.fo/bar', pathshorten('~.foo/bar', 2)) + call assert_equal('.~fo/bar', pathshorten('.~foo/bar', 2)) + call assert_equal('~/fo/bar', pathshorten('~/foo/bar', 2)) + call assert_fails('call pathshorten([],2)', 'E730:') + call assert_notequal('~/fo/bar', pathshorten('~/foo/bar', 3)) + call assert_equal('~/foo/bar', pathshorten('~/foo/bar', 3)) + call assert_equal('~/f/bar', pathshorten('~/foo/bar', 0)) endfunc func Test_strpart() @@ -1377,6 +1396,36 @@ func Test_func_exists_on_reload() delfunc ExistingFunction endfunc +func Test_platform_name() + " The system matches at most only one name. + let names = ['amiga', 'beos', 'bsd', 'hpux', 'linux', 'mac', 'qnx', 'sun', 'vms', 'win32', 'win32unix'] + call assert_inrange(0, 1, len(filter(copy(names), 'has(v:val)'))) + + " Is Unix? + call assert_equal(has('beos'), has('beos') && has('unix')) + call assert_equal(has('bsd'), has('bsd') && has('unix')) + call assert_equal(has('hpux'), has('hpux') && has('unix')) + call assert_equal(has('linux'), has('linux') && has('unix')) + call assert_equal(has('mac'), has('mac') && has('unix')) + call assert_equal(has('qnx'), has('qnx') && has('unix')) + call assert_equal(has('sun'), has('sun') && has('unix')) + call assert_equal(has('win32'), has('win32') && !has('unix')) + call assert_equal(has('win32unix'), has('win32unix') && has('unix')) + + if has('unix') && executable('uname') + let uname = system('uname') + call assert_equal(uname =~? 'BeOS', has('beos')) + " GNU userland on BSD kernels (e.g., GNU/kFreeBSD) don't have BSD defined + call assert_equal(uname =~? '\%(GNU/k\w\+\)\@<!BSD\|DragonFly', has('bsd')) + call assert_equal(uname =~? 'HP-UX', has('hpux')) + call assert_equal(uname =~? 'Linux', has('linux')) + call assert_equal(uname =~? 'Darwin', has('mac')) + call assert_equal(uname =~? 'QNX', has('qnx')) + call assert_equal(uname =~? 'SunOS', has('sun')) + call assert_equal(uname =~? 'CYGWIN\|MSYS', has('win32unix')) + endif +endfunc + sandbox function Fsandbox() normal ix endfunc @@ -1519,24 +1568,31 @@ func Test_libcall_libcallnr() let libc = 'msvcrt.dll' elseif has('mac') let libc = 'libSystem.B.dylib' - elseif system('uname -s') =~ 'SunOS' - " Set the path to libc.so according to the architecture. - let test_bits = system('file ' . GetVimProg()) - let test_arch = system('uname -p') - if test_bits =~ '64-bit' && test_arch =~ 'sparc' - let libc = '/usr/lib/sparcv9/libc.so' - elseif test_bits =~ '64-bit' && test_arch =~ 'i386' - let libc = '/usr/lib/amd64/libc.so' + elseif executable('ldd') + let libc = matchstr(split(system('ldd ' . GetVimProg())), '/libc\.so\>') + endif + if get(l:, 'libc', '') ==# '' + " On Unix, libc.so can be in various places. + if has('linux') + " There is not documented but regarding the 1st argument of glibc's + " dlopen an empty string and nullptr are equivalent, so using an empty + " string for the 1st argument of libcall allows to call functions. + let libc = '' + elseif has('sun') + " Set the path to libc.so according to the architecture. + let test_bits = system('file ' . GetVimProg()) + let test_arch = system('uname -p') + if test_bits =~ '64-bit' && test_arch =~ 'sparc' + let libc = '/usr/lib/sparcv9/libc.so' + elseif test_bits =~ '64-bit' && test_arch =~ 'i386' + let libc = '/usr/lib/amd64/libc.so' + else + let libc = '/usr/lib/libc.so' + endif else - let libc = '/usr/lib/libc.so' + " Unfortunately skip this test until a good way is found. + return endif - elseif system('uname -s') =~ 'OpenBSD' - let libc = 'libc.so' - else - " On Unix, libc.so can be in various places. - " Interestingly, using an empty string for the 1st argument of libcall - " allows to call functions from libc which is not documented. - let libc = '' endif if has('win32') @@ -1675,6 +1731,33 @@ func Test_nr2char() call assert_equal("\x80\xfc\b\xfd\x80\xfeX\x80\xfeX\x80\xfeX\x80\xfeX\x80\xfeX", eval('"\<M-' .. nr2char(0x40000000) .. '>"')) endfunc +" Test for getcurpos() and setpos() +func Test_getcurpos_setpos() + new + call setline(1, ['012345678', '012345678']) + normal gg6l + let sp = getcurpos() + normal 0 + call setpos('.', sp) + normal jyl + call assert_equal('6', @") + call assert_equal(-1, setpos('.', v:_null_list)) + call assert_equal(-1, setpos('.', {})) + + let winid = win_getid() + normal G$ + let pos = getcurpos() + wincmd w + call assert_equal(pos, getcurpos(winid)) + + wincmd w + close! + + call assert_equal(getcurpos(), getcurpos(0)) + call assert_equal([0, 0, 0, 0, 0], getcurpos(-1)) + call assert_equal([0, 0, 0, 0, 0], getcurpos(1999)) +endfunc + func HasDefault(msg = 'msg') return a:msg endfunc diff --git a/src/nvim/testdir/test_gf.vim b/src/nvim/testdir/test_gf.vim index 43efd6248e..589899f532 100644 --- a/src/nvim/testdir/test_gf.vim +++ b/src/nvim/testdir/test_gf.vim @@ -19,11 +19,7 @@ func Test_gf_url() call search("^second") call search("URL") call assert_equal("URL://machine.name/tmp/vimtest2b", expand("<cfile>")) - if has("ebcdic") - set isf=@,240-249,/,.,-,_,+,,,$,:,~,\ - else - set isf=@,48-57,/,.,-,_,+,,,$,~,\ - endif + set isf=@,48-57,/,.,-,_,+,,,$,~,\ call search("^third") call search("name") call assert_equal("URL:\\\\machine.name\\vimtest2c", expand("<cfile>")) @@ -76,11 +72,7 @@ endfunc " Test for invoking 'gf' on a ${VAR} variable func Test_gf() - if has("ebcdic") - set isfname=@,240-249,/,.,-,_,+,,,$,:,~,{,} - else - set isfname=@,48-57,/,.,-,_,+,,,$,:,~,{,} - endif + set isfname=@,48-57,/,.,-,_,+,,,$,:,~,{,} call writefile(["Test for gf command"], "Xtest1") if has("unix") diff --git a/src/nvim/testdir/test_ins_complete.vim b/src/nvim/testdir/test_ins_complete.vim index f066d842b4..186fa8871f 100644 --- a/src/nvim/testdir/test_ins_complete.vim +++ b/src/nvim/testdir/test_ins_complete.vim @@ -109,7 +109,7 @@ func s:CompleteDone_CompleteFuncNone( findstart, base ) return v:none endfunc -function! s:CompleteDone_CompleteFuncDict( findstart, base ) +func s:CompleteDone_CompleteFuncDict( findstart, base ) if a:findstart return 0 endif @@ -126,7 +126,7 @@ function! s:CompleteDone_CompleteFuncDict( findstart, base ) \ } \ ] \ } -endfunction +endfunc func s:CompleteDone_CheckCompletedItemNone() let s:called_completedone = 1 diff --git a/src/nvim/testdir/test_lambda.vim b/src/nvim/testdir/test_lambda.vim index 72ddbcf6dc..c1fe47d1c9 100644 --- a/src/nvim/testdir/test_lambda.vim +++ b/src/nvim/testdir/test_lambda.vim @@ -1,24 +1,24 @@ " Test for lambda and closure -function! Test_lambda_feature() +func Test_lambda_feature() call assert_equal(1, has('lambda')) -endfunction +endfunc -function! Test_lambda_with_filter() +func Test_lambda_with_filter() let s:x = 2 call assert_equal([2, 3], filter([1, 2, 3], {i, v -> v >= s:x})) -endfunction +endfunc -function! Test_lambda_with_map() +func Test_lambda_with_map() let s:x = 1 call assert_equal([2, 3, 4], map([1, 2, 3], {i, v -> v + s:x})) -endfunction +endfunc -function! Test_lambda_with_sort() +func Test_lambda_with_sort() call assert_equal([1, 2, 3, 4, 7], sort([3,7,2,1,4], {a, b -> a - b})) -endfunction +endfunc -function! Test_lambda_with_timer() +func Test_lambda_with_timer() if !has('timers') return endif @@ -54,10 +54,10 @@ function! Test_lambda_with_timer() call assert_true(s:n > m) endfunc -function! Test_lambda_with_partial() +func Test_lambda_with_partial() let l:Cb = function({... -> ['zero', a:1, a:2, a:3]}, ['one', 'two']) call assert_equal(['zero', 'one', 'two', 'three'], l:Cb('three')) -endfunction +endfunc function Test_lambda_fails() call assert_equal(3, {a, b -> a + b}(1, 2)) @@ -70,59 +70,59 @@ func Test_not_lambda() call assert_equal('foo', x['>']) endfunc -function! Test_lambda_capture_by_reference() +func Test_lambda_capture_by_reference() let v = 1 let l:F = {x -> x + v} let v = 2 call assert_equal(12, l:F(10)) -endfunction +endfunc -function! Test_lambda_side_effect() - function! s:update_and_return(arr) +func Test_lambda_side_effect() + func! s:update_and_return(arr) let a:arr[1] = 5 return a:arr - endfunction + endfunc - function! s:foo(arr) + func! s:foo(arr) return {-> s:update_and_return(a:arr)} - endfunction + endfunc let arr = [3,2,1] call assert_equal([3, 5, 1], s:foo(arr)()) -endfunction +endfunc -function! Test_lambda_refer_local_variable_from_other_scope() - function! s:foo(X) +func Test_lambda_refer_local_variable_from_other_scope() + func! s:foo(X) return a:X() " refer l:x in s:bar() - endfunction + endfunc - function! s:bar() + func! s:bar() let x = 123 return s:foo({-> x}) - endfunction + endfunc call assert_equal(123, s:bar()) -endfunction +endfunc -function! Test_lambda_do_not_share_local_variable() - function! s:define_funcs() +func Test_lambda_do_not_share_local_variable() + func! s:define_funcs() let l:One = {-> split(execute("let a = 'abc' | echo a"))[0]} let l:Two = {-> exists("a") ? a : "no"} return [l:One, l:Two] - endfunction + endfunc let l:F = s:define_funcs() call assert_equal('no', l:F[1]()) call assert_equal('abc', l:F[0]()) call assert_equal('no', l:F[1]()) -endfunction +endfunc -function! Test_lambda_closure_counter() - function! s:foo() +func Test_lambda_closure_counter() + func! s:foo() let x = 0 return {-> [execute("let x += 1"), x][-1]} - endfunction + endfunc let l:F = s:foo() call garbagecollect() @@ -130,52 +130,52 @@ function! Test_lambda_closure_counter() call assert_equal(2, l:F()) call assert_equal(3, l:F()) call assert_equal(4, l:F()) -endfunction +endfunc -function! Test_lambda_with_a_var() - function! s:foo() +func Test_lambda_with_a_var() + func! s:foo() let x = 2 return {... -> a:000 + [x]} - endfunction - function! s:bar() + endfunc + func! s:bar() return s:foo()(1) - endfunction + endfunc call assert_equal([1, 2], s:bar()) -endfunction +endfunc -function! Test_lambda_call_lambda_from_lambda() - function! s:foo(x) +func Test_lambda_call_lambda_from_lambda() + func! s:foo(x) let l:F1 = {-> {-> a:x}} return {-> l:F1()} - endfunction + endfunc let l:F = s:foo(1) call assert_equal(1, l:F()()) -endfunction +endfunc -function! Test_lambda_delfunc() - function! s:gen() +func Test_lambda_delfunc() + func! s:gen() let pl = l: let l:Foo = {-> get(pl, "Foo", get(pl, "Bar", {-> 0}))} let l:Bar = l:Foo delfunction l:Foo return l:Bar - endfunction + endfunc let l:F = s:gen() call assert_fails(':call l:F()', 'E933:') -endfunction +endfunc -function! Test_lambda_scope() - function! s:NewCounter() +func Test_lambda_scope() + func! s:NewCounter() let c = 0 return {-> [execute('let c += 1'), c][-1]} - endfunction + endfunc - function! s:NewCounter2() + func! s:NewCounter2() return {-> [execute('let c += 100'), c][-1]} - endfunction + endfunc let l:C = s:NewCounter() let l:D = s:NewCounter2() @@ -183,37 +183,37 @@ function! Test_lambda_scope() call assert_equal(1, l:C()) call assert_fails(':call l:D()', 'E121:') call assert_equal(2, l:C()) -endfunction +endfunc -function! Test_lambda_share_scope() - function! s:New() +func Test_lambda_share_scope() + func! s:New() let c = 0 let l:Inc0 = {-> [execute('let c += 1'), c][-1]} let l:Dec0 = {-> [execute('let c -= 1'), c][-1]} return [l:Inc0, l:Dec0] - endfunction + endfunc let [l:Inc, l:Dec] = s:New() call assert_equal(1, l:Inc()) call assert_equal(2, l:Inc()) call assert_equal(1, l:Dec()) -endfunction +endfunc -function! Test_lambda_circular_reference() - function! s:Foo() +func Test_lambda_circular_reference() + func! s:Foo() let d = {} let d.f = {-> d} return d.f - endfunction + endfunc call s:Foo() call garbagecollect() let i = 0 | while i < 10000 | call s:Foo() | let i+= 1 | endwhile call garbagecollect() -endfunction +endfunc -function! Test_lambda_combination() +func Test_lambda_combination() call assert_equal(2, {x -> {x -> x}}(1)(2)) call assert_equal(10, {y -> {x -> x(y)(10)}({y -> y})}({z -> z})) call assert_equal(5.0, {x -> {y -> x / y}}(10)(2.0)) @@ -226,17 +226,17 @@ function! Test_lambda_combination() let Z = {f -> {x -> f({y -> x(x)(y)})}({x -> f({y -> x(x)(y)})})} let Fact = {f -> {x -> x == 0 ? 1 : x * f(x - 1)}} call assert_equal(120, Z(Fact)(5)) -endfunction +endfunc -function! Test_closure_counter() - function! s:foo() +func Test_closure_counter() + func! s:foo() let x = 0 - function! s:bar() closure + func! s:bar() closure let x += 1 return x - endfunction + endfunc return function('s:bar') - endfunction + endfunc let l:F = s:foo() call garbagecollect() @@ -244,30 +244,30 @@ function! Test_closure_counter() call assert_equal(2, l:F()) call assert_equal(3, l:F()) call assert_equal(4, l:F()) -endfunction +endfunc -function! Test_closure_unlet() - function! s:foo() +func Test_closure_unlet() + func! s:foo() let x = 1 - function! s:bar() closure + func! s:bar() closure unlet x - endfunction + endfunc call s:bar() return l: - endfunction + endfunc call assert_false(has_key(s:foo(), 'x')) call garbagecollect() -endfunction +endfunc -function! LambdaFoo() +func LambdaFoo() let x = 0 - function! LambdaBar() closure + func! LambdaBar() closure let x += 1 return x - endfunction + endfunc return function('LambdaBar') -endfunction +endfunc func Test_closure_refcount() let g:Count = LambdaFoo() diff --git a/src/nvim/testdir/test_listdict.vim b/src/nvim/testdir/test_listdict.vim index 114cca7ec0..aa66d86af1 100644 --- a/src/nvim/testdir/test_listdict.vim +++ b/src/nvim/testdir/test_listdict.vim @@ -516,22 +516,22 @@ func Test_dict_lock_operator() endfunc " No remove() of write-protected scope-level variable -func! Tfunc(this_is_a_long_parameter_name) +func Tfunc1(this_is_a_long_parameter_name) call assert_fails("call remove(a:, 'this_is_a_long_parameter_name')", 'E742') -endfun +endfunc func Test_dict_scope_var_remove() - call Tfunc('testval') + call Tfunc1('testval') endfunc " No extend() of write-protected scope-level variable func Test_dict_scope_var_extend() call assert_fails("call extend(a:, {'this_is_a_long_parameter_name': 1234})", 'E742') endfunc -func! Tfunc(this_is_a_long_parameter_name) +func Tfunc2(this_is_a_long_parameter_name) call assert_fails("call extend(a:, {'this_is_a_long_parameter_name': 1234})", 'E742') endfunc func Test_dict_scope_var_extend_overwrite() - call Tfunc('testval') + call Tfunc2('testval') endfunc " No :unlet of variable in locked scope diff --git a/src/nvim/testdir/test_marks.vim b/src/nvim/testdir/test_marks.vim index 6b9904ec0a..00ee8f6d6a 100644 --- a/src/nvim/testdir/test_marks.vim +++ b/src/nvim/testdir/test_marks.vim @@ -1,6 +1,6 @@ " Test that a deleted mark is restored after delete-undo-redo-undo. -function! Test_Restore_DelMark() +func Test_Restore_DelMark() enew! call append(0, [" textline A", " textline B", " textline C"]) normal! 2gg @@ -11,10 +11,10 @@ function! Test_Restore_DelMark() call assert_equal(2, pos[1]) call assert_equal(1, pos[2]) enew! -endfunction +endfunc " Test that CTRL-A and CTRL-X updates last changed mark '[, ']. -function! Test_Incr_Marks() +func Test_Incr_Marks() enew! call append(0, ["123 123 123", "123 123 123", "123 123 123"]) normal! gg @@ -23,7 +23,7 @@ function! Test_Incr_Marks() call assert_equal("123 XXXXXXX", getline(2)) call assert_equal("XXX 123 123", getline(3)) enew! -endfunction +endfunc func Test_previous_jump_mark() new diff --git a/src/nvim/testdir/test_matchadd_conceal.vim b/src/nvim/testdir/test_matchadd_conceal.vim index 2cbaf5cb76..29a2c30b0d 100644 --- a/src/nvim/testdir/test_matchadd_conceal.vim +++ b/src/nvim/testdir/test_matchadd_conceal.vim @@ -7,7 +7,7 @@ source shared.vim source term_util.vim source view_util.vim -function! Test_simple_matchadd() +func Test_simple_matchadd() new 1put='# This is a Test' @@ -333,7 +333,7 @@ func Test_matchadd_and_syn_conceal() call assert_notequal(screenattr(1, 10) , screenattr(1, 11)) call assert_notequal(screenattr(1, 11) , screenattr(1, 12)) call assert_equal(screenattr(1, 11) , screenattr(1, 32)) -endfunction +endfunc func Test_cursor_column_in_concealed_line_after_window_scroll() CheckRunVimInTerminal diff --git a/src/nvim/testdir/test_matchadd_conceal_utf8.vim b/src/nvim/testdir/test_matchadd_conceal_utf8.vim index 7bfac13ad8..1d0c740734 100644 --- a/src/nvim/testdir/test_matchadd_conceal_utf8.vim +++ b/src/nvim/testdir/test_matchadd_conceal_utf8.vim @@ -3,19 +3,19 @@ if !has('conceal') finish endif -function! s:screenline(lnum) abort +func s:screenline(lnum) abort let line = [] for c in range(1, winwidth(0)) call add(line, nr2char(a:lnum->screenchar(c))) endfor return s:trim(join(line, '')) -endfunction +endfunc -function! s:trim(str) abort +func s:trim(str) abort return matchstr(a:str,'^\s*\zs.\{-}\ze\s*$') -endfunction +endfunc -function! Test_match_using_multibyte_conceal_char() +func Test_match_using_multibyte_conceal_char() new setlocal concealcursor=n conceallevel=1 diff --git a/src/nvim/testdir/test_matchfuzzy.vim b/src/nvim/testdir/test_matchfuzzy.vim new file mode 100644 index 0000000000..abcc9b40c1 --- /dev/null +++ b/src/nvim/testdir/test_matchfuzzy.vim @@ -0,0 +1,248 @@ +" Tests for fuzzy matching + +source shared.vim +source check.vim + +" Test for matchfuzzy() +func Test_matchfuzzy() + call assert_fails('call matchfuzzy(10, "abc")', 'E686:') + " Needs v8.2.1183; match the final error that's thrown for now + " call assert_fails('call matchfuzzy(["abc"], [])', 'E730:') + call assert_fails('call matchfuzzy(["abc"], [])', 'E475:') + call assert_fails("let x = matchfuzzy(v:_null_list, 'foo')", 'E686:') + call assert_fails('call matchfuzzy(["abc"], v:_null_string)', 'E475:') + call assert_equal([], matchfuzzy([], 'abc')) + call assert_equal([], matchfuzzy(['abc'], '')) + call assert_equal(['abc'], matchfuzzy(['abc', 10], 'ac')) + call assert_equal([], matchfuzzy([10, 20], 'ac')) + call assert_equal(['abc'], matchfuzzy(['abc'], 'abc')) + call assert_equal(['crayon', 'camera'], matchfuzzy(['camera', 'crayon'], 'cra')) + call assert_equal(['aabbaa', 'aaabbbaaa', 'aaaabbbbaaaa', 'aba'], matchfuzzy(['aba', 'aabbaa', 'aaabbbaaa', 'aaaabbbbaaaa'], 'aa')) + call assert_equal(['one'], matchfuzzy(['one', 'two'], 'one')) + call assert_equal(['oneTwo', 'onetwo'], matchfuzzy(['onetwo', 'oneTwo'], 'oneTwo')) + call assert_equal(['onetwo', 'one_two'], matchfuzzy(['onetwo', 'one_two'], 'oneTwo')) + call assert_equal(['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'], matchfuzzy(['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'], 'aa')) + call assert_equal(256, matchfuzzy([repeat('a', 256)], repeat('a', 256))[0]->len()) + call assert_equal([], matchfuzzy([repeat('a', 300)], repeat('a', 257))) + " matches with same score should not be reordered + let l = ['abc1', 'abc2', 'abc3'] + call assert_equal(l, l->matchfuzzy('abc')) + + " Tests for match preferences + " preference for camel case match + call assert_equal(['oneTwo', 'onetwo'], ['onetwo', 'oneTwo']->matchfuzzy('onetwo')) + " preference for match after a separator (_ or space) + call assert_equal(['onetwo', 'one_two', 'one two'], ['onetwo', 'one_two', 'one two']->matchfuzzy('onetwo')) + " preference for leading letter match + call assert_equal(['onetwo', 'xonetwo'], ['xonetwo', 'onetwo']->matchfuzzy('onetwo')) + " preference for sequential match + call assert_equal(['onetwo', 'oanbectdweo'], ['oanbectdweo', 'onetwo']->matchfuzzy('onetwo')) + " non-matching leading letter(s) penalty + call assert_equal(['xonetwo', 'xxonetwo'], ['xxonetwo', 'xonetwo']->matchfuzzy('onetwo')) + " total non-matching letter(s) penalty + call assert_equal(['one', 'onex', 'onexx'], ['onexx', 'one', 'onex']->matchfuzzy('one')) + " prefer complete matches over separator matches + call assert_equal(['.vim/vimrc', '.vim/vimrc_colors', '.vim/v_i_m_r_c'], ['.vim/vimrc', '.vim/vimrc_colors', '.vim/v_i_m_r_c']->matchfuzzy('vimrc')) + " gap penalty + call assert_equal(['xxayybxxxx', 'xxayyybxxx', 'xxayyyybxx'], ['xxayyyybxx', 'xxayyybxxx', 'xxayybxxxx']->matchfuzzy('ab')) + " path separator vs word separator + call assert_equal(['color/setup.vim', 'color\\setup.vim', 'color setup.vim', 'color_setup.vim', 'colorsetup.vim'], matchfuzzy(['colorsetup.vim', 'color setup.vim', 'color/setup.vim', 'color_setup.vim', 'color\\setup.vim'], 'setup.vim')) + + " match multiple words (separated by space) + call assert_equal(['foo bar baz'], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzy('baz foo')) + call assert_equal([], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzy('one two')) + call assert_equal([], ['foo bar']->matchfuzzy(" \t ")) + + " test for matching a sequence of words + call assert_equal(['bar foo'], ['foo bar', 'bar foo', 'foobar', 'barfoo']->matchfuzzy('bar foo', {'matchseq' : 1})) + call assert_equal([#{text: 'two one'}], [#{text: 'one two'}, #{text: 'two one'}]->matchfuzzy('two one', #{key: 'text', matchseq: v:true})) + + %bw! + eval ['somebuf', 'anotherone', 'needle', 'yetanotherone']->map({_, v -> bufadd(v) + bufload(v)}) + let l = getbufinfo()->map({_, v -> v.name})->matchfuzzy('ndl') + call assert_equal(1, len(l)) + call assert_match('needle', l[0]) + + " Test for fuzzy matching dicts + let l = [{'id' : 5, 'val' : 'crayon'}, {'id' : 6, 'val' : 'camera'}] + call assert_equal([{'id' : 6, 'val' : 'camera'}], matchfuzzy(l, 'cam', {'text_cb' : {v -> v.val}})) + call assert_equal([{'id' : 6, 'val' : 'camera'}], matchfuzzy(l, 'cam', {'key' : 'val'})) + call assert_equal([], matchfuzzy(l, 'day', {'text_cb' : {v -> v.val}})) + call assert_equal([], matchfuzzy(l, 'day', {'key' : 'val'})) + call assert_fails("let x = matchfuzzy(l, 'cam', 'random')", 'E715:') + call assert_equal([], matchfuzzy(l, 'day', {'text_cb' : {v -> []}})) + call assert_equal([], matchfuzzy(l, 'day', {'text_cb' : {v -> 1}})) + call assert_fails("let x = matchfuzzy(l, 'day', {'text_cb' : {a, b -> 1}})", 'E119:') + call assert_equal([], matchfuzzy(l, 'cam')) + " Nvim's callback implementation is different, so E6000 is expected instead, + " but we need v8.2.1183 to assert it + " call assert_fails("let x = matchfuzzy(l, 'cam', {'text_cb' : []})", 'E921:') + " call assert_fails("let x = matchfuzzy(l, 'cam', {'text_cb' : []})", 'E6000:') + call assert_fails("let x = matchfuzzy(l, 'cam', {'text_cb' : []})", 'E475:') + " call assert_fails("let x = matchfuzzy(l, 'foo', {'key' : []})", 'E730:') + call assert_fails("let x = matchfuzzy(l, 'foo', {'key' : []})", 'E475:') + call assert_fails("let x = matchfuzzy(l, 'cam', v:_null_dict)", 'E715:') + call assert_fails("let x = matchfuzzy(l, 'foo', {'key' : v:_null_string})", 'E475:') + " Nvim doesn't have null functions + " call assert_fails("let x = matchfuzzy(l, 'foo', {'text_cb' : test_null_function()})", 'E475:') + " matches with same score should not be reordered + let l = [#{text: 'abc', id: 1}, #{text: 'abc', id: 2}, #{text: 'abc', id: 3}] + call assert_equal(l, l->matchfuzzy('abc', #{key: 'text'})) + + let l = [{'id' : 5, 'name' : 'foo'}, {'id' : 6, 'name' : []}, {'id' : 7}] + call assert_fails("let x = matchfuzzy(l, 'foo', {'key' : 'name'})", 'E730:') + + " Test in latin1 encoding + let save_enc = &encoding + " Nvim supports utf-8 encoding only + " set encoding=latin1 + call assert_equal(['abc'], matchfuzzy(['abc'], 'abc')) + let &encoding = save_enc +endfunc + +" Test for the matchfuzzypos() function +func Test_matchfuzzypos() + call assert_equal([['curl', 'world'], [[2,3], [2,3]], [128, 127]], matchfuzzypos(['world', 'curl'], 'rl')) + call assert_equal([['curl', 'world'], [[2,3], [2,3]], [128, 127]], matchfuzzypos(['world', 'one', 'curl'], 'rl')) + call assert_equal([['hello', 'hello world hello world'], + \ [[0, 1, 2, 3, 4], [0, 1, 2, 3, 4]], [275, 257]], + \ matchfuzzypos(['hello world hello world', 'hello', 'world'], 'hello')) + call assert_equal([['aaaaaaa'], [[0, 1, 2]], [191]], matchfuzzypos(['aaaaaaa'], 'aaa')) + call assert_equal([['a b'], [[0, 3]], [219]], matchfuzzypos(['a b'], 'a b')) + call assert_equal([['a b'], [[0, 3]], [219]], matchfuzzypos(['a b'], 'a b')) + call assert_equal([['a b'], [[0]], [112]], matchfuzzypos(['a b'], ' a ')) + call assert_equal([[], [], []], matchfuzzypos(['a b'], ' ')) + call assert_equal([[], [], []], matchfuzzypos(['world', 'curl'], 'ab')) + let x = matchfuzzypos([repeat('a', 256)], repeat('a', 256)) + call assert_equal(range(256), x[1][0]) + call assert_equal([[], [], []], matchfuzzypos([repeat('a', 300)], repeat('a', 257))) + call assert_equal([[], [], []], matchfuzzypos([], 'abc')) + + " match in a long string + call assert_equal([[repeat('x', 300) .. 'abc'], [[300, 301, 302]], [-135]], + \ matchfuzzypos([repeat('x', 300) .. 'abc'], 'abc')) + + " preference for camel case match + call assert_equal([['xabcxxaBc'], [[6, 7, 8]], [189]], matchfuzzypos(['xabcxxaBc'], 'abc')) + " preference for match after a separator (_ or space) + call assert_equal([['xabx_ab'], [[5, 6]], [145]], matchfuzzypos(['xabx_ab'], 'ab')) + " preference for leading letter match + call assert_equal([['abcxabc'], [[0, 1]], [150]], matchfuzzypos(['abcxabc'], 'ab')) + " preference for sequential match + call assert_equal([['aobncedone'], [[7, 8, 9]], [158]], matchfuzzypos(['aobncedone'], 'one')) + " best recursive match + call assert_equal([['xoone'], [[2, 3, 4]], [168]], matchfuzzypos(['xoone'], 'one')) + + " match multiple words (separated by space) + call assert_equal([['foo bar baz'], [[8, 9, 10, 0, 1, 2]], [369]], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('baz foo')) + call assert_equal([[], [], []], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('one two')) + call assert_equal([[], [], []], ['foo bar']->matchfuzzypos(" \t ")) + call assert_equal([['grace'], [[1, 2, 3, 4, 2, 3, 4, 0, 1, 2, 3, 4]], [657]], ['grace']->matchfuzzypos('race ace grace')) + + let l = [{'id' : 5, 'val' : 'crayon'}, {'id' : 6, 'val' : 'camera'}] + call assert_equal([[{'id' : 6, 'val' : 'camera'}], [[0, 1, 2]], [192]], + \ matchfuzzypos(l, 'cam', {'text_cb' : {v -> v.val}})) + call assert_equal([[{'id' : 6, 'val' : 'camera'}], [[0, 1, 2]], [192]], + \ matchfuzzypos(l, 'cam', {'key' : 'val'})) + call assert_equal([[], [], []], matchfuzzypos(l, 'day', {'text_cb' : {v -> v.val}})) + call assert_equal([[], [], []], matchfuzzypos(l, 'day', {'key' : 'val'})) + call assert_fails("let x = matchfuzzypos(l, 'cam', 'random')", 'E715:') + call assert_equal([[], [], []], matchfuzzypos(l, 'day', {'text_cb' : {v -> []}})) + call assert_equal([[], [], []], matchfuzzypos(l, 'day', {'text_cb' : {v -> 1}})) + call assert_fails("let x = matchfuzzypos(l, 'day', {'text_cb' : {a, b -> 1}})", 'E119:') + call assert_equal([[], [], []], matchfuzzypos(l, 'cam')) + " Nvim's callback implementation is different, so E6000 is expected instead, + " but we need v8.2.1183 to assert it + " call assert_fails("let x = matchfuzzypos(l, 'cam', {'text_cb' : []})", 'E921:') + " call assert_fails("let x = matchfuzzypos(l, 'cam', {'text_cb' : []})", 'E6000:') + call assert_fails("let x = matchfuzzypos(l, 'cam', {'text_cb' : []})", 'E475:') + " call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : []})", 'E730:') + call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : []})", 'E475:') + call assert_fails("let x = matchfuzzypos(l, 'cam', v:_null_dict)", 'E715:') + call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : v:_null_string})", 'E475:') + " Nvim doesn't have null functions + " call assert_fails("let x = matchfuzzypos(l, 'foo', {'text_cb' : test_null_function()})", 'E475:') + + let l = [{'id' : 5, 'name' : 'foo'}, {'id' : 6, 'name' : []}, {'id' : 7}] + call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : 'name'})", 'E730:') +endfunc + +" Test for matchfuzzy() with multibyte characters +func Test_matchfuzzy_mbyte() + CheckFeature multi_lang + call assert_equal(['ンヹㄇヺヴ'], matchfuzzy(['ンヹㄇヺヴ'], 'ヹヺ')) + " reverse the order of characters + call assert_equal([], matchfuzzy(['ンヹㄇヺヴ'], 'ヺヹ')) + call assert_equal(['αβΩxxx', 'xαxβxΩx'], + \ matchfuzzy(['αβΩxxx', 'xαxβxΩx'], 'αβΩ')) + call assert_equal(['ππbbππ', 'πππbbbπππ', 'ππππbbbbππππ', 'πbπ'], + \ matchfuzzy(['πbπ', 'ππbbππ', 'πππbbbπππ', 'ππππbbbbππππ'], 'ππ')) + + " match multiple words (separated by space) + call assert_equal(['세 마리의 작은 돼지'], ['세 마리의 작은 돼지', '마리의', '마리의 작은', '작은 돼지']->matchfuzzy('돼지 마리의')) + call assert_equal([], ['세 마리의 작은 돼지', '마리의', '마리의 작은', '작은 돼지']->matchfuzzy('파란 하늘')) + + " preference for camel case match + call assert_equal(['oneĄwo', 'oneąwo'], + \ ['oneąwo', 'oneĄwo']->matchfuzzy('oneąwo')) + " preference for complete match then match after separator (_ or space) + call assert_equal(['ⅠⅡabㄟㄠ'] + sort(['ⅠⅡa_bㄟㄠ', 'ⅠⅡa bㄟㄠ']), + \ ['ⅠⅡabㄟㄠ', 'ⅠⅡa bㄟㄠ', 'ⅠⅡa_bㄟㄠ']->matchfuzzy('ⅠⅡabㄟㄠ')) + " preference for match after a separator (_ or space) + call assert_equal(['ㄓㄔabㄟㄠ', 'ㄓㄔa_bㄟㄠ', 'ㄓㄔa bㄟㄠ'], + \ ['ㄓㄔa_bㄟㄠ', 'ㄓㄔa bㄟㄠ', 'ㄓㄔabㄟㄠ']->matchfuzzy('ㄓㄔabㄟㄠ')) + " preference for leading letter match + call assert_equal(['ŗŝţũŵż', 'xŗŝţũŵż'], + \ ['xŗŝţũŵż', 'ŗŝţũŵż']->matchfuzzy('ŗŝţũŵż')) + " preference for sequential match + call assert_equal(['ㄞㄡㄤfffifl', 'ㄞaㄡbㄤcffdfiefl'], + \ ['ㄞaㄡbㄤcffdfiefl', 'ㄞㄡㄤfffifl']->matchfuzzy('ㄞㄡㄤfffifl')) + " non-matching leading letter(s) penalty + call assert_equal(['xㄞㄡㄤfffifl', 'xxㄞㄡㄤfffifl'], + \ ['xxㄞㄡㄤfffifl', 'xㄞㄡㄤfffifl']->matchfuzzy('ㄞㄡㄤfffifl')) + " total non-matching letter(s) penalty + call assert_equal(['ŗŝţ', 'ŗŝţx', 'ŗŝţxx'], + \ ['ŗŝţxx', 'ŗŝţ', 'ŗŝţx']->matchfuzzy('ŗŝţ')) +endfunc + +" Test for matchfuzzypos() with multibyte characters +func Test_matchfuzzypos_mbyte() + CheckFeature multi_lang + call assert_equal([['こんにちは世界'], [[0, 1, 2, 3, 4]], [273]], + \ matchfuzzypos(['こんにちは世界'], 'こんにちは')) + call assert_equal([['ンヹㄇヺヴ'], [[1, 3]], [88]], matchfuzzypos(['ンヹㄇヺヴ'], 'ヹヺ')) + " reverse the order of characters + call assert_equal([[], [], []], matchfuzzypos(['ンヹㄇヺヴ'], 'ヺヹ')) + call assert_equal([['αβΩxxx', 'xαxβxΩx'], [[0, 1, 2], [1, 3, 5]], [222, 113]], + \ matchfuzzypos(['αβΩxxx', 'xαxβxΩx'], 'αβΩ')) + call assert_equal([['ππbbππ', 'πππbbbπππ', 'ππππbbbbππππ', 'πbπ'], + \ [[0, 1], [0, 1], [0, 1], [0, 2]], [151, 148, 145, 110]], + \ matchfuzzypos(['πbπ', 'ππbbππ', 'πππbbbπππ', 'ππππbbbbππππ'], 'ππ')) + call assert_equal([['ααααααα'], [[0, 1, 2]], [191]], + \ matchfuzzypos(['ααααααα'], 'ααα')) + + call assert_equal([[], [], []], matchfuzzypos(['ンヹㄇ', 'ŗŝţ'], 'fffifl')) + let x = matchfuzzypos([repeat('Ψ', 256)], repeat('Ψ', 256)) + call assert_equal(range(256), x[1][0]) + call assert_equal([[], [], []], matchfuzzypos([repeat('✓', 300)], repeat('✓', 257))) + + " match multiple words (separated by space) + call assert_equal([['세 마리의 작은 돼지'], [[9, 10, 2, 3, 4]], [328]], ['세 마리의 작은 돼지', '마리의', '마리의 작은', '작은 돼지']->matchfuzzypos('돼지 마리의')) + call assert_equal([[], [], []], ['세 마리의 작은 돼지', '마리의', '마리의 작은', '작은 돼지']->matchfuzzypos('파란 하늘')) + + " match in a long string + call assert_equal([[repeat('ぶ', 300) .. 'ẼẼẼ'], [[300, 301, 302]], [-135]], + \ matchfuzzypos([repeat('ぶ', 300) .. 'ẼẼẼ'], 'ẼẼẼ')) + " preference for camel case match + call assert_equal([['xѳѵҁxxѳѴҁ'], [[6, 7, 8]], [189]], matchfuzzypos(['xѳѵҁxxѳѴҁ'], 'ѳѵҁ')) + " preference for match after a separator (_ or space) + call assert_equal([['xちだx_ちだ'], [[5, 6]], [145]], matchfuzzypos(['xちだx_ちだ'], 'ちだ')) + " preference for leading letter match + call assert_equal([['ѳѵҁxѳѵҁ'], [[0, 1]], [150]], matchfuzzypos(['ѳѵҁxѳѵҁ'], 'ѳѵ')) + " preference for sequential match + call assert_equal([['aンbヹcㄇdンヹㄇ'], [[7, 8, 9]], [158]], matchfuzzypos(['aンbヹcㄇdンヹㄇ'], 'ンヹㄇ')) + " best recursive match + call assert_equal([['xффйд'], [[2, 3, 4]], [168]], matchfuzzypos(['xффйд'], 'фйд')) +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_messages.vim b/src/nvim/testdir/test_messages.vim index e0286548d9..9c84d77dd2 100644 --- a/src/nvim/testdir/test_messages.vim +++ b/src/nvim/testdir/test_messages.vim @@ -40,7 +40,7 @@ endfunc " indicator (e.g., "-- INSERT --") when ":stopinsert" is invoked. Message " output could then be disturbed when 'cmdheight' was greater than one. " This test ensures that the bugfix for this issue remains in place. -function! Test_stopinsert_does_not_break_message_output() +func Test_stopinsert_does_not_break_message_output() set cmdheight=2 redraw! @@ -55,7 +55,7 @@ function! Test_stopinsert_does_not_break_message_output() redraw! set cmdheight& -endfunction +endfunc func Test_message_completion() call feedkeys(":message \<C-A>\<C-B>\"\<CR>", 'tx') diff --git a/src/nvim/testdir/test_number.vim b/src/nvim/testdir/test_number.vim index d737ebe9f0..dfbdc0bffd 100644 --- a/src/nvim/testdir/test_number.vim +++ b/src/nvim/testdir/test_number.vim @@ -284,10 +284,10 @@ func Test_relativenumber_colors() " Default colors call VerifyScreenDump(buf, 'Test_relnr_colors_1', {}) - call term_sendkeys(buf, ":hi LineNrAbove ctermfg=blue\<CR>") + call term_sendkeys(buf, ":hi LineNrAbove ctermfg=blue\<CR>:\<CR>") call VerifyScreenDump(buf, 'Test_relnr_colors_2', {}) - call term_sendkeys(buf, ":hi LineNrBelow ctermfg=green\<CR>") + call term_sendkeys(buf, ":hi LineNrBelow ctermfg=green\<CR>:\<CR>") call VerifyScreenDump(buf, 'Test_relnr_colors_3', {}) call term_sendkeys(buf, ":hi clear LineNrAbove\<CR>") diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim index 2312df5450..a5adb5ff16 100644 --- a/src/nvim/testdir/test_options.vim +++ b/src/nvim/testdir/test_options.vim @@ -22,16 +22,16 @@ func Test_whichwrap() call assert_equal('h', &whichwrap) set whichwrap& -endfunction +endfunc -function! Test_isfname() +func Test_isfname() " This used to cause Vim to access uninitialized memory. set isfname= call assert_equal("~X", expand("~X")) set isfname& -endfunction +endfunc -function Test_wildchar() +func Test_wildchar() " Empty 'wildchar' used to access invalid memory. call assert_fails('set wildchar=', 'E521:') call assert_fails('set wildchar=abc', 'E521:') @@ -42,7 +42,7 @@ function Test_wildchar() let a=execute('set wildchar?') call assert_equal("\n wildchar=<Esc>", a) set wildchar& -endfunction +endfunc func Test_wildoptions() set wildoptions= @@ -90,7 +90,7 @@ func Test_options_command() close endfunc -function! Test_path_keep_commas() +func Test_path_keep_commas() " Test that changing 'path' keeps two commas. set path=foo,,bar set path-=bar @@ -98,7 +98,7 @@ function! Test_path_keep_commas() call assert_equal('foo,,bar', &path) set path& -endfunction +endfunc func Test_filetype_valid() set ft=valid_name @@ -259,6 +259,8 @@ func Test_set_errors() call assert_fails('set shiftwidth=-1', 'E487:') call assert_fails('set sidescroll=-1', 'E487:') call assert_fails('set tabstop=-1', 'E487:') + call assert_fails('set tabstop=10000', 'E474:') + call assert_fails('set tabstop=5500000000', 'E474:') call assert_fails('set textwidth=-1', 'E487:') call assert_fails('set timeoutlen=-1', 'E487:') call assert_fails('set updatecount=-1', 'E487:') diff --git a/src/nvim/testdir/test_put.vim b/src/nvim/testdir/test_put.vim index 440717eaa8..ed76709a56 100644 --- a/src/nvim/testdir/test_put.vim +++ b/src/nvim/testdir/test_put.vim @@ -1,5 +1,7 @@ " Tests for put commands, e.g. ":put", "p", "gp", "P", "gP", etc. +source check.vim + func Test_put_block() new call feedkeys("i\<C-V>u2500\<CR>x\<ESC>", 'x') @@ -112,6 +114,39 @@ func Test_put_p_indent_visual() bwipe! endfunc +func Test_gp_with_count_leaves_cursor_at_end() + new + call setline(1, '<---->') + call setreg('@', "foo\nbar", 'c') + normal 1G3|3gp + call assert_equal([0, 4, 4, 0], getpos(".")) + call assert_equal(['<--foo', 'barfoo', 'barfoo', 'bar-->'], getline(1, '$')) + call assert_equal([0, 4, 3, 0], getpos("']")) + + bwipe! +endfunc + +func Test_p_with_count_leaves_mark_at_end() + new + call setline(1, '<---->') + call setreg('@', "start\nend", 'c') + normal 1G3|3p + call assert_equal([0, 1, 4, 0], getpos(".")) + call assert_equal(['<--start', 'endstart', 'endstart', 'end-->'], getline(1, '$')) + call assert_equal([0, 4, 3, 0], getpos("']")) + + bwipe! +endfunc + +func Test_put_above_first_line() + new + let @" = 'text' + silent! normal 0o00 + 0put + call assert_equal('text', getline(1)) + bwipe! +endfunc + func Test_multibyte_op_end_mark() new call setline(1, 'тест') diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 6db679c5f9..00679e1958 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -32,7 +32,7 @@ func s:setup_commands(cchar) command! -count -nargs=* -bang Xnfile <mods><count>cnfile<bang> <args> command! -nargs=* -bang Xpfile <mods>cpfile<bang> <args> command! -nargs=* Xexpr <mods>cexpr <args> - command! -count -nargs=* Xvimgrep <mods> <count>vimgrep <args> + command! -count=999 -nargs=* Xvimgrep <mods> <count>vimgrep <args> command! -nargs=* Xvimgrepadd <mods> vimgrepadd <args> command! -nargs=* Xgrep <mods> grep <args> command! -nargs=* Xgrepadd <mods> grepadd <args> @@ -69,7 +69,7 @@ func s:setup_commands(cchar) command! -count -nargs=* -bang Xnfile <mods><count>lnfile<bang> <args> command! -nargs=* -bang Xpfile <mods>lpfile<bang> <args> command! -nargs=* Xexpr <mods>lexpr <args> - command! -count -nargs=* Xvimgrep <mods> <count>lvimgrep <args> + command! -count=999 -nargs=* Xvimgrep <mods> <count>lvimgrep <args> command! -nargs=* Xvimgrepadd <mods> lvimgrepadd <args> command! -nargs=* Xgrep <mods> lgrep <args> command! -nargs=* Xgrepadd <mods> lgrepadd <args> @@ -1914,6 +1914,7 @@ func Test_switchbuf() " If opening a file changes 'switchbuf', then the new value should be " retained. + set modeline&vim call writefile(["vim: switchbuf=split"], 'Xqftestfile1') enew | only set switchbuf&vim @@ -5027,6 +5028,52 @@ func Test_qfbuf_update() call Xqfbuf_update('l') endfunc +" Test for the :vimgrep 'f' flag (fuzzy match) +func Xvimgrep_fuzzy_match(cchar) + call s:setup_commands(a:cchar) + + Xvimgrep /three one/f Xfile* + let l = g:Xgetlist() + call assert_equal(2, len(l)) + call assert_equal(['Xfile1', 1, 9, 'one two three'], + \ [bufname(l[0].bufnr), l[0].lnum, l[0].col, l[0].text]) + call assert_equal(['Xfile2', 2, 1, 'three one two'], + \ [bufname(l[1].bufnr), l[1].lnum, l[1].col, l[1].text]) + + Xvimgrep /the/f Xfile* + let l = g:Xgetlist() + call assert_equal(3, len(l)) + call assert_equal(['Xfile1', 1, 9, 'one two three'], + \ [bufname(l[0].bufnr), l[0].lnum, l[0].col, l[0].text]) + call assert_equal(['Xfile2', 2, 1, 'three one two'], + \ [bufname(l[1].bufnr), l[1].lnum, l[1].col, l[1].text]) + call assert_equal(['Xfile2', 4, 4, 'aaathreeaaa'], + \ [bufname(l[2].bufnr), l[2].lnum, l[2].col, l[2].text]) + + Xvimgrep /aaa/fg Xfile* + let l = g:Xgetlist() + call assert_equal(4, len(l)) + call assert_equal(['Xfile1', 2, 1, 'aaaaaa'], + \ [bufname(l[0].bufnr), l[0].lnum, l[0].col, l[0].text]) + call assert_equal(['Xfile1', 2, 4, 'aaaaaa'], + \ [bufname(l[1].bufnr), l[1].lnum, l[1].col, l[1].text]) + call assert_equal(['Xfile2', 4, 1, 'aaathreeaaa'], + \ [bufname(l[2].bufnr), l[2].lnum, l[2].col, l[2].text]) + call assert_equal(['Xfile2', 4, 9, 'aaathreeaaa'], + \ [bufname(l[3].bufnr), l[3].lnum, l[3].col, l[3].text]) + + call assert_fails('Xvimgrep /xyz/fg Xfile*', 'E480:') +endfunc + +func Test_vimgrep_fuzzy_match() + call writefile(['one two three', 'aaaaaa'], 'Xfile1') + call writefile(['one', 'three one two', 'two', 'aaathreeaaa'], 'Xfile2') + call Xvimgrep_fuzzy_match('c') + call Xvimgrep_fuzzy_match('l') + call delete('Xfile1') + call delete('Xfile2') +endfunc + " Test for getting a specific item from a quickfix list func Xtest_getqflist_by_idx(cchar) call s:setup_commands(a:cchar) diff --git a/src/nvim/testdir/test_random.vim b/src/nvim/testdir/test_random.vim new file mode 100644 index 0000000000..6d3f7dcfd9 --- /dev/null +++ b/src/nvim/testdir/test_random.vim @@ -0,0 +1,51 @@ +" Tests for srand() and rand() + +func Test_Rand() + let r = srand(123456789) + call assert_equal([1573771921, 319883699, 2742014374, 1324369493], r) + call assert_equal(4284103975, rand(r)) + call assert_equal(1001954530, rand(r)) + call assert_equal(2701803082, rand(r)) + call assert_equal(2658065534, rand(r)) + call assert_equal(3104308804, rand(r)) + + " Nvim does not support test_settime + " call test_settime(12341234) + let s = srand() + if !has('win32') && filereadable('/dev/urandom') + " using /dev/urandom + call assert_notequal(s, srand()) + " else + " " using time() + " call assert_equal(s, srand()) + " call test_settime(12341235) + " call assert_notequal(s, srand()) + endif + + " Nvim does not support test_srand_seed + " call test_srand_seed(123456789) + " call assert_equal(4284103975, rand()) + " call assert_equal(1001954530, rand()) + " call test_srand_seed() + + if has('float') + call assert_fails('echo srand(1.2)', 'E805:') + endif + call assert_fails('echo srand([1])', 'E745:') + call assert_fails('echo rand("burp")', 'E475:') + call assert_fails('echo rand([1, 2, 3])', 'E475:') + call assert_fails('echo rand([[1], 2, 3, 4])', 'E475:') + call assert_fails('echo rand([1, [2], 3, 4])', 'E475:') + call assert_fails('echo rand([1, 2, [3], 4])', 'E475:') + call assert_fails('echo rand([1, 2, 3, [4]])', 'E475:') + + " call test_settime(0) +endfunc + +func Test_issue_5587() + call rand() + call garbagecollect() + call rand() +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_regexp_utf8.vim b/src/nvim/testdir/test_regexp_utf8.vim index c568805f87..b640a6d043 100644 --- a/src/nvim/testdir/test_regexp_utf8.vim +++ b/src/nvim/testdir/test_regexp_utf8.vim @@ -152,9 +152,6 @@ func s:classes_test() if has('win32') let identchars_ok = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz
¡¢£¤¥¦§µÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ' let kwordchars_ok = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyzµÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ' - elseif has('ebcdic') - let identchars_ok = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz¬®µº¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ' - let kwordchars_ok = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz¬®µº¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ' else let identchars_ok = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyzµÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ' let kwordchars_ok = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyzµÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ' @@ -166,8 +163,6 @@ func s:classes_test() let fnamechars_ok = '$+,-./0123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ' elseif has('vms') let fnamechars_ok = '#$%+,-./0123456789:;<>ABCDEFGHIJKLMNOPQRSTUVWXYZ[]_abcdefghijklmnopqrstuvwxyz~ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ' - elseif has('ebcdic') - let fnamechars_ok = '#$%+,-./=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ' else let fnamechars_ok = '#$%+,-./0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ' endif diff --git a/src/nvim/testdir/test_rename.vim b/src/nvim/testdir/test_rename.vim index 3887fcfabf..5359b84923 100644 --- a/src/nvim/testdir/test_rename.vim +++ b/src/nvim/testdir/test_rename.vim @@ -1,5 +1,7 @@ " Test rename() +source shared.vim + func Test_rename_file_to_file() call writefile(['foo'], 'Xrename1') @@ -81,7 +83,7 @@ func Test_rename_copy() call assert_equal(0, rename('Xrenamedir/Xrenamefile', 'Xrenamefile')) - if !has('win32') + if !has('win32') && !IsRoot() " On Windows, the source file is removed despite " its directory being made not writable. call assert_equal(['foo'], readfile('Xrenamedir/Xrenamefile')) diff --git a/src/nvim/testdir/test_search_stat.vim b/src/nvim/testdir/test_search_stat.vim index f7f7467e91..d950626615 100644 --- a/src/nvim/testdir/test_search_stat.vim +++ b/src/nvim/testdir/test_search_stat.vim @@ -1,14 +1,15 @@ " Tests for search_stats, when "S" is not in 'shortmess' -source screendump.vim source check.vim +source screendump.vim func Test_search_stat() new set shortmess-=S " Append 50 lines with text to search for, "foobar" appears 20 times call append(0, repeat(['foobar', 'foo', 'fooooobar', 'foba', 'foobar'], 10)) - call nvim_win_set_cursor(0, [1, 0]) + + call cursor(1, 1) " searchcount() returns an empty dictionary when previous pattern was not set call assert_equal({}, searchcount(#{pattern: ''})) @@ -45,7 +46,6 @@ func Test_search_stat() \ searchcount(#{pattern: 'fooooobar', maxcount: 1})) " match at second line - call cursor(1, 1) let messages_before = execute('messages') let @/ = 'fo*\(bar\?\)\?' let g:a = execute(':unsilent :norm! n') @@ -262,6 +262,34 @@ func Test_searchcount_fails() call assert_fails('echo searchcount("boo!")', 'E715:') endfunc +func Test_searchcount_in_statusline() + CheckScreendump + + let lines =<< trim END + set shortmess-=S + call append(0, 'this is something') + function TestSearchCount() abort + let search_count = searchcount() + if !empty(search_count) + return '[' . search_count.current . '/' . search_count.total . ']' + else + return '' + endif + endfunction + set hlsearch + set laststatus=2 statusline+=%{TestSearchCount()} + END + call writefile(lines, 'Xsearchstatusline') + let buf = RunVimInTerminal('-S Xsearchstatusline', #{rows: 10}) + call TermWait(buf) + call term_sendkeys(buf, "/something") + call VerifyScreenDump(buf, 'Test_searchstat_4', {}) + + call term_sendkeys(buf, "\<Esc>") + call StopVimInTerminal(buf) + call delete('Xsearchstatusline') +endfunc + func Test_search_stat_foldopen() CheckScreendump @@ -319,30 +347,29 @@ func! Test_search_stat_screendump() call delete('Xsearchstat') endfunc -func Test_searchcount_in_statusline() +func Test_search_stat_then_gd() CheckScreendump let lines =<< trim END + call setline(1, ['int cat;', 'int dog;', 'cat = dog;']) set shortmess-=S - call append(0, 'this is something') - function TestSearchCount() abort - let search_count = searchcount() - if !empty(search_count) - return '[' . search_count.current . '/' . search_count.total . ']' - else - return '' - endif - endfunction set hlsearch - set laststatus=2 statusline+=%{TestSearchCount()} END - call writefile(lines, 'Xsearchstatusline') - let buf = RunVimInTerminal('-S Xsearchstatusline', #{rows: 10}) + call writefile(lines, 'Xsearchstatgd') + + let buf = RunVimInTerminal('-S Xsearchstatgd', #{rows: 10}) + call term_sendkeys(buf, "/dog\<CR>") call TermWait(buf) - call term_sendkeys(buf, "/something") - call VerifyScreenDump(buf, 'Test_searchstat_4', {}) + call VerifyScreenDump(buf, 'Test_searchstatgd_1', {}) + + call term_sendkeys(buf, "G0gD") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_searchstatgd_2', {}) - call term_sendkeys(buf, "\<Esc>") call StopVimInTerminal(buf) - call delete('Xsearchstatusline') + call delete('Xsearchstatgd') endfunc + + + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_source_utf8.vim b/src/nvim/testdir/test_source_utf8.vim index e93ea29dff..66fabe0442 100644 --- a/src/nvim/testdir/test_source_utf8.vim +++ b/src/nvim/testdir/test_source_utf8.vim @@ -1,4 +1,5 @@ " Test the :source! command +source check.vim func Test_source_utf8() " check that sourcing a script with 0x80 as second byte works @@ -31,24 +32,24 @@ endfunc " Test for sourcing a file with CTRL-V's at the end of the line func Test_source_ctrl_v() - call writefile(['map __1 afirst', - \ 'map __2 asecond', - \ 'map __3 athird', - \ 'map __4 afourth', - \ 'map __5 afifth', - \ "map __1 asd\<C-V>", - \ "map __2 asd\<C-V>\<C-V>", - \ "map __3 asd\<C-V>\<C-V>", - \ "map __4 asd\<C-V>\<C-V>\<C-V>", - \ "map __5 asd\<C-V>\<C-V>\<C-V>", - \ ], 'Xtestfile') + call writefile(['map __1 afirst', + \ 'map __2 asecond', + \ 'map __3 athird', + \ 'map __4 afourth', + \ 'map __5 afifth', + \ "map __1 asd\<C-V>", + \ "map __2 asd\<C-V>\<C-V>", + \ "map __3 asd\<C-V>\<C-V>", + \ "map __4 asd\<C-V>\<C-V>\<C-V>", + \ "map __5 asd\<C-V>\<C-V>\<C-V>", + \ ], 'Xtestfile') source Xtestfile enew! exe "normal __1\<Esc>\<Esc>__2\<Esc>__3\<Esc>\<Esc>__4\<Esc>__5\<Esc>" exe "%s/\<C-J>/0/g" call assert_equal(['sd', - \ "map __2 asd\<Esc>secondsd\<Esc>sd0map __5 asd0fifth"], - \ getline(1, 2)) + \ "map __2 asd\<Esc>secondsd\<Esc>sd0map __5 asd0fifth"], + \ getline(1, 2)) enew! call delete('Xtestfile') diff --git a/src/nvim/testdir/test_statusline.vim b/src/nvim/testdir/test_statusline.vim index f40c9ae097..fad13e3340 100644 --- a/src/nvim/testdir/test_statusline.vim +++ b/src/nvim/testdir/test_statusline.vim @@ -507,5 +507,20 @@ func Test_statusline_after_split_vsplit() set ls& stl& endfunc +" Test using a multibyte character for 'stl' and 'stlnc' items in 'fillchars' +" with a custom 'statusline' +func Test_statusline_mbyte_fillchar() + only + set laststatus=2 + set fillchars=vert:\|,fold:-,stl:━,stlnc:═ + set statusline=a%=b + call assert_match('^a\+━\+b$', s:get_statusline()) + vnew + call assert_match('^a\+━\+b━a\+═\+b$', s:get_statusline()) + wincmd w + call assert_match('^a\+═\+b═a\+━\+b$', s:get_statusline()) + set statusline& fillchars& laststatus& + %bw! +endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim index 20b760ac15..ecd980472a 100644 --- a/src/nvim/testdir/test_substitute.vim +++ b/src/nvim/testdir/test_substitute.vim @@ -105,7 +105,7 @@ func Test_substitute_variants() call assert_equal(var.exp, getline('.'), msg) endfor endfor -endfunction +endfunc " Test the l, p, # flags. func Test_substitute_flags_lp() diff --git a/src/nvim/testdir/test_swap.vim b/src/nvim/testdir/test_swap.vim index b3018b2b0d..b180f27685 100644 --- a/src/nvim/testdir/test_swap.vim +++ b/src/nvim/testdir/test_swap.vim @@ -1,6 +1,7 @@ " Tests for the swap feature source check.vim +source shared.vim func s:swapname() return trim(execute('swapname')) @@ -198,14 +199,17 @@ func Test_swapfile_delete() quit call assert_equal(fnamemodify(swapfile_name, ':t'), fnamemodify(s:swapname, ':t')) - " Write the swapfile with a modified PID, now it will be automatically - " deleted. Process one should never be Vim. - let swapfile_bytes[24:27] = 0z01000000 - call writefile(swapfile_bytes, swapfile_name) - let s:swapname = '' - split XswapfileText - quit - call assert_equal('', s:swapname) + " This test won't work as root because root can successfully run kill(1, 0) + if !IsRoot() + " Write the swapfile with a modified PID, now it will be automatically + " deleted. Process one should never be Vim. + let swapfile_bytes[24:27] = 0z01000000 + call writefile(swapfile_bytes, swapfile_name) + let s:swapname = '' + split XswapfileText + quit + call assert_equal('', s:swapname) + endif " Now set the modified flag, the swap file will not be deleted let swapfile_bytes[28 + 80 + 899] = 0x55 diff --git a/src/nvim/testdir/test_system.vim b/src/nvim/testdir/test_system.vim index 1858b48807..5b8079d7b6 100644 --- a/src/nvim/testdir/test_system.vim +++ b/src/nvim/testdir/test_system.vim @@ -46,9 +46,9 @@ func Test_System() bwipe! call assert_fails('call system("wc -l", 99999)', 'E86:') -endfunction +endfunc -function! Test_system_exmode() +func Test_system_exmode() if has('unix') " echo $? only works on Unix let cmd = ' -es --headless -u NONE -c "source Xscript" +q; echo "result=$?"' " Need to put this in a script, "catch" isn't found after an unknown diff --git a/src/nvim/testdir/test_textformat.vim b/src/nvim/testdir/test_textformat.vim index bf0706a0c2..783793984d 100644 --- a/src/nvim/testdir/test_textformat.vim +++ b/src/nvim/testdir/test_textformat.vim @@ -196,6 +196,130 @@ func Test_text_format() enew! endfunc +func Test_format_c_comment() + new + setl ai cindent tw=40 et fo=croql + let text =<< trim END + var = 2345; // asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf + END + call setline(1, text) + normal gql + let expected =<< trim END + var = 2345; // asdf asdf asdf asdf asdf + // asdf asdf asdf asdf asdf + END + call assert_equal(expected, getline(1, '$')) + + %del + let text =<< trim END + var = 2345; // asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf + END + call setline(1, text) + normal gql + let expected =<< trim END + var = 2345; // asdf asdf asdf asdf asdf + // asdf asdf asdf asdf asdf + // asdf asdf + END + call assert_equal(expected, getline(1, '$')) + + %del + let text =<< trim END + #if 0 // This is another long end of + // line comment that + // wraps. + END + call setline(1, text) + normal gq2j + let expected =<< trim END + #if 0 // This is another long + // end of line comment + // that wraps. + END + call assert_equal(expected, getline(1, '$')) + + " 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; + val = val; // This is a comment + END + call setline(1, text) + normal 2Go + let expected =<< trim END + nop; + val = val; // This is a comment + // + END + call assert_equal(expected, getline(1, '$')) + normal 2GO + let expected =<< trim END + nop; + + val = val; // This is a comment + // + END + call assert_equal(expected, getline(1, '$')) + + " Using "o" does not repeat a comment in a string + %del + let text =<< trim END + nop; + val = " // This is not a comment"; + END + call setline(1, text) + normal 2Gox + let expected =<< trim END + nop; + val = " // This is not a comment"; + x + END + call assert_equal(expected, getline(1, '$')) + + " Using CTRL-U after "o" fixes the indent + %del + let text =<< trim END + { + val = val; // This is a comment + END + call setline(1, text) + exe "normal! 2Go\<C-U>x\<Esc>" + let expected =<< trim END + { + val = val; // This is a comment + x + END + call assert_equal(expected, getline(1, '$')) + + bwipe! +endfunc + " Tests for :right, :center and :left on text with embedded TAB. func Test_format_align() enew! diff --git a/src/nvim/testdir/test_utf8_comparisons.vim b/src/nvim/testdir/test_utf8_comparisons.vim index fdf9d80802..f3c86b44fb 100644 --- a/src/nvim/testdir/test_utf8_comparisons.vim +++ b/src/nvim/testdir/test_utf8_comparisons.vim @@ -1,12 +1,12 @@ " Tests for case-insensitive UTF-8 comparisons (utf_strnicmp() in mbyte.c) " Also test "g~ap". -function! Ch(a, op, b, expected) +func Ch(a, op, b, expected) call assert_equal(eval(printf('"%s" %s "%s"', a:a, a:op, a:b)), a:expected, \ printf('"%s" %s "%s" should return %d', a:a, a:op, a:b, a:expected)) -endfunction +endfunc -function! Chk(a, b, result) +func Chk(a, b, result) if a:result == 0 call Ch(a:a, '==?', a:b, 1) call Ch(a:a, '!=?', a:b, 0) @@ -86,6 +86,9 @@ endfunc " test that g~ap changes one paragraph only. func Test_gap() new - call feedkeys("iabcd\n\ndefggg0g~ap", "tx") + " setup text + call feedkeys("iabcd\<cr>\<cr>defg", "tx") + " modify only first line + call feedkeys("gg0g~ap", "tx") call assert_equal(["ABCD", "", "defg"], getline(1,3)) endfunc diff --git a/src/nvim/testdir/test_vartabs.vim b/src/nvim/testdir/test_vartabs.vim index 46e0d62313..017bb6675d 100644 --- a/src/nvim/testdir/test_vartabs.vim +++ b/src/nvim/testdir/test_vartabs.vim @@ -135,7 +135,17 @@ func Test_vartabs() bwipeout! endfunc -func! Test_vartabs_breakindent() +func Test_retab_invalid_arg() + new + call setline(1, "\ttext") + retab 0 + call assert_fails("retab -8", 'E487: Argument must be positive') + call assert_fails("retab 10000", 'E475:') + call assert_fails("retab 720575940379279360", 'E475:') + bwipe! +endfunc + +func Test_vartabs_breakindent() if !exists("+breakindent") return endif diff --git a/src/nvim/testdir/test_vimscript.vim b/src/nvim/testdir/test_vimscript.vim index 34733f127f..75a965f16d 100644 --- a/src/nvim/testdir/test_vimscript.vim +++ b/src/nvim/testdir/test_vimscript.vim @@ -25,7 +25,7 @@ com! -nargs=1 Xout call Xout(<args>) " in the variable argument list. This function is useful if similar tests are " to be made for a ":return" from a function call or a ":finish" in a script " file. -function! MakeScript(funcname, ...) +func MakeScript(funcname, ...) let script = tempname() execute "redir! >" . script execute "function" a:funcname @@ -1156,6 +1156,82 @@ func Test_type() call assert_equal(v:t_list, type(v:_null_list)) call assert_equal(v:t_dict, type(v:_null_dict)) call assert_equal(v:t_blob, type(v:_null_blob)) + + call assert_equal(0, 0 + v:false) + call assert_equal(1, 0 + v:true) + " call assert_equal(0, 0 + v:none) + call assert_equal(0, 0 + v:null) + + call assert_equal('false', '' . v:false) + call assert_equal('true', '' . v:true) + " call assert_equal('none', '' . v:none) + call assert_equal('null', '' . v:null) + + call assert_true(v:false == 0) + call assert_false(v:false != 0) + call assert_true(v:true == 1) + call assert_false(v:true != 1) + call assert_false(v:true == v:false) + call assert_true(v:true != v:false) + + call assert_true(v:null == 0) + call assert_false(v:null != 0) + " call assert_true(v:none == 0) + " call assert_false(v:none != 0) + + call assert_true(v:false is v:false) + call assert_true(v:true is v:true) + " call assert_true(v:none is v:none) + call assert_true(v:null is v:null) + + call assert_false(v:false isnot v:false) + call assert_false(v:true isnot v:true) + " call assert_false(v:none isnot v:none) + call assert_false(v:null isnot v:null) + + call assert_false(v:false is 0) + call assert_false(v:true is 1) + call assert_false(v:true is v:false) + " call assert_false(v:none is 0) + call assert_false(v:null is 0) + " call assert_false(v:null is v:none) + + call assert_true(v:false isnot 0) + call assert_true(v:true isnot 1) + call assert_true(v:true isnot v:false) + " call assert_true(v:none isnot 0) + call assert_true(v:null isnot 0) + " call assert_true(v:null isnot v:none) + + call assert_equal(v:false, eval(string(v:false))) + call assert_equal(v:true, eval(string(v:true))) + " call assert_equal(v:none, eval(string(v:none))) + call assert_equal(v:null, eval(string(v:null))) + + call assert_equal(v:false, copy(v:false)) + call assert_equal(v:true, copy(v:true)) + " call assert_equal(v:none, copy(v:none)) + call assert_equal(v:null, copy(v:null)) + + call assert_equal([v:false], deepcopy([v:false])) + call assert_equal([v:true], deepcopy([v:true])) + " call assert_equal([v:none], deepcopy([v:none])) + call assert_equal([v:null], deepcopy([v:null])) + + call assert_true(empty(v:false)) + call assert_false(empty(v:true)) + call assert_true(empty(v:null)) + " call assert_true(empty(v:none)) + + func ChangeYourMind() + try + return v:true + finally + return 'something else' + endtry + endfunc + + call ChangeYourMind() endfunc "------------------------------------------------------------------------------- @@ -1692,6 +1768,26 @@ func Test_function_defined_line() call delete('Xtest.vim') endfunc +func Test_for_over_string() + let res = '' + for c in 'aéc̀d' + let res ..= c .. '-' + endfor + call assert_equal('a-é-c̀-d-', res) + + let res = '' + for c in '' + let res ..= c .. '-' + endfor + call assert_equal('', res) + + let res = '' + for c in v:_null_string + let res ..= c .. '-' + endfor + call assert_equal('', res) +endfunc + "------------------------------------------------------------------------------- " Modelines {{{1 " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker diff --git a/src/nvim/testdir/test_window_cmd.vim b/src/nvim/testdir/test_window_cmd.vim index db5c0b2a11..ef6dec580f 100644 --- a/src/nvim/testdir/test_window_cmd.vim +++ b/src/nvim/testdir/test_window_cmd.vim @@ -575,7 +575,7 @@ func Test_winrestcmd() only endfunc -function! Fun_RenewFile() +func Fun_RenewFile() " Need to wait a bit for the timestamp to be older. let old_ftime = getftime("tmp.txt") while getftime("tmp.txt") == old_ftime @@ -585,7 +585,7 @@ function! Fun_RenewFile() sp wincmd p edit! tmp.txt -endfunction +endfunc func Test_window_prevwin() " Can we make this work on MS-Windows? diff --git a/src/nvim/testdir/test_writefile.vim b/src/nvim/testdir/test_writefile.vim index aa7882d129..5ffbe82082 100644 --- a/src/nvim/testdir/test_writefile.vim +++ b/src/nvim/testdir/test_writefile.vim @@ -43,7 +43,7 @@ func Test_writefile_fails_gently() endfunc func Test_writefile_fails_conversion() - if !has('iconv') || system('uname -s') =~ 'SunOS' + if !has('iconv') || has('sun') return endif " Without a backup file the write won't happen if there is a conversion diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index 5fec41f9a5..6b889cf97c 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -227,7 +227,7 @@ static void forward_modified_utf8(TermInput *input, TermKeyKey *key) && !(key->modifiers & TERMKEY_KEYMOD_SHIFT) && ASCII_ISUPPER(key->code.codepoint)) { assert(len <= 62); - // Make remove for the S- + // Make room for the S- memmove(buf + 3, buf + 1, len - 1); buf[1] = 'S'; buf[2] = '-'; @@ -315,7 +315,6 @@ static TermKeyResult tk_getkey(TermKey *tk, TermKeyKey *key, bool force) return force ? termkey_getkey_force(tk, key) : termkey_getkey(tk, key); } -static void tinput_timer_cb(TimeWatcher *watcher, void *data); static void tk_getkeys(TermInput *input, bool force) { diff --git a/src/nvim/version.c b/src/nvim/version.c index 5e2a81795a..71cca52773 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -2093,7 +2093,7 @@ void list_in_columns(char_u **items, int size, int current) // The rightmost column doesn't need a separator. // Sacrifice it to fit in one more column if possible. - int ncol = (int)(Columns + 1) / width; + int ncol = (Columns + 1) / width; int nrow = item_count / ncol + (item_count % ncol ? 1 : 0); int cur_row = 1; diff --git a/src/nvim/window.c b/src/nvim/window.c index 8e44fd5014..6996a93928 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -577,18 +577,19 @@ static void cmd_with_count(char *cmd, char_u *bufp, size_t bufsize, int64_t Pren void win_set_buf(Window window, Buffer buffer, bool noautocmd, Error *err) { - win_T *win = find_window_by_handle(window, err), *save_curwin = curwin; + win_T *win = find_window_by_handle(window, err); buf_T *buf = find_buffer_by_handle(buffer, err); - tabpage_T *tab = win_find_tabpage(win), *save_curtab = curtab; + tabpage_T *tab = win_find_tabpage(win); if (!win || !buf) { return; } - if (noautocmd) { block_autocmds(); } - if (switch_win_noblock(&save_curwin, &save_curtab, win, tab, false) == FAIL) { + + switchwin_T switchwin; + if (switch_win_noblock(&switchwin, win, tab, false) == FAIL) { api_set_error(err, kErrorTypeException, "Failed to switch to window %d", @@ -608,7 +609,7 @@ void win_set_buf(Window window, Buffer buffer, bool noautocmd, Error *err) // So do it now. validate_cursor(); - restore_win_noblock(save_curwin, save_curtab, false); + restore_win_noblock(&switchwin, false); if (noautocmd) { unblock_autocmds(); } @@ -6631,20 +6632,27 @@ static win_T *get_snapshot_focus(int idx) /// triggered, another tabpage access is limited. /// /// @return FAIL if switching to "win" failed. -int switch_win(win_T **save_curwin, tabpage_T **save_curtab, win_T *win, tabpage_T *tp, - bool no_display) +int switch_win(switchwin_T *switchwin, win_T *win, tabpage_T *tp, bool no_display) { block_autocmds(); - return switch_win_noblock(save_curwin, save_curtab, win, tp, no_display); + return switch_win_noblock(switchwin, win, tp, no_display); } // As switch_win() but without blocking autocommands. -int switch_win_noblock(win_T **save_curwin, tabpage_T **save_curtab, win_T *win, tabpage_T *tp, - bool no_display) +int switch_win_noblock(switchwin_T *switchwin, win_T *win, tabpage_T *tp, bool no_display) { - *save_curwin = curwin; + memset(switchwin, 0, sizeof(switchwin_T)); + switchwin->sw_curwin = curwin; + if (win == curwin) { + switchwin->sw_same_win = true; + } else { + // Disable Visual selection, because redrawing may fail. + switchwin->sw_visual_active = VIsual_active; + VIsual_active = false; + } + if (tp != NULL) { - *save_curtab = curtab; + switchwin->sw_curtab = curtab; if (no_display) { curtab->tp_firstwin = firstwin; curtab->tp_lastwin = lastwin; @@ -6666,28 +6674,33 @@ int switch_win_noblock(win_T **save_curwin, tabpage_T **save_curtab, win_T *win, // Restore current tabpage and window saved by switch_win(), if still valid. // When "no_display" is true the display won't be affected, no redraw is // triggered. -void restore_win(win_T *save_curwin, tabpage_T *save_curtab, bool no_display) +void restore_win(switchwin_T *switchwin, bool no_display) { - restore_win_noblock(save_curwin, save_curtab, no_display); + restore_win_noblock(switchwin, no_display); unblock_autocmds(); } // As restore_win() but without unblocking autocommands. -void restore_win_noblock(win_T *save_curwin, tabpage_T *save_curtab, bool no_display) +void restore_win_noblock(switchwin_T *switchwin, bool no_display) { - if (save_curtab != NULL && valid_tabpage(save_curtab)) { + if (switchwin->sw_curtab != NULL && valid_tabpage(switchwin->sw_curtab)) { if (no_display) { curtab->tp_firstwin = firstwin; curtab->tp_lastwin = lastwin; - curtab = save_curtab; + curtab = switchwin->sw_curtab; firstwin = curtab->tp_firstwin; lastwin = curtab->tp_lastwin; } else { - goto_tabpage_tp(save_curtab, false, false); + goto_tabpage_tp(switchwin->sw_curtab, false, false); } } - if (win_valid(save_curwin)) { - curwin = save_curwin; + + if (!switchwin->sw_same_win) { + VIsual_active = switchwin->sw_visual_active; + } + + if (win_valid(switchwin->sw_curwin)) { + curwin = switchwin->sw_curwin; curbuf = curwin->w_buffer; } // If called by win_execute() and executing the command changed the diff --git a/src/nvim/window.h b/src/nvim/window.h index 7e465a9f08..e2fd2c515d 100644 --- a/src/nvim/window.h +++ b/src/nvim/window.h @@ -4,6 +4,7 @@ #include <stdbool.h> #include "nvim/buffer_defs.h" +#include "nvim/mark.h" // Values for file_name_in_line() #define FNAME_MESS 1 // give error message @@ -32,6 +33,38 @@ #define MIN_COLUMNS 12 // minimal columns for screen #define MIN_LINES 2 // minimal lines for screen +/// Structure used by switch_win() to pass values to restore_win() +typedef struct { + win_T *sw_curwin; + tabpage_T *sw_curtab; + bool sw_same_win; ///< VIsual_active was not reset + bool sw_visual_active; +} switchwin_T; + +/// Execute a block of code in the context of window `wp` in tabpage `tp`. +/// Ensures the status line is redrawn and cursor position is valid if it is moved. +#define WIN_EXECUTE(wp, tp, block) \ + do { \ + win_T *const wp_ = (wp); \ + const pos_T curpos_ = wp_->w_cursor; \ + switchwin_T switchwin_; \ + if (switch_win_noblock(&switchwin_, wp_, (tp), true) == OK) { \ + check_cursor(); \ + block; \ + } \ + restore_win_noblock(&switchwin_, true); \ + /* Update the status line if the cursor moved. */ \ + if (win_valid(wp_) && !equalpos(curpos_, wp_->w_cursor)) { \ + wp_->w_redr_status = true; \ + } \ + /* In case the command moved the cursor or changed the Visual area, */ \ + /* check it is valid. */ \ + check_cursor(); \ + if (VIsual_active) { \ + check_pos(curbuf, &VIsual); \ + } \ + } while (false) + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "window.h.generated.h" #endif diff --git a/test/functional/api/highlight_spec.lua b/test/functional/api/highlight_spec.lua index 21e3094f8e..a74da6073a 100644 --- a/test/functional/api/highlight_spec.lua +++ b/test/functional/api/highlight_spec.lua @@ -3,6 +3,7 @@ local clear, nvim = helpers.clear, helpers.nvim local Screen = require('test.functional.ui.screen') local eq, eval = helpers.eq, helpers.eval local command = helpers.command +local exec_capture = helpers.exec_capture local meths = helpers.meths local funcs = helpers.funcs local pcall_err = helpers.pcall_err @@ -252,4 +253,23 @@ describe("API: set highlight", function() eq(highlight3_result_gui, meths.get_hl_by_name('Test_hl', true)) eq(highlight3_result_cterm, meths.get_hl_by_name('Test_hl', false)) end) + + it ("can set a highlight in the global namespace", function() + meths.set_hl(0, 'Test_hl', highlight2_config) + eq('Test_hl xxx cterm=underline,reverse ctermfg=8 ctermbg=15 gui=underline,reverse', + exec_capture('highlight Test_hl')) + + meths.set_hl(0, 'Test_hl', { background = highlight_color.bg }) + eq('Test_hl xxx guibg=#0032aa', + exec_capture('highlight Test_hl')) + + meths.set_hl(0, 'Test_hl2', highlight3_config) + eq('Test_hl2 xxx cterm=undercurl,italic,reverse ctermfg=8 ctermbg=15 gui=bold,underline,undercurl,italic,reverse guifg=#ff0000 guibg=#0032aa', + exec_capture('highlight Test_hl2')) + + -- Colors are stored exactly as they are defined. + meths.set_hl(0, 'Test_hl3', { bg = 'reD', fg = 'bLue'}) + eq('Test_hl3 xxx guifg=bLue guibg=reD', + exec_capture('highlight Test_hl3')) + end) end) diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 201ba45803..937b6559de 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -333,6 +333,7 @@ describe('API', function() describe('nvim_command_output', function() it('does not induce hit-enter prompt', function() + nvim("ui_attach", 80, 20, {}) -- Induce a hit-enter prompt use nvim_input (non-blocking). nvim('command', 'set cmdheight=1') nvim('input', [[:echo "hi\nhi2"<CR>]]) @@ -1093,7 +1094,20 @@ describe('API', function() eq({mode='n', blocking=false}, nvim("get_mode")) end) + it("during press-enter prompt without UI returns blocking=false", function() + eq({mode='n', blocking=false}, nvim("get_mode")) + command("echom 'msg1'") + command("echom 'msg2'") + command("echom 'msg3'") + command("echom 'msg4'") + command("echom 'msg5'") + eq({mode='n', blocking=false}, nvim("get_mode")) + nvim("input", ":messages<CR>") + eq({mode='n', blocking=false}, nvim("get_mode")) + end) + it("during press-enter prompt returns blocking=true", function() + nvim("ui_attach", 80, 20, {}) eq({mode='n', blocking=false}, nvim("get_mode")) command("echom 'msg1'") command("echom 'msg2'") @@ -1117,6 +1131,7 @@ describe('API', function() -- TODO: bug #6247#issuecomment-286403810 it("batched with input", function() + nvim("ui_attach", 80, 20, {}) eq({mode='n', blocking=false}, nvim("get_mode")) command("echom 'msg1'") command("echom 'msg2'") @@ -2551,6 +2566,34 @@ describe('API', function() 'Should be truncated%<', { maxwidth = 15 })) end) + it('supports ASCII fillchar', function() + eq({ str = 'a~~~b', width = 5 }, + meths.eval_statusline('a%=b', { fillchar = '~', maxwidth = 5 })) + end) + it('supports single-width multibyte fillchar', function() + eq({ str = 'a━━━b', width = 5 }, + meths.eval_statusline('a%=b', { fillchar = '━', maxwidth = 5 })) + end) + it('rejects double-width fillchar', function() + eq('fillchar must be a single-width character', + pcall_err(meths.eval_statusline, '', { fillchar = '哦' })) + end) + it('rejects control character fillchar', function() + eq('fillchar must be a single-width character', + pcall_err(meths.eval_statusline, '', { fillchar = '\a' })) + end) + it('rejects multiple-character fillchar', function() + eq('fillchar must be a single-width character', + pcall_err(meths.eval_statusline, '', { fillchar = 'aa' })) + end) + it('rejects empty string fillchar', function() + eq('fillchar must be a single-width character', + pcall_err(meths.eval_statusline, '', { fillchar = '' })) + end) + it('rejects non-string fillchar', function() + eq('fillchar must be a single-width character', + pcall_err(meths.eval_statusline, '', { fillchar = 1 })) + end) describe('highlight parsing', function() it('works', function() eq({ diff --git a/test/functional/autocmd/focus_spec.lua b/test/functional/autocmd/focus_spec.lua index 3f9a0ad09b..e3c9e1f9ee 100644 --- a/test/functional/autocmd/focus_spec.lua +++ b/test/functional/autocmd/focus_spec.lua @@ -56,7 +56,7 @@ describe('autoread TUI FocusGained/FocusLost', function() line 3 | line 4 | {5:xtest-foo }| - "xtest-foo" 4L, 28C | + "xtest-foo" 4L, 28B | {3:-- TERMINAL --} | ]]} end) diff --git a/test/functional/fixtures/autoload/provider/python.vim b/test/functional/fixtures/autoload/provider/python3.vim index d68360ac30..8ed4330a35 100644 --- a/test/functional/fixtures/autoload/provider/python.vim +++ b/test/functional/fixtures/autoload/provider/python3.vim @@ -1,6 +1,6 @@ " Dummy test provider, missing this required variable: " let g:loaded_brokenenabled_provider = 0 -function! provider#python#Call(method, args) +function! provider#python3#Call(method, args) return 42 endfunction diff --git a/test/functional/legacy/003_cindent_spec.lua b/test/functional/legacy/003_cindent_spec.lua deleted file mode 100644 index 061904c42f..0000000000 --- a/test/functional/legacy/003_cindent_spec.lua +++ /dev/null @@ -1,4774 +0,0 @@ --- Test for 'cindent'. --- For new tests, consider putting them in test_cindent.vim. --- --- There are 50+ test command blocks (the stuff between STARTTEST and ENDTEST) --- in the original test. These have been converted to "it" test cases here. - -local helpers = require('test.functional.helpers')(after_each) -local feed, insert = helpers.feed, helpers.insert -local clear, feed_command, expect = helpers.clear, helpers.feed_command, helpers.expect - --- Inserts text as usual, and additionally positions the cursor on line 1 and --- sets 'cindent' and tab settings. (In the original "test3.in" the modeline at --- the top of the file takes care of this.) -local function insert_(content) - insert(content) - feed_command('1', 'set cin ts=4 sw=4') -end - --- luacheck: ignore 621 (Indentation) --- luacheck: ignore 613 (Trailing whitespace in a string) -describe('cindent', function() - before_each(clear) - - it('1 is working', function() - insert_([=[ - - /* start of AUTO matically checked vim: set ts=4 : */ - { - if (test) - cmd1; - cmd2; - } - - { - if (test) - cmd1; - else - cmd2; - } - - { - if (test) - { - cmd1; - cmd2; - } - } - - { - if (test) - { - cmd1; - else - } - } - - { - while (this) - if (test) - cmd1; - cmd2; - } - - { - while (this) - if (test) - cmd1; - else - cmd2; - } - - { - if (test) - { - cmd; - } - - if (test) - cmd; - } - - { - if (test) { - cmd; - } - - if (test) cmd; - } - - { - cmd1; - for (blah) - while (this) - if (test) - cmd2; - cmd3; - } - - { - cmd1; - for (blah) - while (this) - if (test) - cmd2; - cmd3; - - if (test) - { - cmd1; - cmd2; - cmd3; - } - } - - - /* Test for 'cindent' do/while mixed with if/else: */ - - { - do - if (asdf) - asdfasd; - while (cond); - - do - if (asdf) - while (asdf) - asdf; - while (asdf); - } - - /* Test for 'cindent' with two ) on a continuation line */ - { - if (asdfasdf;asldkfj asdlkfj as;ldkfj sal;d - aal;sdkjf ( ;asldfkja;sldfk - al;sdjfka ;slkdf ) sa;ldkjfsa dlk;) - line up here; - } - - - /* C++ tests: */ - - // foo() these three lines should remain in column 0 - // { - // } - - /* Test for continuation and unterminated lines: */ - { - i = 99 + 14325 + - 21345 + - 21345 + - 21345 + ( 21345 + - 21345) + - 2345 + - 1234; - c = 1; - } - - /* - testje for indent with empty line - - here */ - - { - if (testing && - not a joke || - line up here) - hay; - if (testing && - (not a joke || testing - )line up here) - hay; - if (testing && - (not a joke || testing - line up here)) - hay; - } - - - { - switch (c) - { - case xx: - do - if (asdf) - do - asdfasdf; - while (asdf); - else - asdfasdf; - while (cond); - case yy: - case xx: - case zz: - testing; - } - } - - { - if (cond) { - foo; - } - else - { - bar; - } - } - - { - if (alskdfj ;alsdkfjal;skdjf (;sadlkfsa ;dlkf j;alksdfj ;alskdjf - alsdkfj (asldk;fj - awith cino=(0 ;lf this one goes to below the paren with == - ;laksjfd ;lsakdjf ;alskdf asd) - asdfasdf;))) - asdfasdf; - } - - int - func(a, b) - int a; - int c; - { - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3) - ) - } - - { - while (asd) - { - if (asdf) - if (test) - if (that) - { - if (asdf) - do - cdasd; - while (as - df); - } - else - if (asdf) - asdf; - else - asdf; - asdf; - } - } - - { - s = "/*"; b = ';' - s = "/*"; b = ';'; - a = b; - } - - { - switch (a) - { - case a: - switch (t) - { - case 1: - cmd; - break; - case 2: - cmd; - break; - } - cmd; - break; - case b: - { - int i; - cmd; - } - break; - case c: { - int i; - cmd; - } - case d: if (cond && - test) { /* this line doesn't work right */ - int i; - cmd; - } - break; - } - } - - { - if (!(vim_strchr(p_cpo, CPO_BUFOPTGLOB) != NULL && entering) && - (bp_to->b_p_initialized || - (!entering && vim_strchr(p_cpo, CPO_BUFOPT) != NULL))) - return; - label : - asdf = asdf ? - asdf : asdf; - asdf = asdf ? - asdf: asdf; - } - - /* Special Comments : This function has the added complexity (compared */ - /* : to addtolist) of having to check for a detail */ - /* : texture and add that to the list first. */ - - char *(array[100]) = { - "testje", - "foo", - "bar", - } - - enum soppie - { - yes = 0, - no, - maybe - }; - - typedef enum soppie - { - yes = 0, - no, - maybe - }; - - static enum - { - yes = 0, - no, - maybe - } soppie; - - public static enum - { - yes = 0, - no, - maybe - } soppie; - - static private enum - { - yes = 0, - no, - maybe - } soppie; - - { - int a, - b; - } - - { - struct Type - { - int i; - char *str; - } var[] = - { - 0, "zero", - 1, "one", - 2, "two", - 3, "three" - }; - - float matrix[3][3] = - { - { - 0, - 1, - 2 - }, - { - 3, - 4, - 5 - }, - { - 6, - 7, - 8 - } - }; - } - - { - /* blah ( blah */ - /* where does this go? */ - - /* blah ( blah */ - cmd; - - func(arg1, - /* comment */ - arg2); - a; - { - b; - { - c; /* Hey, NOW it indents?! */ - } - } - - { - func(arg1, - arg2, - arg3); - /* Hey, what am I doing here? Is this coz of the ","? */ - } - } - - main () - { - if (cond) - { - a = b; - } - if (cond) { - a = c; - } - if (cond) - a = d; - return; - } - - { - case 2: if (asdf && - asdfasdf) - aasdf; - a = 9; - case 3: if (asdf) - aasdf; - a = 9; - case 4: x = 1; - y = 2; - - label: if (asdf) - here; - - label: if (asdf && - asdfasdf) - { - } - - label: if (asdf && - asdfasdf) { - there; - } - - label: if (asdf && - asdfasdf) - there; - } - - { - /* - hello with ":set comments= cino=c5" - */ - - /* - hello with ":set comments= cino=" - */ - } - - - { - if (a < b) { - a = a + 1; - } else - a = a + 2; - - if (a) - do { - testing; - } while (asdfasdf); - a = b + 1; - asdfasdf - } - - { - for ( int i = 0; - i < 10; i++ ) - { - } - i = 0; - } - - class bob - { - int foo() {return 1;} - int bar; - } - - main() - { - while(1) - if (foo) - { - bar; - } - else { - asdf; - } - misplacedline; - } - - { - if (clipboard.state == SELECT_DONE - && ((row == clipboard.start.lnum - && col >= clipboard.start.col) - || row > clipboard.start.lnum)) - } - - { - if (1) {i += 4;} - where_am_i; - return 0; - } - - { - { - } // sdf(asdf - if (asdf) - asd; - } - - { - label1: - label2: - } - - { - int fooRet = foo(pBar1, false /*fKB*/, - true /*fPTB*/, 3 /*nT*/, false /*fDF*/); - f() { - for ( i = 0; - i < m; - /* c */ i++ ) { - a = b; - } - } - } - - { - f1(/*comment*/); - f2(); - } - - { - do { - if (foo) { - } else - ; - } while (foo); - foo(); // was wrong - } - - int x; // no extra indent because of the ; - void func() - { - } - - char *tab[] = {"aaa", - "};", /* }; */ NULL} - int indented; - {} - - char *a[] = {"aaa", "bbb", - "ccc", NULL}; - // here - - char *tab[] = {"aaa", - "xx", /* xx */}; /* asdf */ - int not_indented; - - { - do { - switch (bla) - { - case 1: if (foo) - bar; - } - } while (boo); - wrong; - } - - int foo, - bar; - int foo; - - #if defined(foo) \ - && defined(bar) - char * xx = "asdf\ - foo\ - bor"; - int x; - - char *foo = "asdf\ - asdf\ - asdf", - *bar; - - void f() - { - #if defined(foo) \ - && defined(bar) - char *foo = "asdf\ - asdf\ - asdf", - *bar; - { - int i; - char *foo = "asdf\ - asdf\ - asdf", - *bar; - } - #endif - } - #endif - - int y; // comment - // comment - - // comment - - { - Constructor(int a, - int b ) : BaseClass(a) - { - } - } - - void foo() - { - char one, - two; - struct bla piet, - jan; - enum foo kees, - jannie; - static unsigned sdf, - krap; - unsigned int piet, - jan; - int - kees, - jan; - } - - { - t(int f, - int d); // ) - d(); - } - - Constructor::Constructor(int a, - int b - ) : - BaseClass(a, - b, - c), - mMember(b), - { - } - - Constructor::Constructor(int a, - int b ) : - BaseClass(a) - { - } - - Constructor::Constructor(int a, - int b ) /*x*/ : /*x*/ BaseClass(a), - member(b) - { - } - - A::A(int a, int b) - : aa(a), - bb(b), - cc(c) - { - } - - class CAbc : - public BaseClass1, - protected BaseClass2 - { - int Test() { return FALSE; } - int Test1() { return TRUE; } - - CAbc(int a, int b ) : - BaseClass(a) - { - switch(xxx) - { - case abc: - asdf(); - break; - - case 999: - baer(); - break; - } - } - - public: // <-- this was incoreectly indented before!! - void testfall(); - protected: - void testfall(); - }; - - class CAbc : public BaseClass1, - protected BaseClass2 - { - }; - - static struct - { - int a; - int b; - } variable[COUNT] = - { - { - 123, - 456 - }, - { - 123, - 456 - } - }; - - static struct - { - int a; - int b; - } variable[COUNT] = - { - { 123, 456 }, - { 123, 456 } - }; - - void asdf() /* ind_maxparen may cause trouble here */ - { - if ((0 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1)) break; - } - - foo() - { - a = cond ? foo() : asdf - + asdf; - - a = cond ? - foo() : asdf - + asdf; - } - - int main(void) - { - if (a) - if (b) - 2; - else 3; - next_line_of_code(); - } - - barry() - { - Foo::Foo (int one, - int two) - : something(4) - {} - } - - barry() - { - Foo::Foo (int one, int two) - : something(4) - {} - } - - Constructor::Constructor(int a, - int b - ) : - BaseClass(a, - b, - c), - mMember(b) - { - } - int main () - { - if (lala) - do - ++(*lolo); - while (lili - && lele); - lulu; - } - - int main () - { - switch (c) - { - case 'c': if (cond) - { - } - } - } - - main() - { - (void) MyFancyFuasdfadsfnction( - argument); - } - - main() - { - char foo[] = "/*"; - /* as - df */ - hello - } - - /* valid namespaces with normal indent */ - namespace - { - { - 111111111111; - } - } - namespace /* test */ - { - 11111111111111111; - } - namespace // test - { - 111111111111111111; - } - namespace - { - 111111111111111111; - } - namespace test - { - 111111111111111111; - } - namespace{ - 111111111111111111; - } - namespace test{ - 111111111111111111; - } - namespace { - 111111111111111111; - } - namespace test { - 111111111111111111; - namespace test2 { - 22222222222222222; - } - } - - /* invalid namespaces use block indent */ - namespace test test2 { - 111111111111111111111; - } - namespace11111111111 { - 111111111111; - } - namespace() { - 1111111111111; - } - namespace() - { - 111111111111111111; - } - namespace test test2 - { - 1111111111111111111; - } - namespace111111111 - { - 111111111111111111; - } - void getstring() { - /* Raw strings */ - const char* s = R"( - test { - # comment - field: 123 - } - )"; - } - void getstring() { - const char* s = R"foo( - test { - # comment - field: 123 - } - )foo"; - } - - { - int a[4] = { - [0] = 0, - [1] = 1, - [2] = 2, - [3] = 3, - }; - } - - { - a = b[2] - + 3; - } - - { - if (1) - /* aaaaa - * bbbbb - */ - a = 1; - } - - void func() - { - switch (foo) - { - case (bar): - if (baz()) - quux(); - break; - case (shmoo): - if (!bar) - { - } - case (foo1): - switch (bar) - { - case baz: - baz_f(); - break; - } - break; - default: - baz(); - baz(); - break; - } - } - - /* end of AUTO */ - ]=]) - - feed_command('/start of AUTO') - feed('=/end of AUTO<cr>') - - expect([=[ - - /* start of AUTO matically checked vim: set ts=4 : */ - { - if (test) - cmd1; - cmd2; - } - - { - if (test) - cmd1; - else - cmd2; - } - - { - if (test) - { - cmd1; - cmd2; - } - } - - { - if (test) - { - cmd1; - else - } - } - - { - while (this) - if (test) - cmd1; - cmd2; - } - - { - while (this) - if (test) - cmd1; - else - cmd2; - } - - { - if (test) - { - cmd; - } - - if (test) - cmd; - } - - { - if (test) { - cmd; - } - - if (test) cmd; - } - - { - cmd1; - for (blah) - while (this) - if (test) - cmd2; - cmd3; - } - - { - cmd1; - for (blah) - while (this) - if (test) - cmd2; - cmd3; - - if (test) - { - cmd1; - cmd2; - cmd3; - } - } - - - /* Test for 'cindent' do/while mixed with if/else: */ - - { - do - if (asdf) - asdfasd; - while (cond); - - do - if (asdf) - while (asdf) - asdf; - while (asdf); - } - - /* Test for 'cindent' with two ) on a continuation line */ - { - if (asdfasdf;asldkfj asdlkfj as;ldkfj sal;d - aal;sdkjf ( ;asldfkja;sldfk - al;sdjfka ;slkdf ) sa;ldkjfsa dlk;) - line up here; - } - - - /* C++ tests: */ - - // foo() these three lines should remain in column 0 - // { - // } - - /* Test for continuation and unterminated lines: */ - { - i = 99 + 14325 + - 21345 + - 21345 + - 21345 + ( 21345 + - 21345) + - 2345 + - 1234; - c = 1; - } - - /* - testje for indent with empty line - - here */ - - { - if (testing && - not a joke || - line up here) - hay; - if (testing && - (not a joke || testing - )line up here) - hay; - if (testing && - (not a joke || testing - line up here)) - hay; - } - - - { - switch (c) - { - case xx: - do - if (asdf) - do - asdfasdf; - while (asdf); - else - asdfasdf; - while (cond); - case yy: - case xx: - case zz: - testing; - } - } - - { - if (cond) { - foo; - } - else - { - bar; - } - } - - { - if (alskdfj ;alsdkfjal;skdjf (;sadlkfsa ;dlkf j;alksdfj ;alskdjf - alsdkfj (asldk;fj - awith cino=(0 ;lf this one goes to below the paren with == - ;laksjfd ;lsakdjf ;alskdf asd) - asdfasdf;))) - asdfasdf; - } - - int - func(a, b) - int a; - int c; - { - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3) - ) - } - - { - while (asd) - { - if (asdf) - if (test) - if (that) - { - if (asdf) - do - cdasd; - while (as - df); - } - else - if (asdf) - asdf; - else - asdf; - asdf; - } - } - - { - s = "/*"; b = ';' - s = "/*"; b = ';'; - a = b; - } - - { - switch (a) - { - case a: - switch (t) - { - case 1: - cmd; - break; - case 2: - cmd; - break; - } - cmd; - break; - case b: - { - int i; - cmd; - } - break; - case c: { - int i; - cmd; - } - case d: if (cond && - test) { /* this line doesn't work right */ - int i; - cmd; - } - break; - } - } - - { - if (!(vim_strchr(p_cpo, CPO_BUFOPTGLOB) != NULL && entering) && - (bp_to->b_p_initialized || - (!entering && vim_strchr(p_cpo, CPO_BUFOPT) != NULL))) - return; - label : - asdf = asdf ? - asdf : asdf; - asdf = asdf ? - asdf: asdf; - } - - /* Special Comments : This function has the added complexity (compared */ - /* : to addtolist) of having to check for a detail */ - /* : texture and add that to the list first. */ - - char *(array[100]) = { - "testje", - "foo", - "bar", - } - - enum soppie - { - yes = 0, - no, - maybe - }; - - typedef enum soppie - { - yes = 0, - no, - maybe - }; - - static enum - { - yes = 0, - no, - maybe - } soppie; - - public static enum - { - yes = 0, - no, - maybe - } soppie; - - static private enum - { - yes = 0, - no, - maybe - } soppie; - - { - int a, - b; - } - - { - struct Type - { - int i; - char *str; - } var[] = - { - 0, "zero", - 1, "one", - 2, "two", - 3, "three" - }; - - float matrix[3][3] = - { - { - 0, - 1, - 2 - }, - { - 3, - 4, - 5 - }, - { - 6, - 7, - 8 - } - }; - } - - { - /* blah ( blah */ - /* where does this go? */ - - /* blah ( blah */ - cmd; - - func(arg1, - /* comment */ - arg2); - a; - { - b; - { - c; /* Hey, NOW it indents?! */ - } - } - - { - func(arg1, - arg2, - arg3); - /* Hey, what am I doing here? Is this coz of the ","? */ - } - } - - main () - { - if (cond) - { - a = b; - } - if (cond) { - a = c; - } - if (cond) - a = d; - return; - } - - { - case 2: if (asdf && - asdfasdf) - aasdf; - a = 9; - case 3: if (asdf) - aasdf; - a = 9; - case 4: x = 1; - y = 2; - - label: if (asdf) - here; - - label: if (asdf && - asdfasdf) - { - } - - label: if (asdf && - asdfasdf) { - there; - } - - label: if (asdf && - asdfasdf) - there; - } - - { - /* - hello with ":set comments= cino=c5" - */ - - /* - hello with ":set comments= cino=" - */ - } - - - { - if (a < b) { - a = a + 1; - } else - a = a + 2; - - if (a) - do { - testing; - } while (asdfasdf); - a = b + 1; - asdfasdf - } - - { - for ( int i = 0; - i < 10; i++ ) - { - } - i = 0; - } - - class bob - { - int foo() {return 1;} - int bar; - } - - main() - { - while(1) - if (foo) - { - bar; - } - else { - asdf; - } - misplacedline; - } - - { - if (clipboard.state == SELECT_DONE - && ((row == clipboard.start.lnum - && col >= clipboard.start.col) - || row > clipboard.start.lnum)) - } - - { - if (1) {i += 4;} - where_am_i; - return 0; - } - - { - { - } // sdf(asdf - if (asdf) - asd; - } - - { - label1: - label2: - } - - { - int fooRet = foo(pBar1, false /*fKB*/, - true /*fPTB*/, 3 /*nT*/, false /*fDF*/); - f() { - for ( i = 0; - i < m; - /* c */ i++ ) { - a = b; - } - } - } - - { - f1(/*comment*/); - f2(); - } - - { - do { - if (foo) { - } else - ; - } while (foo); - foo(); // was wrong - } - - int x; // no extra indent because of the ; - void func() - { - } - - char *tab[] = {"aaa", - "};", /* }; */ NULL} - int indented; - {} - - char *a[] = {"aaa", "bbb", - "ccc", NULL}; - // here - - char *tab[] = {"aaa", - "xx", /* xx */}; /* asdf */ - int not_indented; - - { - do { - switch (bla) - { - case 1: if (foo) - bar; - } - } while (boo); - wrong; - } - - int foo, - bar; - int foo; - - #if defined(foo) \ - && defined(bar) - char * xx = "asdf\ - foo\ - bor"; - int x; - - char *foo = "asdf\ - asdf\ - asdf", - *bar; - - void f() - { - #if defined(foo) \ - && defined(bar) - char *foo = "asdf\ - asdf\ - asdf", - *bar; - { - int i; - char *foo = "asdf\ - asdf\ - asdf", - *bar; - } - #endif - } - #endif - - int y; // comment - // comment - - // comment - - { - Constructor(int a, - int b ) : BaseClass(a) - { - } - } - - void foo() - { - char one, - two; - struct bla piet, - jan; - enum foo kees, - jannie; - static unsigned sdf, - krap; - unsigned int piet, - jan; - int - kees, - jan; - } - - { - t(int f, - int d); // ) - d(); - } - - Constructor::Constructor(int a, - int b - ) : - BaseClass(a, - b, - c), - mMember(b), - { - } - - Constructor::Constructor(int a, - int b ) : - BaseClass(a) - { - } - - Constructor::Constructor(int a, - int b ) /*x*/ : /*x*/ BaseClass(a), - member(b) - { - } - - A::A(int a, int b) - : aa(a), - bb(b), - cc(c) - { - } - - class CAbc : - public BaseClass1, - protected BaseClass2 - { - int Test() { return FALSE; } - int Test1() { return TRUE; } - - CAbc(int a, int b ) : - BaseClass(a) - { - switch(xxx) - { - case abc: - asdf(); - break; - - case 999: - baer(); - break; - } - } - - public: // <-- this was incoreectly indented before!! - void testfall(); - protected: - void testfall(); - }; - - class CAbc : public BaseClass1, - protected BaseClass2 - { - }; - - static struct - { - int a; - int b; - } variable[COUNT] = - { - { - 123, - 456 - }, - { - 123, - 456 - } - }; - - static struct - { - int a; - int b; - } variable[COUNT] = - { - { 123, 456 }, - { 123, 456 } - }; - - void asdf() /* ind_maxparen may cause trouble here */ - { - if ((0 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1)) break; - } - - foo() - { - a = cond ? foo() : asdf - + asdf; - - a = cond ? - foo() : asdf - + asdf; - } - - int main(void) - { - if (a) - if (b) - 2; - else 3; - next_line_of_code(); - } - - barry() - { - Foo::Foo (int one, - int two) - : something(4) - {} - } - - barry() - { - Foo::Foo (int one, int two) - : something(4) - {} - } - - Constructor::Constructor(int a, - int b - ) : - BaseClass(a, - b, - c), - mMember(b) - { - } - int main () - { - if (lala) - do - ++(*lolo); - while (lili - && lele); - lulu; - } - - int main () - { - switch (c) - { - case 'c': if (cond) - { - } - } - } - - main() - { - (void) MyFancyFuasdfadsfnction( - argument); - } - - main() - { - char foo[] = "/*"; - /* as - df */ - hello - } - - /* valid namespaces with normal indent */ - namespace - { - { - 111111111111; - } - } - namespace /* test */ - { - 11111111111111111; - } - namespace // test - { - 111111111111111111; - } - namespace - { - 111111111111111111; - } - namespace test - { - 111111111111111111; - } - namespace{ - 111111111111111111; - } - namespace test{ - 111111111111111111; - } - namespace { - 111111111111111111; - } - namespace test { - 111111111111111111; - namespace test2 { - 22222222222222222; - } - } - - /* invalid namespaces use block indent */ - namespace test test2 { - 111111111111111111111; - } - namespace11111111111 { - 111111111111; - } - namespace() { - 1111111111111; - } - namespace() - { - 111111111111111111; - } - namespace test test2 - { - 1111111111111111111; - } - namespace111111111 - { - 111111111111111111; - } - void getstring() { - /* Raw strings */ - const char* s = R"( - test { - # comment - field: 123 - } - )"; - } - void getstring() { - const char* s = R"foo( - test { - # comment - field: 123 - } - )foo"; - } - - { - int a[4] = { - [0] = 0, - [1] = 1, - [2] = 2, - [3] = 3, - }; - } - - { - a = b[2] - + 3; - } - - { - if (1) - /* aaaaa - * bbbbb - */ - a = 1; - } - - void func() - { - switch (foo) - { - case (bar): - if (baz()) - quux(); - break; - case (shmoo): - if (!bar) - { - } - case (foo1): - switch (bar) - { - case baz: - baz_f(); - break; - } - break; - default: - baz(); - baz(); - break; - } - } - - /* end of AUTO */ - ]=]) - end) - - it('2 is working', function() - insert_([=[ - - { - - /* this is - * a real serious important big - * comment - */ - /* insert " about life, the universe, and the rest" after "serious" */ - } - ]=]) - - feed_command('set tw=0 noai fo=croq') - feed_command('let &wm = &columns - 20') - feed_command('/serious/e') - feed('a about life, the universe, and the rest<esc>') - - expect([=[ - - { - - /* this is - * a real serious - * about life, the - * universe, and the - * rest important big - * comment - */ - /* insert " about life, the universe, and the rest" after "serious" */ - } - ]=]) - end) - - it('3 is working', function() - insert_([=[ - - { - /* - * Testing for comments, without 'cin' set - */ - - /* - * what happens here? - */ - - /* - the end of the comment, try inserting a line below */ - - /* how about - this one */ - } - ]=]) - - feed_command('set nocin') - feed_command('/comments') - feed('joabout life<esc>/happens<cr>') - feed('jothere<esc>/below<cr>') - feed('oline<esc>/this<cr>') - feed('Ohello<esc>') - - expect([=[ - - { - /* - * Testing for comments, without 'cin' set - */ - about life - - /* - * what happens here? - */ - there - - /* - the end of the comment, try inserting a line below */ - line - - /* how about - hello - this one */ - } - ]=]) - end) - - it('4 is working', function() - insert_([=[ - - { - var = this + that + vec[0] * vec[0] - + vec[1] * vec[1] - + vec2[2] * vec[2]; - } - ]=]) - feed_command('set cin') - feed_command('/vec2') - feed('==<cr>') - - expect([=[ - - { - var = this + that + vec[0] * vec[0] - + vec[1] * vec[1] - + vec2[2] * vec[2]; - } - ]=]) - end) - - it('5 is working', function() - insert_([=[ - - { - asdf asdflkajds f; - if (tes & ting) { - asdf asdf asdf ; - asdfa sdf asdf; - } - testing1; - if (tes & ting) - { - asdf asdf asdf ; - asdfa sdf asdf; - } - testing2; - } - ]=]) - - feed_command('set cin') - feed_command('set cino=}4') - feed_command('/testing1') - feed('k2==/testing2<cr>') - feed('k2==<cr>') - - expect([=[ - - { - asdf asdflkajds f; - if (tes & ting) { - asdf asdf asdf ; - asdfa sdf asdf; - } - testing1; - if (tes & ting) - { - asdf asdf asdf ; - asdfa sdf asdf; - } - testing2; - } - ]=]) - end) - - it('6 is working', function() - insert_([=[ - - main ( int first_par, /* - * Comment for - * first par - */ - int second_par /* - * Comment for - * second par - */ - ) - { - func( first_par, /* - * Comment for - * first par - */ - second_par /* - * Comment for - * second par - */ - ); - - } - ]=]) - - feed_command('set cin') - feed_command('set cino=(0,)20') - feed_command('/main') - feed('=][<cr>') - - expect([=[ - - main ( int first_par, /* - * Comment for - * first par - */ - int second_par /* - * Comment for - * second par - */ - ) - { - func( first_par, /* - * Comment for - * first par - */ - second_par /* - * Comment for - * second par - */ - ); - - } - ]=]) - end) - - it('7 is working', function() - insert_([=[ - - main(void) - { - /* Make sure that cino=X0s is not parsed like cino=Xs. */ - if (cond) - foo(); - else - { - bar(); - } - } - ]=]) - - feed_command('set cin') - feed_command('set cino=es,n0s') - feed_command('/main') - feed('=][<cr>') - - expect([=[ - - main(void) - { - /* Make sure that cino=X0s is not parsed like cino=Xs. */ - if (cond) - foo(); - else - { - bar(); - } - } - ]=]) - end) - - it('8 is working', function() - insert_([=[ - - { - do - { - if () - { - if () - asdf; - else - asdf; - } - } while (); - cmd; /* this should go under the } */ - } - ]=]) - - feed_command('set cin') - feed_command('set cino=') - feed(']]=][<cr>') - - expect([=[ - - { - do - { - if () - { - if () - asdf; - else - asdf; - } - } while (); - cmd; /* this should go under the } */ - } - ]=]) - end) - - it('9 is working', function() - insert_([=[ - - void f() - { - if ( k() ) { - l(); - - } else { /* Start (two words) end */ - m(); - } - - n(); - } - ]=]) - - feed(']]=][<cr>') - - expect([=[ - - void f() - { - if ( k() ) { - l(); - - } else { /* Start (two words) end */ - m(); - } - - n(); - } - ]=]) - end) - - it('10 is working', function() - -- This is nasty. This is the only test case where the buffer content - -- differs from the original. Why? Proper behaviour of this test depends on - -- the fact that the setup code contains an (unbalanced) opening curly - -- bracket in "set cino={s,e-s". This bracket actually affects the outcome - -- of the test: without it the curly bracket under "void f()" would not be - -- indented properly. And that's why we've had to add one explicitly. - insert_([=[ - { <= THIS IS THE CURLY BRACKET EXPLAINED IN THE COMMENT. - - void f() - { - if ( k() ) - { - l(); - } else { /* Start (two words) end */ - m(); - } - n(); /* should be under the if () */ - } - ]=]) - - feed_command('set cino={s,e-s') - feed(']]=][<cr>') - - expect([=[ - { <= THIS IS THE CURLY BRACKET EXPLAINED IN THE COMMENT. - - void f() - { - if ( k() ) - { - l(); - } else { /* Start (two words) end */ - m(); - } - n(); /* should be under the if () */ - } - ]=]) - end) - - it('11 is working', function() - insert_([=[ - - void bar(void) - { - static array[2][2] = - { - { 1, 2 }, - { 3, 4 }, - } - - while (a) - { - foo(&a); - } - - { - int a; - { - a = a + 1; - } - } - b = a; - } - - void func(void) - { - a = 1; - { - b = 2; - } - c = 3; - d = 4; - } - /* foo */ - ]=]) - - feed_command('set cino={s,fs') - feed(']]=/ foo<cr>') - - expect([=[ - - void bar(void) - { - static array[2][2] = - { - { 1, 2 }, - { 3, 4 }, - } - - while (a) - { - foo(&a); - } - - { - int a; - { - a = a + 1; - } - } - b = a; - } - - void func(void) - { - a = 1; - { - b = 2; - } - c = 3; - d = 4; - } - /* foo */ - ]=]) - end) - - it('12 is working', function() - insert_([=[ - - a() - { - do { - a = a + - a; - } while ( a ); /* add text under this line */ - if ( a ) - a; - } - ]=]) - - feed_command('set cino=') - feed_command('/while') - feed('ohere<esc>') - - expect([=[ - - a() - { - do { - a = a + - a; - } while ( a ); /* add text under this line */ - here - if ( a ) - a; - } - ]=]) - end) - - it('13 is working', function() - insert_([=[ - - a() - { - label1: - /* hmm */ - // comment - } - ]=]) - - feed_command('set cino= com=') - feed_command('/comment') - feed('olabel2: b();<cr>label3 /* post */:<cr>/* pre */ label4:<cr>f(/*com*/);<cr>if (/*com*/)<cr>cmd();<esc>') - - expect([=[ - - a() - { - label1: - /* hmm */ - // comment - label2: b(); - label3 /* post */: - /* pre */ label4: - f(/*com*/); - if (/*com*/) - cmd(); - } - ]=]) - end) - - it('14 is working', function() - insert_([=[ - - /* - * A simple comment - */ - - /* - ** A different comment - */ - ]=]) - - feed_command('set comments& comments^=s:/*,m:**,ex:*/') - feed_command('/simple') - feed('=5j<cr>') - - expect([=[ - - /* - * A simple comment - */ - - /* - ** A different comment - */ - ]=]) - end) - - it('15 is working', function() - insert_([=[ - - - void f() - { - - /********* - A comment. - *********/ - } - ]=]) - - feed_command('set cino=c0') - feed_command('set comments& comments-=s1:/* comments^=s0:/*') - feed('2kdd]]=][<cr>') - - expect([=[ - - void f() - { - - /********* - A comment. - *********/ - } - ]=]) - end) - - it('16 is working', function() - insert_([=[ - - - void f() - { - - /********* - A comment. - *********/ - } - ]=]) - - feed_command('set cino=c0,C1') - feed_command('set comments& comments-=s1:/* comments^=s0:/*') - feed('2kdd]]=][<cr>') - - expect([=[ - - void f() - { - - /********* - A comment. - *********/ - } - ]=]) - end) - - it('17 is working', function() - insert_([=[ - - void f() - { - c = c1 && - ( - c2 || - c3 - ) && c4; - } - ]=]) - - feed_command('set cino=') - feed(']]=][<cr>') - - expect([=[ - - void f() - { - c = c1 && - ( - c2 || - c3 - ) && c4; - } - ]=]) - end) - - it('18 is working', function() - insert_([=[ - - - void f() - { - c = c1 && - ( - c2 || - c3 - ) && c4; - } - ]=]) - - feed_command('set cino=(s') - feed('2kdd]]=][<cr>') - - expect([=[ - - void f() - { - c = c1 && - ( - c2 || - c3 - ) && c4; - } - ]=]) - end) - - it('19 is working', function() - insert_([=[ - - - void f() - { - c = c1 && - ( - c2 || - c3 - ) && c4; - } - ]=]) - - feed_command('set cino=(s,U1 ') - feed('2kdd]]=][<cr>') - - expect([=[ - - void f() - { - c = c1 && - ( - c2 || - c3 - ) && c4; - } - ]=]) - end) - - it('20 is working', function() - insert_([=[ - - - void f() - { - if ( c1 - && ( c2 - || c3)) - foo; - } - ]=]) - - feed_command('set cino=(0') - feed('2kdd]]=][<cr>') - - expect([=[ - - void f() - { - if ( c1 - && ( c2 - || c3)) - foo; - } - ]=]) - end) - - it('21 is working', function() - insert_([=[ - - - void f() - { - if ( c1 - && ( c2 - || c3)) - foo; - } - ]=]) - - feed_command('set cino=(0,w1 ') - feed('2kdd]]=][<cr>') - - expect([=[ - - void f() - { - if ( c1 - && ( c2 - || c3)) - foo; - } - ]=]) - end) - - it('22 is working', function() - insert_([=[ - - - void f() - { - c = c1 && ( - c2 || - c3 - ) && c4; - if ( - c1 && c2 - ) - foo; - } - ]=]) - - feed_command('set cino=(s') - feed('2kdd]]=][<cr>') - - expect([=[ - - void f() - { - c = c1 && ( - c2 || - c3 - ) && c4; - if ( - c1 && c2 - ) - foo; - } - ]=]) - end) - - it('23 is working', function() - insert_([=[ - - - void f() - { - c = c1 && ( - c2 || - c3 - ) && c4; - if ( - c1 && c2 - ) - foo; - } - ]=]) - - feed_command('set cino=(s,m1 ') - feed('2kdd]]=][<cr>') - - expect([=[ - - void f() - { - c = c1 && ( - c2 || - c3 - ) && c4; - if ( - c1 && c2 - ) - foo; - } - ]=]) - end) - - it('24 is working', function() - insert_([=[ - - - void f() - { - switch (x) - { - case 1: - a = b; - break; - default: - a = 0; - break; - } - } - ]=]) - - feed_command('set cino=b1') - feed('2kdd]]=][<cr>') - - expect([=[ - - void f() - { - switch (x) - { - case 1: - a = b; - break; - default: - a = 0; - break; - } - } - ]=]) - end) - - it('25 is working', function() - insert_([=[ - - - void f() - { - invokeme( - argu, - ment); - invokeme( - argu, - ment - ); - invokeme(argu, - ment - ); - } - ]=]) - - feed_command('set cino=(0,W5') - feed('2kdd]]=][<cr>') - - expect([=[ - - void f() - { - invokeme( - argu, - ment); - invokeme( - argu, - ment - ); - invokeme(argu, - ment - ); - } - ]=]) - end) - - it('26 is working', function() - insert_([=[ - - - void f() - { - statement; - // comment 1 - // comment 2 - } - ]=]) - - feed_command('set cino=/6') - feed('2kdd]]=][<cr>') - - expect([=[ - - void f() - { - statement; - // comment 1 - // comment 2 - } - ]=]) - end) - - it('27 is working', function() - insert_([=[ - - - void f() - { - statement; - // comment 1 - // comment 2 - } - ]=]) - - feed_command('set cino=') - feed('2kdd]]/comment 1/+1<cr>') - feed('==<cr>') - - expect([=[ - - void f() - { - statement; - // comment 1 - // comment 2 - } - ]=]) - end) - - it('28 is working', function() - insert_([=[ - - - class CAbc - { - int Test() { return FALSE; } - - public: // comment - void testfall(); - protected: - void testfall(); - }; - ]=]) - - feed_command('set cino=g0') - feed('2kdd]]=][<cr>') - - expect([=[ - - class CAbc - { - int Test() { return FALSE; } - - public: // comment - void testfall(); - protected: - void testfall(); - }; - ]=]) - end) - - it('29 is working', function() - insert_([=[ - - - class Foo : public Bar - { - public: - virtual void method1(void) = 0; - virtual void method2(int arg1, - int arg2, - int arg3) = 0; - }; - ]=]) - - feed_command('set cino=(0,gs,hs') - feed('2kdd]]=][<cr>') - - expect([=[ - - class Foo : public Bar - { - public: - virtual void method1(void) = 0; - virtual void method2(int arg1, - int arg2, - int arg3) = 0; - }; - ]=]) - end) - - it('30 is working', function() - insert_([=[ - - - void - foo() - { - if (a) - { - } else - asdf; - } - ]=]) - - feed_command('set cino=+20') - feed('2kdd]]=][<cr>') - - expect([=[ - - void - foo() - { - if (a) - { - } else - asdf; - } - ]=]) - end) - - it('31 is working', function() - insert_([=[ - - - { - averylongfunctionnamelongfunctionnameaverylongfunctionname()->asd( - asdasdf, - func(asdf, - asdfadsf), - asdfasdf - ); - - /* those are ugly, but consequent */ - - func()->asd(asdasdf, - averylongfunctionname( - abc, - dec)->averylongfunctionname( - asdfadsf, - asdfasdf, - asdfasdf, - ), - func(asdfadf, - asdfasdf - ), - asdasdf - ); - - averylongfunctionnameaverylongfunctionnameavery()->asd(fasdf( - abc, - dec)->asdfasdfasdf( - asdfadsf, - asdfasdf, - asdfasdf, - ), - func(asdfadf, - asdfasdf), - asdasdf - ); - } - ]=]) - - feed_command('set cino=(0,W2s') - feed('2kdd]]=][<cr>') - - expect([=[ - - { - averylongfunctionnamelongfunctionnameaverylongfunctionname()->asd( - asdasdf, - func(asdf, - asdfadsf), - asdfasdf - ); - - /* those are ugly, but consequent */ - - func()->asd(asdasdf, - averylongfunctionname( - abc, - dec)->averylongfunctionname( - asdfadsf, - asdfasdf, - asdfasdf, - ), - func(asdfadf, - asdfasdf - ), - asdasdf - ); - - averylongfunctionnameaverylongfunctionnameavery()->asd(fasdf( - abc, - dec)->asdfasdfasdf( - asdfadsf, - asdfasdf, - asdfasdf, - ), - func(asdfadf, - asdfasdf), - asdasdf - ); - } - ]=]) - end) - - it('32 is working', function() - insert_([=[ - - - int main () - { - if (cond1 && - cond2 - ) - foo; - } - ]=]) - - feed_command('set cino=M1') - feed('2kdd]]=][<cr>') - - expect([=[ - - int main () - { - if (cond1 && - cond2 - ) - foo; - } - ]=]) - end) - - it('33 is working', function() - insert_([=[ - - - void func(int a - #if defined(FOO) - , int b - , int c - #endif - ) - { - } - ]=]) - - feed_command('set cino=(0,ts') - feed('2kdd2j=][<cr>') - - expect([=[ - - void func(int a - #if defined(FOO) - , int b - , int c - #endif - ) - { - } - ]=]) - end) - - it('34 is working', function() - insert_([=[ - - - - void - func(int a - #if defined(FOO) - , int b - , int c - #endif - ) - { - } - ]=]) - - feed_command('set cino=(0') - feed('2kdd2j=][<cr>') - - expect([=[ - - - void - func(int a - #if defined(FOO) - , int b - , int c - #endif - ) - { - } - ]=]) - end) - - it('35 is working', function() - insert_([=[ - - - void func(void) - { - if(x==y) - if(y==z) - foo=1; - else { bar=1; - baz=2; - } - printf("Foo!\n"); - } - - void func1(void) - { - char* tab[] = {"foo", "bar", - "baz", "quux", - "this line used", "to be indented incorrectly"}; - foo(); - } - - void func2(void) - { - int tab[] = - {1, 2, - 3, 4, - 5, 6}; - - printf("This line used to be indented incorrectly.\n"); - } - - int foo[] - #ifdef BAR - - = { 1, 2, 3, - 4, 5, 6 } - - #endif - ; - int baz; - - void func3(void) - { - int tab[] = { - 1, 2, - 3, 4, - 5, 6}; - - printf("Don't you dare indent this line incorrectly!\n"); - } - - void - func4(a, b, - c) - int a; - int b; - int c; - { - } - - void - func5( - int a, - int b) - { - } - - void - func6( - int a) - { - } - ]=]) - - feed_command('set cino&') - feed('2kdd2j=7][<cr>') - - expect([=[ - - void func(void) - { - if(x==y) - if(y==z) - foo=1; - else { bar=1; - baz=2; - } - printf("Foo!\n"); - } - - void func1(void) - { - char* tab[] = {"foo", "bar", - "baz", "quux", - "this line used", "to be indented incorrectly"}; - foo(); - } - - void func2(void) - { - int tab[] = - {1, 2, - 3, 4, - 5, 6}; - - printf("This line used to be indented incorrectly.\n"); - } - - int foo[] - #ifdef BAR - - = { 1, 2, 3, - 4, 5, 6 } - - #endif - ; - int baz; - - void func3(void) - { - int tab[] = { - 1, 2, - 3, 4, - 5, 6}; - - printf("Don't you dare indent this line incorrectly!\n"); - } - - void - func4(a, b, - c) - int a; - int b; - int c; - { - } - - void - func5( - int a, - int b) - { - } - - void - func6( - int a) - { - } - ]=]) - end) - - it('36 is working', function() - insert_([=[ - - - void func(void) - { - int tab[] = - { - 1, 2, 3, - 4, 5, 6}; - - printf("Indent this line correctly!\n"); - - switch (foo) - { - case bar: - printf("bar"); - break; - case baz: { - printf("baz"); - break; - } - case quux: - printf("But don't break the indentation of this instruction\n"); - break; - } - } - ]=]) - - feed_command('set cino&') - feed_command('set cino+=l1') - feed('2kdd2j=][<cr>') - - expect([=[ - - void func(void) - { - int tab[] = - { - 1, 2, 3, - 4, 5, 6}; - - printf("Indent this line correctly!\n"); - - switch (foo) - { - case bar: - printf("bar"); - break; - case baz: { - printf("baz"); - break; - } - case quux: - printf("But don't break the indentation of this instruction\n"); - break; - } - } - ]=]) - end) - - it('37 is working', function() - insert_([=[ - - - void func(void) - { - cout << "a" - << "b" - << ") :" - << "c"; - } - ]=]) - - feed_command('set cino&') - feed('2kdd2j=][<cr>') - - expect([=[ - - void func(void) - { - cout << "a" - << "b" - << ") :" - << "c"; - } - ]=]) - end) - - it('38 is working', function() - insert_([=[ - - void func(void) - { - /* - * This is a comment. - */ - } - ]=]) - - feed_command('set com=s1:/*,m:*,ex:*/') - feed(']]3jofoo();<esc>') - - expect([=[ - - void func(void) - { - /* - * This is a comment. - */ - foo(); - } - ]=]) - end) - - it('39 is working', function() - insert_([=[ - - - void func(void) - { - for (int i = 0; i < 10; ++i) - if (i & 1) { - foo(1); - } else - foo(0); - baz(); - } - ]=]) - - feed_command('set cino&') - feed('2kdd2j=][<cr>') - - expect([=[ - - void func(void) - { - for (int i = 0; i < 10; ++i) - if (i & 1) { - foo(1); - } else - foo(0); - baz(); - } - ]=]) - end) - - it('40 is working', function() - insert_([=[ - - - void func(void) - { - if (condition1 - && condition2) - action(); - function(argument1 - && argument2); - - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3)) - { - } - - if ( c1 - && ( c2 - || c3)) - foo; - func( c1 - && ( c2 - || c3)) - foo; - } - ]=]) - - feed_command('set cino=k2s,(0') - feed('2kdd3j=][<cr>') - - expect([=[ - - void func(void) - { - if (condition1 - && condition2) - action(); - function(argument1 - && argument2); - - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3)) - { - } - - if ( c1 - && ( c2 - || c3)) - foo; - func( c1 - && ( c2 - || c3)) - foo; - } - ]=]) - end) - - it('41 is working', function() - insert_([=[ - - - void func(void) - { - if (condition1 - && condition2) - action(); - function(argument1 - && argument2); - - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3)) - { - } - - if ( c1 - && ( c2 - || c3)) - foo; - func( c1 - && ( c2 - || c3)) - foo; - } - ]=]) - - feed_command('set cino=k2s,(s') - feed('2kdd3j=][<cr>') - - expect([=[ - - void func(void) - { - if (condition1 - && condition2) - action(); - function(argument1 - && argument2); - - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3)) - { - } - - if ( c1 - && ( c2 - || c3)) - foo; - func( c1 - && ( c2 - || c3)) - foo; - } - ]=]) - end) - - it('42 is working', function() - insert_([=[ - - - void func(void) - { - if (condition1 - && condition2) - action(); - function(argument1 - && argument2); - - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3)) - { - } - if (c123456789 - && (c22345 - || c3)) - printf("foo\n"); - - c = c1 && - ( - c2 || - c3 - ) && c4; - } - ]=]) - - feed_command('set cino=k2s,(s,U1') - feed('2kdd3j=][<cr>') - - expect([=[ - - void func(void) - { - if (condition1 - && condition2) - action(); - function(argument1 - && argument2); - - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3)) - { - } - if (c123456789 - && (c22345 - || c3)) - printf("foo\n"); - - c = c1 && - ( - c2 || - c3 - ) && c4; - } - ]=]) - end) - - it('43 is working', function() - insert_([=[ - - - void func(void) - { - if (condition1 - && condition2) - action(); - function(argument1 - && argument2); - - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3)) - { - } - if (c123456789 - && (c22345 - || c3)) - printf("foo\n"); - - if ( c1 - && ( c2 - || c3)) - foo; - - a_long_line( - argument, - argument); - a_short_line(argument, - argument); - } - ]=]) - - feed_command('set cino=k2s,(0,W4') - feed('2kdd3j=][<cr>') - - expect([=[ - - void func(void) - { - if (condition1 - && condition2) - action(); - function(argument1 - && argument2); - - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3)) - { - } - if (c123456789 - && (c22345 - || c3)) - printf("foo\n"); - - if ( c1 - && ( c2 - || c3)) - foo; - - a_long_line( - argument, - argument); - a_short_line(argument, - argument); - } - ]=]) - end) - - it('44 is working', function() - insert_([=[ - - - void func(void) - { - if (condition1 - && condition2) - action(); - function(argument1 - && argument2); - - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3)) - { - } - if (c123456789 - && (c22345 - || c3)) - printf("foo\n"); - } - ]=]) - - feed_command('set cino=k2s,u2') - feed('2kdd3j=][<cr>') - - expect([=[ - - void func(void) - { - if (condition1 - && condition2) - action(); - function(argument1 - && argument2); - - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3)) - { - } - if (c123456789 - && (c22345 - || c3)) - printf("foo\n"); - } - ]=]) - end) - - it('45 is working', function() - insert_([=[ - - - void func(void) - { - if (condition1 - && condition2) - action(); - function(argument1 - && argument2); - - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3)) - { - } - if (c123456789 - && (c22345 - || c3)) - printf("foo\n"); - - if ( c1 - && ( c2 - || c3)) - foo; - func( c1 - && ( c2 - || c3)) - foo; - } - ]=]) - - feed_command('set cino=k2s,(0,w1') - feed('2kdd3j=][<cr>') - - expect([=[ - - void func(void) - { - if (condition1 - && condition2) - action(); - function(argument1 - && argument2); - - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3)) - { - } - if (c123456789 - && (c22345 - || c3)) - printf("foo\n"); - - if ( c1 - && ( c2 - || c3)) - foo; - func( c1 - && ( c2 - || c3)) - foo; - } - ]=]) - end) - - it('46 is working', function() - insert_([=[ - - - void func(void) - { - if (condition1 - && condition2) - action(); - function(argument1 - && argument2); - - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3)) - { - } - } - ]=]) - - feed_command('set cino=k2,(s') - feed('2kdd3j=][<cr>') - - expect([=[ - - void func(void) - { - if (condition1 - && condition2) - action(); - function(argument1 - && argument2); - - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3)) - { - } - } - ]=]) - end) - - it('47 is working', function() - insert_([=[ - - NAMESPACESTART - /* valid namespaces with normal indent */ - namespace - { - { - 111111111111; - } - } - namespace /* test */ - { - 11111111111111111; - } - namespace // test - { - 111111111111111111; - } - namespace - { - 111111111111111111; - } - namespace test - { - 111111111111111111; - } - namespace test::cpp17 - { - 111111111111111111; - } - namespace ::incorrectcpp17 - { - 111111111111111111; - } - namespace test::incorrectcpp17:: - { - 111111111111111111; - } - namespace test:incorrectcpp17 - { - 111111111111111111; - } - namespace test:::incorrectcpp17 - { - 111111111111111111; - } - namespace{ - 111111111111111111; - } - namespace test{ - 111111111111111111; - } - namespace { - 111111111111111111; - } - namespace test { - 111111111111111111; - namespace test2 { - 22222222222222222; - } - } - - /* invalid namespaces use block indent */ - namespace test test2 { - 111111111111111111111; - } - namespace11111111111 { - 111111111111; - } - namespace() { - 1111111111111; - } - namespace() - { - 111111111111111111; - } - namespace test test2 - { - 1111111111111111111; - } - namespace111111111 - { - 111111111111111111; - } - NAMESPACEEND - ]=]) - - feed_command('set cino=N-s') - feed_command('/^NAMESPACESTART') - feed('=/^NAMESPACEEND<cr>') - - expect([=[ - - NAMESPACESTART - /* valid namespaces with normal indent */ - namespace - { - { - 111111111111; - } - } - namespace /* test */ - { - 11111111111111111; - } - namespace // test - { - 111111111111111111; - } - namespace - { - 111111111111111111; - } - namespace test - { - 111111111111111111; - } - namespace test::cpp17 - { - 111111111111111111; - } - namespace ::incorrectcpp17 - { - 111111111111111111; - } - namespace test::incorrectcpp17:: - { - 111111111111111111; - } - namespace test:incorrectcpp17 - { - 111111111111111111; - } - namespace test:::incorrectcpp17 - { - 111111111111111111; - } - namespace{ - 111111111111111111; - } - namespace test{ - 111111111111111111; - } - namespace { - 111111111111111111; - } - namespace test { - 111111111111111111; - namespace test2 { - 22222222222222222; - } - } - - /* invalid namespaces use block indent */ - namespace test test2 { - 111111111111111111111; - } - namespace11111111111 { - 111111111111; - } - namespace() { - 1111111111111; - } - namespace() - { - 111111111111111111; - } - namespace test test2 - { - 1111111111111111111; - } - namespace111111111 - { - 111111111111111111; - } - NAMESPACEEND - ]=]) - end) - - it('48 is working', function() - insert_([=[ - - JSSTART - var bar = { - foo: { - that: this, - some: ok, - }, - "bar":{ - a : 2, - b: "123abc", - x: 4, - "y": 5 - } - } - JSEND - ]=]) - - feed_command('set cino=j1,J1') - feed_command('/^JSSTART') - feed('=/^JSEND<cr>') - - expect([=[ - - JSSTART - var bar = { - foo: { - that: this, - some: ok, - }, - "bar":{ - a : 2, - b: "123abc", - x: 4, - "y": 5 - } - } - JSEND - ]=]) - end) - - it('49 is working', function() - insert_([=[ - - JSSTART - var foo = [ - 1, - 2, - 3 - ]; - JSEND - ]=]) - - feed_command('set cino=j1,J1') - feed_command('/^JSSTART') - feed('=/^JSEND<cr>') - - expect([=[ - - JSSTART - var foo = [ - 1, - 2, - 3 - ]; - JSEND - ]=]) - end) - - it('50 is working', function() - insert_([=[ - - JSSTART - function bar() { - var foo = [ - 1, - 2, - 3 - ]; - } - JSEND - ]=]) - - feed_command('set cino=j1,J1') - feed_command('/^JSSTART') - feed('=/^JSEND<cr>') - - expect([=[ - - JSSTART - function bar() { - var foo = [ - 1, - 2, - 3 - ]; - } - JSEND - ]=]) - end) - - it('51 is working', function() - insert_([=[ - - JSSTART - (function($){ - - if (cond && - cond) { - stmt; - } - window.something.left = - (width - 50 + offset) + "px"; - var class_name='myclass'; - - function private_method() { - } - - var public_method={ - method: function(options,args){ - private_method(); - } - } - - function init(options) { - - $(this).data(class_name+'_public',$.extend({},{ - foo: 'bar', - bar: 2, - foobar: [ - 1, - 2, - 3 - ], - callback: function(){ - return true; - } - }, options||{})); - } - - $.fn[class_name]=function() { - - var _arguments=arguments; - return this.each(function(){ - - var options=$(this).data(class_name+'_public'); - if (!options) { - init.apply(this,_arguments); - - } else { - var method=public_method[_arguments[0]]; - - if (typeof(method)!='function') { - console.log(class_name+' has no method "'+_arguments[0]+'"'); - return false; - } - _arguments[0]=options; - method.apply(this,_arguments); - } - }); - } - - })(jQuery); - JSEND - ]=]) - - feed_command('set cino=j1,J1') - feed_command('/^JSSTART') - feed('=/^JSEND<cr>') - - expect([=[ - - JSSTART - (function($){ - - if (cond && - cond) { - stmt; - } - window.something.left = - (width - 50 + offset) + "px"; - var class_name='myclass'; - - function private_method() { - } - - var public_method={ - method: function(options,args){ - private_method(); - } - } - - function init(options) { - - $(this).data(class_name+'_public',$.extend({},{ - foo: 'bar', - bar: 2, - foobar: [ - 1, - 2, - 3 - ], - callback: function(){ - return true; - } - }, options||{})); - } - - $.fn[class_name]=function() { - - var _arguments=arguments; - return this.each(function(){ - - var options=$(this).data(class_name+'_public'); - if (!options) { - init.apply(this,_arguments); - - } else { - var method=public_method[_arguments[0]]; - - if (typeof(method)!='function') { - console.log(class_name+' has no method "'+_arguments[0]+'"'); - return false; - } - _arguments[0]=options; - method.apply(this,_arguments); - } - }); - } - - })(jQuery); - JSEND - ]=]) - end) - - it('52 is working', function() - insert_([=[ - - JSSTART - function init(options) { - $(this).data(class_name+'_public',$.extend({},{ - foo: 'bar', - bar: 2, - foobar: [ - 1, - 2, - 3 - ], - callback: function(){ - return true; - } - }, options||{})); - } - JSEND - ]=]) - - feed_command('set cino=j1,J1') - feed_command('/^JSSTART') - feed('=/^JSEND<cr>') - - expect([=[ - - JSSTART - function init(options) { - $(this).data(class_name+'_public',$.extend({},{ - foo: 'bar', - bar: 2, - foobar: [ - 1, - 2, - 3 - ], - callback: function(){ - return true; - } - }, options||{})); - } - JSEND - ]=]) - end) - - it('53 is working', function() - insert_([=[ - - JSSTART - (function($){ - function init(options) { - $(this).data(class_name+'_public',$.extend({},{ - foo: 'bar', - bar: 2, - foobar: [ - 1, - 2, - 3 - ], - callback: function(){ - return true; - } - }, options||{})); - } - })(jQuery); - JSEND - ]=]) - - feed_command('set cino=j1,J1') - feed_command('/^JSSTART') - feed('=/^JSEND<cr>') - - expect([=[ - - JSSTART - (function($){ - function init(options) { - $(this).data(class_name+'_public',$.extend({},{ - foo: 'bar', - bar: 2, - foobar: [ - 1, - 2, - 3 - ], - callback: function(){ - return true; - } - }, options||{})); - } - })(jQuery); - JSEND - ]=]) - end) - - it('javascript indent / vim-patch 7.4.670', function() - insert_([=[ - - JSSTART - // Results of JavaScript indent - // 1 - (function(){ - var a = [ - 'a', - 'b', - 'c', - 'd', - 'e', - 'f', - 'g', - 'h', - 'i' - ]; - }()) - - // 2 - (function(){ - var a = [ - 0 + - 5 * - 9 * - 'a', - 'b', - 0 + - 5 * - 9 * - 'c', - 'd', - 'e', - 'f', - 'g', - 'h', - 'i' - ]; - }()) - - // 3 - (function(){ - var a = [ - 0 + - // comment 1 - 5 * - /* comment 2 */ - 9 * - 'a', - 'b', - 0 + - 5 * - 9 * - 'c', - 'd', - 'e', - 'f', - 'g', - 'h', - 'i' - ]; - }()) - - // 4 - { - var a = [ - 0, - 1 - ]; - var b; - var c; - } - - // 5 - { - var a = [ - [ - 0 - ], - 2, - 3 - ]; - } - - // 6 - { - var a = [ - [ - 0, - 1 - ], - 2, - 3 - ]; - } - - // 7 - { - var a = [ - // [ - 0, - // 1 - // ], - 2, - 3 - ]; - } - - // 8 - var x = [ - (function(){ - var a, - b, - c, - d, - e, - f, - g, - h, - i; - }) - ]; - - // 9 - var a = [ - 0 + - 5 * - 9 * - 'a', - 'b', - 0 + - 5 * - 9 * - 'c', - 'd', - 'e', - 'f', - 'g', - 'h', - 'i' - ]; - - // 10 - var a, - b, - c, - d, - e, - f, - g, - h, - i; - JSEND - ]=]) - - -- :set cino=j1,J1,+2 - feed_command('set cino=j1,J1,+2') - feed_command('/^JSSTART') - feed('=/^JSEND<cr>') - - expect([=[ - - JSSTART - // Results of JavaScript indent - // 1 - (function(){ - var a = [ - 'a', - 'b', - 'c', - 'd', - 'e', - 'f', - 'g', - 'h', - 'i' - ]; - }()) - - // 2 - (function(){ - var a = [ - 0 + - 5 * - 9 * - 'a', - 'b', - 0 + - 5 * - 9 * - 'c', - 'd', - 'e', - 'f', - 'g', - 'h', - 'i' - ]; - }()) - - // 3 - (function(){ - var a = [ - 0 + - // comment 1 - 5 * - /* comment 2 */ - 9 * - 'a', - 'b', - 0 + - 5 * - 9 * - 'c', - 'd', - 'e', - 'f', - 'g', - 'h', - 'i' - ]; - }()) - - // 4 - { - var a = [ - 0, - 1 - ]; - var b; - var c; - } - - // 5 - { - var a = [ - [ - 0 - ], - 2, - 3 - ]; - } - - // 6 - { - var a = [ - [ - 0, - 1 - ], - 2, - 3 - ]; - } - - // 7 - { - var a = [ - // [ - 0, - // 1 - // ], - 2, - 3 - ]; - } - - // 8 - var x = [ - (function(){ - var a, - b, - c, - d, - e, - f, - g, - h, - i; - }) - ]; - - // 9 - var a = [ - 0 + - 5 * - 9 * - 'a', - 'b', - 0 + - 5 * - 9 * - 'c', - 'd', - 'e', - 'f', - 'g', - 'h', - 'i' - ]; - - // 10 - var a, - b, - c, - d, - e, - f, - g, - h, - i; - JSEND - ]=]) - end) - - it('line continuations in macros / vim-patch 8.0.0148', function() - insert_([=[ - /* start of define */ - { - } - #define AAA \ - BBB\ - CCC - - #define CNT \ - 1 + \ - 2 + \ - 4 - /* end of define */]=]) - - feed_command('set cino&') - feed_command('/start of define') - feed('=/end of define<cr>') - - expect([=[ - /* start of define */ - { - } - #define AAA \ - BBB\ - CCC - - #define CNT \ - 1 + \ - 2 + \ - 4 - /* end of define */]=]) - end) - - it('* immediately follows comment / vim-patch 8.0.1291', function() - insert_([=[ - { - a = second/*bug*/*line; - }]=]) - - feed_command('set cin cino&') - feed_command('/a = second') - feed('ox') - - expect([=[ - { - a = second/*bug*/*line; - x - }]=]) - end) -end) diff --git a/test/functional/legacy/eval_spec.lua b/test/functional/legacy/eval_spec.lua index b5de5cd232..05d853622e 100644 --- a/test/functional/legacy/eval_spec.lua +++ b/test/functional/legacy/eval_spec.lua @@ -666,7 +666,7 @@ describe('eval', function() source([[ " Vim script used in test_eval.in. Needed for script-local function. - func! s:Testje() + func s:Testje() return "foo" endfunc 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/expand_spec.lua b/test/functional/legacy/expand_spec.lua deleted file mode 100644 index cd3713eabe..0000000000 --- a/test/functional/legacy/expand_spec.lua +++ /dev/null @@ -1,129 +0,0 @@ --- Test for expanding file names - -local helpers = require('test.functional.helpers')(after_each) -local eq = helpers.eq -local call = helpers.call -local nvim = helpers.meths -local clear = helpers.clear -local source = helpers.source - -local function expected_empty() - eq({}, nvim.get_vvar('errors')) -end - -describe('expand file name', function() - after_each(function() - helpers.rmdir('Xdir1') - helpers.rmdir('Xdir2') - helpers.rmdir('Xdir3') - helpers.rmdir('Xdir4') - end) - - before_each(function() - clear() - - source([[ - func Test_with_directories() - call mkdir('Xdir1') - call mkdir('Xdir2') - call mkdir('Xdir3') - cd Xdir3 - call mkdir('Xdir4') - cd .. - - split Xdir1/file - call setline(1, ['a', 'b']) - w - w Xdir3/Xdir4/file - close - - next Xdir?/*/file - call assert_equal('Xdir3/Xdir4/file', expand('%')) - if has('unix') - next! Xdir?/*/nofile - call assert_equal('Xdir?/*/nofile', expand('%')) - endif - " Edit another file, on MS-Windows the swap file would be in use and can't - " be deleted - edit foo - - call assert_equal(0, delete('Xdir1', 'rf')) - call assert_equal(0, delete('Xdir2', 'rf')) - call assert_equal(0, delete('Xdir3', 'rf')) - endfunc - - func Test_with_tilde() - let dir = getcwd() - call mkdir('Xdir ~ dir') - call assert_true(isdirectory('Xdir ~ dir')) - cd Xdir\ ~\ dir - call assert_true(getcwd() =~ 'Xdir \~ dir') - exe 'cd ' . fnameescape(dir) - call delete('Xdir ~ dir', 'd') - call assert_false(isdirectory('Xdir ~ dir')) - endfunc - - func Test_expand_tilde_filename() - split ~ - call assert_equal('~', expand('%')) - call assert_notequal(expand('%:p'), expand('~/')) - call assert_match('\~', expand('%:p')) - bwipe! - endfunc - - func Test_expandcmd() - let $FOO = 'Test' - call assert_equal('e x/Test/y', expandcmd('e x/$FOO/y')) - unlet $FOO - - new - edit Xfile1 - call assert_equal('e Xfile1', expandcmd('e %')) - edit Xfile2 - edit Xfile1 - call assert_equal('e Xfile2', 'e #'->expandcmd()) - edit Xfile2 - edit Xfile3 - edit Xfile4 - let bnum = bufnr('Xfile2') - call assert_equal('e Xfile2', expandcmd('e #' . bnum)) - call setline('.', 'Vim!@#') - call assert_equal('e Vim', expandcmd('e <cword>')) - call assert_equal('e Vim!@#', expandcmd('e <cWORD>')) - enew! - edit Xfile.java - call assert_equal('e Xfile.py', expandcmd('e %:r.py')) - call assert_equal('make abc.java', expandcmd('make abc.%:e')) - call assert_equal('make Xabc.java', expandcmd('make %:s?file?abc?')) - edit a1a2a3.rb - call assert_equal('make b1b2b3.rb a1a2a3 Xfile.o', expandcmd('make %:gs?a?b? %< #<.o')) - - call assert_fails('call expandcmd("make <afile>")', 'E495:') - call assert_fails('call expandcmd("make <afile>")', 'E495:') - enew - call assert_fails('call expandcmd("make %")', 'E499:') - close - endfunc - ]]) - end) - - it('works with directories', function() - call('Test_with_directories') - expected_empty() - end) - - it('works with tilde', function() - call('Test_with_tilde') - expected_empty() - end) - - it('does not expand tilde if it is a filename', function() - call('Test_expand_tilde_filename') - expected_empty() - end) - - it('works with expandcmd()', function() - call('Test_expandcmd') - expected_empty() - end) -end) diff --git a/test/functional/legacy/search_stat_spec.lua b/test/functional/legacy/search_stat_spec.lua new file mode 100644 index 0000000000..fdd46c0cb9 --- /dev/null +++ b/test/functional/legacy/search_stat_spec.lua @@ -0,0 +1,121 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear, feed, exec, command = helpers.clear, helpers.feed, helpers.exec, helpers.command +local poke_eventloop = helpers.poke_eventloop + +describe('search stat', function() + local screen + before_each(function() + clear() + screen = Screen.new(30, 10) + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [2] = {background = Screen.colors.Yellow}, -- Search + [3] = {foreground = Screen.colors.Blue4, background = Screen.colors.LightGrey}, -- Folded + }) + screen:attach() + end) + + it('right spacing with silent mapping vim-patch:8.1.1970', function() + exec([[ + set shortmess-=S + " Append 50 lines with text to search for, "foobar" appears 20 times + call append(0, repeat(['foobar', 'foo', 'fooooobar', 'foba', 'foobar'], 20)) + call setline(2, 'find this') + call setline(70, 'find this') + nnoremap n n + let @/ = 'find this' + call cursor(1,1) + norm n + ]]) + screen:expect([[ + foobar | + {2:^find this} | + fooooobar | + foba | + foobar | + foobar | + foo | + fooooobar | + foba | + /find this [1/2] | + ]]) + command('nnoremap <silent> n n') + feed('gg0n') + screen:expect([[ + foobar | + {2:^find this} | + fooooobar | + foba | + foobar | + foobar | + foo | + fooooobar | + foba | + [1/2] | + ]]) + end) + + it('when only match is in fold vim-patch:8.2.0840', function() + exec([[ + set shortmess-=S + setl foldenable foldmethod=indent foldopen-=search + call append(0, ['if', "\tfoo", "\tfoo", 'endif']) + let @/ = 'foo' + call cursor(1,1) + norm n + ]]) + screen:expect([[ + if | + {3:^+-- 2 lines: foo·············}| + endif | + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + /foo [1/2] | + ]]) + feed('n') + poke_eventloop() + screen:expect_unchanged() + feed('n') + poke_eventloop() + screen:expect_unchanged() + end) + + it('is cleared by gd and gD vim-patch:8.2.3583', function() + exec([[ + call setline(1, ['int cat;', 'int dog;', 'cat = dog;']) + set shortmess-=S + set hlsearch + ]]) + feed('/dog<CR>') + screen:expect([[ + int cat; | + int {2:^dog}; | + cat = {2:dog}; | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + /dog [1/2] | + ]]) + feed('G0gD') + screen:expect([[ + int {2:^cat}; | + int dog; | + {2:cat} = dog; | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + end) +end) diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index ddcf7687f5..17f7a04db6 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -2406,6 +2406,17 @@ describe('lua stdlib', function() eq(buf1, meths.get_current_buf()) eq(buf2, val) end) + + it('does not cause ml_get errors with invalid visual selection', function() + -- Should be fixed by vim-patch:8.2.4028. + exec_lua [[ + local a = vim.api + local t = function(s) return a.nvim_replace_termcodes(s, true, true, true) end + a.nvim_buf_set_lines(0, 0, -1, true, {"a", "b", "c"}) + a.nvim_feedkeys(t "G<C-V>", "txn", false) + a.nvim_buf_call(a.nvim_create_buf(false, true), function() vim.cmd "redraw" end) + ]] + end) end) describe('vim.api.nvim_win_call', function() @@ -2434,6 +2445,75 @@ describe('lua stdlib', function() eq(win1, meths.get_current_win()) eq(win2, val) end) + + it('does not cause ml_get errors with invalid visual selection', function() + -- Add lines to the current buffer and make another window looking into an empty buffer. + exec_lua [[ + _G.a = vim.api + _G.t = function(s) return a.nvim_replace_termcodes(s, true, true, true) end + _G.win_lines = a.nvim_get_current_win() + vim.cmd "new" + _G.win_empty = a.nvim_get_current_win() + a.nvim_set_current_win(win_lines) + a.nvim_buf_set_lines(0, 0, -1, true, {"a", "b", "c"}) + ]] + + -- Start Visual in current window, redraw in other window with fewer lines. + -- Should be fixed by vim-patch:8.2.4018. + exec_lua [[ + a.nvim_feedkeys(t "G<C-V>", "txn", false) + a.nvim_win_call(win_empty, function() vim.cmd "redraw" end) + ]] + + -- Start Visual in current window, extend it in other window with more lines. + -- Fixed for win_execute by vim-patch:8.2.4026, but nvim_win_call should also not be affected. + exec_lua [[ + a.nvim_feedkeys(t "<Esc>gg", "txn", false) + a.nvim_set_current_win(win_empty) + a.nvim_feedkeys(t "gg<C-V>", "txn", false) + a.nvim_win_call(win_lines, function() a.nvim_feedkeys(t "G<C-V>", "txn", false) end) + vim.cmd "redraw" + ]] + end) + + it('updates ruler if cursor moved', function() + -- Fixed for win_execute in vim-patch:8.1.2124, but should've applied to nvim_win_call too! + local screen = Screen.new(30, 5) + screen:set_default_attr_ids { + [1] = {reverse = true}, + [2] = {bold = true, reverse = true}, + } + screen:attach() + exec_lua [[ + _G.a = vim.api + vim.opt.ruler = true + local lines = {} + for i = 0, 499 do lines[#lines + 1] = tostring(i) end + a.nvim_buf_set_lines(0, 0, -1, true, lines) + a.nvim_win_set_cursor(0, {20, 0}) + vim.cmd "split" + _G.win = a.nvim_get_current_win() + vim.cmd "wincmd w | redraw" + ]] + screen:expect [[ + 19 | + {1:[No Name] [+] 20,1 3%}| + ^19 | + {2:[No Name] [+] 20,1 3%}| + | + ]] + exec_lua [[ + a.nvim_win_call(win, function() a.nvim_win_set_cursor(0, {100, 0}) end) + vim.cmd "redraw" + ]] + screen:expect [[ + 99 | + {1:[No Name] [+] 100,1 19%}| + ^19 | + {2:[No Name] [+] 20,1 3%}| + | + ]] + end) end) end) diff --git a/test/functional/lua/xdiff_spec.lua b/test/functional/lua/xdiff_spec.lua index 4f28f84c01..d55268fc78 100644 --- a/test/functional/lua/xdiff_spec.lua +++ b/test/functional/lua/xdiff_spec.lua @@ -90,6 +90,48 @@ describe('xdiff bindings', function() exec_lua([[return vim.diff(a2, b2, {result_type = 'indices'})]])) end) + it('can run different algorithms', function() + local a = table.concat({ + '.foo1 {', + ' margin: 0;', + '}', + '', + '.bar {', + ' margin: 0;', + '}', + ''}, '\n') + + local b = table.concat({ + '.bar {', + ' margin: 0;', + '}', + '', + '.foo1 {', + ' margin: 0;', + ' color: green;', + '}', + ''}, '\n') + + eq( + table.concat({'@@ -1,4 +0,0 @@', + '-.foo1 {', + '- margin: 0;', + '-}', + '-', + '@@ -7,0 +4,5 @@', + '+', + '+.foo1 {', + '+ margin: 0;', + '+ color: green;', + '+}', + ''}, '\n'), + exec_lua([[ + local args = {...} + return vim.diff(args[1], args[2], { + algorithm = 'patience' + }) + ]], a, b)) + end) end) it('can handle bad args', function() diff --git a/test/functional/plugin/health_spec.lua b/test/functional/plugin/health_spec.lua index 37de5d0ce6..f7c2dbdb43 100644 --- a/test/functional/plugin/health_spec.lua +++ b/test/functional/plugin/health_spec.lua @@ -230,3 +230,14 @@ describe('health.vim', function() end) end) end) + +describe(':checkhealth provider', function() + it("works correctly with a wrongly configured 'shell'", function() + clear() + command([[set shell=echo\ WRONG!!!]]) + command('let g:loaded_perl_provider = 0') + command('let g:loaded_python3_provider = 0') + command('checkhealth provider') + eq(nil, string.match(curbuf_contents(), 'WRONG!!!')) + end) +end) diff --git a/test/functional/provider/provider_spec.lua b/test/functional/provider/provider_spec.lua index 78bc4a4edb..3895b8613f 100644 --- a/test/functional/provider/provider_spec.lua +++ b/test/functional/provider/provider_spec.lua @@ -14,8 +14,8 @@ describe('providers', function() command('set loadplugins') -- Using test-fixture with broken impl: -- test/functional/fixtures/autoload/provider/python.vim - eq('Vim:provider: python: missing required variable g:loaded_python_provider', - pcall_err(eval, "has('python')")) + eq('Vim:provider: python3: missing required variable g:loaded_python3_provider', + pcall_err(eval, "has('python3')")) end) it('with g:loaded_xx_provider, missing #Call()', function() diff --git a/test/functional/ui/inccommand_spec.lua b/test/functional/ui/inccommand_spec.lua index b6e2f2311f..10700d9508 100644 --- a/test/functional/ui/inccommand_spec.lua +++ b/test/functional/ui/inccommand_spec.lua @@ -293,6 +293,70 @@ describe(":substitute, 'inccommand' preserves", function() end) end + for _, case in ipairs({'', 'split', 'nosplit'}) do + it('previous substitute string ~ (inccommand='..case..') #12109', function() + local screen = Screen.new(30,10) + common_setup(screen, case, default_text) + + feed(':%s/Inc/SUB<CR>') + expect([[ + SUB substitution on + two lines + ]]) + + feed(':%s/line/') + poke_eventloop() + feed('~') + poke_eventloop() + feed('<CR>') + expect([[ + SUB substitution on + two SUBs + ]]) + + feed(':%s/sti/') + poke_eventloop() + feed('~') + poke_eventloop() + feed('B') + poke_eventloop() + feed('<CR>') + expect([[ + SUB subSUBBtution on + two SUBs + ]]) + + feed(':%s/ion/NEW<CR>') + expect([[ + SUB subSUBBtutNEW on + two SUBs + ]]) + + feed(':%s/two/') + poke_eventloop() + feed('N') + poke_eventloop() + feed('~') + poke_eventloop() + feed('<CR>') + expect([[ + SUB subSUBBtutNEW on + NNEW SUBs + ]]) + + feed(':%s/bS/') + poke_eventloop() + feed('~') + poke_eventloop() + feed('W') + poke_eventloop() + feed('<CR>') + expect([[ + SUB suNNEWWUBBtutNEW on + NNEW SUBs + ]]) + end) + end end) describe(":substitute, 'inccommand' preserves undo", function() diff --git a/test/functional/ui/input_spec.lua b/test/functional/ui/input_spec.lua index 11718a6e18..7000505909 100644 --- a/test/functional/ui/input_spec.lua +++ b/test/functional/ui/input_spec.lua @@ -140,6 +140,25 @@ describe('input utf sequences that contain CSI (0x9B)', function() end) end) +describe('input split utf sequences', function() + it('ok', function() + local str = '►' + feed('i' .. str:sub(1, 1)) + helpers.sleep(10) + feed(str:sub(2, 3)) + expect('►') + end) + + it('can be mapped', function() + command('inoremap ► E296BA') + local str = '►' + feed('i' .. str:sub(1, 1)) + helpers.sleep(10) + feed(str:sub(2, 3)) + expect('E296BA') + end) +end) + describe('input non-printable chars', function() after_each(function() os.remove('Xtest-overwrite') @@ -165,7 +184,7 @@ describe('input non-printable chars', function() {1:~ }| {1:~ }| {1:~ }| - "Xtest-overwrite" [noeol] 1L, 6C | + "Xtest-overwrite" [noeol] 1L, 6B | ]]) -- The timestamp is in second resolution, wait two seconds to be sure. diff --git a/test/functional/vimscript/timer_spec.lua b/test/functional/vimscript/timer_spec.lua index e45b64422f..25e46062b7 100644 --- a/test/functional/vimscript/timer_spec.lua +++ b/test/functional/vimscript/timer_spec.lua @@ -272,4 +272,12 @@ describe('timers', function() ]] eq("Vim(call):E48: Not allowed in sandbox", exc_exec("sandbox call timer_start(0, 'Scary')")) end) + + it('can be triggered after an empty string <expr> mapping', function() + local screen = Screen.new(40, 6) + screen:attach() + command([=[imap <expr> <F2> [timer_start(0, { _ -> execute("throw 'x'", "") }), ''][-1]]=]) + feed('i<F2>') + screen:expect({any='E605: Exception not caught: x'}) + end) end) diff --git a/test/helpers.lua b/test/helpers.lua index 87431e4342..522714c8be 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -815,7 +815,7 @@ end function module.read_nvim_log(logfile, ci_rename) logfile = logfile or os.getenv('NVIM_LOG_FILE') or '.nvimlog' local is_ci = module.isCI() - local keep = is_ci and 999 or 10 + local keep = is_ci and 100 or 10 local lines = module.read_file_list(logfile, -keep) or {} local log = (('-'):rep(78)..'\n' ..string.format('$NVIM_LOG_FILE: %s\n', logfile) diff --git a/test/unit/buffer_spec.lua b/test/unit/buffer_spec.lua index 3692e19379..34c88248e6 100644 --- a/test/unit/buffer_spec.lua +++ b/test/unit/buffer_spec.lua @@ -249,7 +249,7 @@ describe('buffer functions', function() -- -- @param arg Options can be placed in an optional dictionary as the last parameter -- .expected_cell_count The expected number of cells build_stl_str_hl will return - -- .expected_byte_length The expected byte length of the string + -- .expected_byte_length The expected byte length of the string (defaults to byte length of expected_stl) -- .file_name The name of the file to be tested (useful in %f type tests) -- .fillchar The character that will be used to fill any 'extra' space in the stl local function statusline_test (description, @@ -264,7 +264,7 @@ describe('buffer functions', function() local fillchar = option.fillchar or (' '):byte() local expected_cell_count = option.expected_cell_count or statusline_cell_count - local expected_byte_length = option.expected_byte_length or expected_cell_count + local expected_byte_length = option.expected_byte_length or #expected_stl itp(description, function() if option.file_name then @@ -312,12 +312,12 @@ describe('buffer functions', function() statusline_test('should put fillchar `~` in between text', 10, 'abc%=def', 'abc~~~~def', {fillchar=('~'):byte()}) + statusline_test('should put fillchar `━` in between text', 10, + 'abc%=def', 'abc━━━━def', + {fillchar=0x2501}) statusline_test('should handle zero-fillchar as a space', 10, 'abcde%=', 'abcde ', {fillchar=0}) - statusline_test('should handle multibyte-fillchar as a dash', 10, - 'abcde%=', 'abcde-----', - {fillchar=0x80}) statusline_test('should print the tail file name', 80, '%t', 'buffer_spec.lua', {file_name='test/unit/buffer_spec.lua', expected_cell_count=15}) @@ -366,70 +366,86 @@ describe('buffer functions', function() statusline_test('should ignore trailing %', 3, 'abc%', 'abc') - -- alignment testing - statusline_test('should right align when using =', 20, - 'neo%=vim', 'neo vim') - statusline_test('should, when possible, center text when using %=text%=', 20, - 'abc%=neovim%=def', 'abc neovim def') - statusline_test('should handle uneven spacing in the buffer when using %=text%=', 20, - 'abc%=neo_vim%=def', 'abc neo_vim def') - statusline_test('should have equal spaces even with non-equal sides when using =', 20, - 'foobar%=test%=baz', 'foobar test baz') - statusline_test('should have equal spaces even with longer right side when using =', 20, - 'a%=test%=longtext', 'a test longtext') - statusline_test('should handle an empty left side when using ==', 20, - '%=test%=baz', ' test baz') - statusline_test('should handle an empty right side when using ==', 20, - 'foobar%=test%=', 'foobar test ') - statusline_test('should handle consecutive empty ==', 20, - '%=%=test%=', ' test ') - statusline_test('should handle an = alone', 20, - '%=', ' ') - statusline_test('should right align text when it is alone with =', 20, - '%=foo', ' foo') - statusline_test('should left align text when it is alone with =', 20, - 'foo%=', 'foo ') - - statusline_test('should approximately center text when using %=text%=', 21, - 'abc%=neovim%=def', 'abc neovim def') - statusline_test('should completely fill the buffer when using %=text%=', 21, - 'abc%=neo_vim%=def', 'abc neo_vim def') - statusline_test('should have equal spaces even with non-equal sides when using =', 21, - 'foobar%=test%=baz', 'foobar test baz') - statusline_test('should have equal spaces even with longer right side when using =', 21, - 'a%=test%=longtext', 'a test longtext') - statusline_test('should handle an empty left side when using ==', 21, - '%=test%=baz', ' test baz') - statusline_test('should handle an empty right side when using ==', 21, - 'foobar%=test%=', 'foobar test ') - - statusline_test('should quadrant the text when using 3 %=', 40, - 'abcd%=n%=eovim%=ef', 'abcd n eovim ef') - statusline_test('should work well with %t', 40, - '%t%=right_aligned', 'buffer_spec.lua right_aligned', + -- alignment testing with fillchar + local function statusline_test_align (description, + statusline_cell_count, + input_stl, + expected_stl, + arg) + arg = arg or {} + statusline_test(description .. ' without fillchar', + statusline_cell_count, input_stl, expected_stl:gsub('%~', ' '), arg) + arg.fillchar = ('!'):byte() + statusline_test(description .. ' with fillchar `!`', + statusline_cell_count, input_stl, expected_stl:gsub('%~', '!'), arg) + arg.fillchar = 0x2501 + statusline_test(description .. ' with fillchar `━`', + statusline_cell_count, input_stl, expected_stl:gsub('%~', '━'), arg) + end + + statusline_test_align('should right align when using =', 20, + 'neo%=vim', 'neo~~~~~~~~~~~~~~vim') + statusline_test_align('should, when possible, center text when using %=text%=', 20, + 'abc%=neovim%=def', 'abc~~~~neovim~~~~def') + statusline_test_align('should handle uneven spacing in the buffer when using %=text%=', 20, + 'abc%=neo_vim%=def', 'abc~~~neo_vim~~~~def') + statusline_test_align('should have equal spaces even with non-equal sides when using =', 20, + 'foobar%=test%=baz', 'foobar~~~test~~~~baz') + statusline_test_align('should have equal spaces even with longer right side when using =', 20, + 'a%=test%=longtext', 'a~~~test~~~~longtext') + statusline_test_align('should handle an empty left side when using ==', 20, + '%=test%=baz', '~~~~~~test~~~~~~~baz') + statusline_test_align('should handle an empty right side when using ==', 20, + 'foobar%=test%=', 'foobar~~~~~test~~~~~') + statusline_test_align('should handle consecutive empty ==', 20, + '%=%=test%=', '~~~~~~~~~~test~~~~~~') + statusline_test_align('should handle an = alone', 20, + '%=', '~~~~~~~~~~~~~~~~~~~~') + statusline_test_align('should right align text when it is alone with =', 20, + '%=foo', '~~~~~~~~~~~~~~~~~foo') + statusline_test_align('should left align text when it is alone with =', 20, + 'foo%=', 'foo~~~~~~~~~~~~~~~~~') + + statusline_test_align('should approximately center text when using %=text%=', 21, + 'abc%=neovim%=def', 'abc~~~~neovim~~~~~def') + statusline_test_align('should completely fill the buffer when using %=text%=', 21, + 'abc%=neo_vim%=def', 'abc~~~~neo_vim~~~~def') + statusline_test_align('should have equal spacing even with non-equal sides when using =', 21, + 'foobar%=test%=baz', 'foobar~~~~test~~~~baz') + statusline_test_align('should have equal spacing even with longer right side when using =', 21, + 'a%=test%=longtext', 'a~~~~test~~~~longtext') + statusline_test_align('should handle an empty left side when using ==', 21, + '%=test%=baz', '~~~~~~~test~~~~~~~baz') + statusline_test_align('should handle an empty right side when using ==', 21, + 'foobar%=test%=', 'foobar~~~~~test~~~~~~') + + statusline_test_align('should quadrant the text when using 3 %=', 40, + 'abcd%=n%=eovim%=ef', 'abcd~~~~~~~~~n~~~~~~~~~eovim~~~~~~~~~~ef') + statusline_test_align('should work well with %t', 40, + '%t%=right_aligned', 'buffer_spec.lua~~~~~~~~~~~~right_aligned', {file_name='test/unit/buffer_spec.lua'}) - statusline_test('should work well with %t and regular text', 40, - 'l%=m_l %t m_r%=r', 'l m_l buffer_spec.lua m_r r', + statusline_test_align('should work well with %t and regular text', 40, + 'l%=m_l %t m_r%=r', 'l~~~~~~~m_l buffer_spec.lua m_r~~~~~~~~r', {file_name='test/unit/buffer_spec.lua'}) - statusline_test('should work well with %=, %t, %L, and %l', 40, - '%t %= %L %= %l', 'buffer_spec.lua 1 0', + statusline_test_align('should work well with %=, %t, %L, and %l', 40, + '%t %= %L %= %l', 'buffer_spec.lua ~~~~~~~~~ 1 ~~~~~~~~~~ 0', {file_name='test/unit/buffer_spec.lua'}) - statusline_test('should quadrant the text when using 3 %=', 41, - 'abcd%=n%=eovim%=ef', 'abcd n eovim ef') - statusline_test('should work well with %t', 41, - '%t%=right_aligned', 'buffer_spec.lua right_aligned', + statusline_test_align('should quadrant the text when using 3 %=', 41, + 'abcd%=n%=eovim%=ef', 'abcd~~~~~~~~~n~~~~~~~~~eovim~~~~~~~~~~~ef') + statusline_test_align('should work well with %t', 41, + '%t%=right_aligned', 'buffer_spec.lua~~~~~~~~~~~~~right_aligned', {file_name='test/unit/buffer_spec.lua'}) - statusline_test('should work well with %t and regular text', 41, - 'l%=m_l %t m_r%=r', 'l m_l buffer_spec.lua m_r r', + statusline_test_align('should work well with %t and regular text', 41, + 'l%=m_l %t m_r%=r', 'l~~~~~~~~m_l buffer_spec.lua m_r~~~~~~~~r', {file_name='test/unit/buffer_spec.lua'}) - statusline_test('should work well with %=, %t, %L, and %l', 41, - '%t %= %L %= %l', 'buffer_spec.lua 1 0', + statusline_test_align('should work well with %=, %t, %L, and %l', 41, + '%t %= %L %= %l', 'buffer_spec.lua ~~~~~~~~~~ 1 ~~~~~~~~~~ 0', {file_name='test/unit/buffer_spec.lua'}) - statusline_test('should work with 10 %=', 50, + statusline_test_align('should work with 10 %=', 50, 'aaaa%=b%=c%=d%=e%=fg%=hi%=jk%=lmnop%=qrstuv%=wxyz', - 'aaaa b c d e fg hi jk lmnop qrstuv wxyz') + 'aaaa~~b~~c~~d~~e~~fg~~hi~~jk~~lmnop~~qrstuv~~~wxyz') -- stl item testing local tabline = '' @@ -452,11 +468,10 @@ describe('buffer functions', function() -- multi-byte testing statusline_test('should handle multibyte characters', 10, - 'Ĉ%=x', 'Ĉ x', - {expected_byte_length=11}) + 'Ĉ%=x', 'Ĉ x') statusline_test('should handle multibyte characters and different fillchars', 10, 'Ą%=mid%=end', 'Ą@mid@@end', - {fillchar=('@'):byte(), expected_byte_length=11}) + {fillchar=('@'):byte()}) -- escaping % testing statusline_test('should handle escape of %', 4, 'abc%%', 'abc%') |